はじめに
kintoneアプリのレコードをバックアップしたり、他のシステムに同期する場合、
レコード一括取得REST APIを複数回実行する事で大量のレコードを取得するコードを書くと思います。
この記事では、kintoneの1つのアプリから大量のレコードを高速に取得する手法について紹介します。
この手法を使うことで、今回行った実験では50万レコードを取得する時間が33%に削減されました。
手法1: シンプルなやり方
シンプルなやり方としては、offsetの値を徐々に増やしながらレコード一括取得REST APIを繰り返し実行するというものがあります。
手法1の問題点
この手法1で問題なく全レコードを取得することができるのですが、このやり方には一つ問題があります。
それは、レコード数が多くなると非常に遅くなるというものです。
上図は取得するレコード件数を増やした時の全レコード取得にかかる時間を計測したものです。
このグラフを見るとわかるように、取得するレコードの件数が増えた時に掛かる時間が線形ではなく2次関数的に増加していきます。
手法2: より高速なやり方
offsetを使う替わりに、次のコードのようなレコードの取得の仕方をすると処理が高速化されます。
この手法のポイントは、絞り込みクエリを組み立てる際に、
前回に取得したレコードの中で一番最後のレコードのレコード番号フィールドの値を使って、
「レコード番号 > 最後のレコードのレコード番号」という条件を付け足している部分です。
なぜこの条件によって次の500件を取得できるかと言うと、
レコード一括取得を行う際に「order by レコード番号 asc」という順番でレコード一括取得を行っているためです。
この手法は、kintoneに限らずRDBMS一般で知られている手法です。この手法が高速な理由はこちらのページに詳しく紹介されています。
下図は手法1と手法2の実行時間の比較です。
手法2では、データ取得にかかる時間がレコードの数に線形に比例する形に抑えられていることがわかります。
そのため、取得するレコードの総数が多くなるほど高速化の恩恵を受けることができます。
今回の実験では50万レコードを書き出す際にはoffsetを用いる手法と比べてほぼ3倍の高速化が達成されました。
レコード一括取得REST APIの実行の仕方を変えるだけで高速化されるので、是非皆さんも試してみてください。
手法2の問題点
手法2にも問題点はあります。それはコードが複雑になるということです。
今回の例のように全レコードを書き出す場合はレコードのソート順はあまり気にしなくても良いのでレコード番号を使ってソートしていましたが、
例えば複数のフィールドでソートした結果を書き出したい場合は、手法2では絞り込み条件が複雑なものになってしまいます。
まとめ
今回はkintoneアプリのレコード取得を高速に行うことができる手法を紹介しました。
この手法はコードが複雑になってしまうというデメリットもあるので、大量のレコードを取得する際に是非ご参考にしていただければと思います。
このTipsは、2018年5月版 kintoneで確認したものになります。
> 手法2の問題点
> 手法2にも問題点はあります。それはコードが複雑になるということです。
> 今回の例のように全レコードを書き出す場合はレコードのソート順はあまり気にしなくても良いのでレコード番号を使ってソートしていましたが、
> 例えば複数のフィールドでソートした結果を書き出したい場合は、手法2では絞り込み条件が複雑なものになってしまいます。
「手法2では絞り込み条件が複雑なもの」になる というのは、具体的にどのようなことなのでしょうか。
手法2で全レコードを取り出すには $id の昇順で取得する必要があり、order by 句を使って任意のソート順を指定すると $id がバラバラで返ってきてしまう(大きいレコード番号のデータが先に返ってくると「次の500件」で取得できない)ため、全レコードを取得した後に自力でソートする必要があるということではないでしょうか。
r3_yamauchi 様
コメントをいただきありがとうございます。
現在担当者に確認中ですので、しばらくお待ちください。
Kintone Promiseも合わせてどういう風に記述したらよいか
ご教授下さい。
r3_yamauchi 様
返答遅くなりました。担当者からの返答を記載します。
なお、手法2について各お客様環境による記述のご質問ついては、当欄ではなくコミュニティのご利用をお願いいたします。
---
おっしゃる通り、$idでソートしてその後自前でソートし直す方が良いのですが、記事内で複雑な条件になると書いた背景の説明もこちらでざっと書いてみます。
例えば、手法2を使って"order by f asc, $id asc"という並び順で書き出しを行うというケースを想定します。そして、最初の500件を取得した結果、500件目のレコードはレコード番号がR、fフィールドがFという値だったとします。
この場合、次の500件を取得するには
"f >= F and not (f = F and $id <= R) order by f asc, $id asc"
という条件を指定する必要が出てきます。ソートで使うフィールドが増えると更に条件が複雑になっていきます。
こうなる理由は↓のドキュメントの「SQLの行値式」とあるコラムの下付近に記載があります。
https://use-the-index-luke.com/ja/sql/partial-results/fetch-next-page
---
Kif 様
お世話になっております。
cybozu developer network 事務局です。
こちらのコメント欄は直接記事の内容にかかわるフィードバックを受け付けております。
お手数ではありますが、技術的なコメントはコミュニティをご利用ください。
有志の技術者からフィードバックを頂けると思います。
宜しくお願い致します。
ご回答ありがとうございます。
> "f >= F and not (f = F and $id <= R) order by f asc, $id asc"
なるほど。opt_last_record_id だけでなく、 FとRを対で fetch_fastの再帰呼び出しに渡すようにすれば
可能ということですね。
理解できました。ありがとうございます。