REST API GET の応答時間について

■質問の背景■
REST API GET(kintoneUtility.rest.getAllRecordsByQuery)を利用して、
自身のアプリのレコードを取得する際に、取得するレコード件数が少し増えると
コマンド発行から結果取得までの応答時間がとても長くなります。

例)
全レコード数:15件
応答時間:1.4 秒   (気にならない速度)

全レコード数:4191件
応答時間:20.1 秒  (とても遅い)

1レコード中の項目数は27項目程度で、各項目のデータは約20文字以内の
文字列などです。
他のアプリから全レコードを取得するときも同じようにレコード数が
少し増えると、応答時間がとても遅くなります。

レコード数は最大50000レコード程度を想定しており、全レコードを
取得するだけの処理で、単純計算で50000件で4分程度掛かることになり、
ユーザー操作に耐えられません。

▼質問1
レコード数が増えた場合でも、応答時間を短くする方法はございませんでしょうか?
このような大量のレコードを取得する際などの応答時間に関する要望は当然ながら
他社様でも同様にあるかと思いますが、他社様での対処された事例などは
ございませんでしょうか?

▼質問2
データ取得の応答時間は、取得するデータの「データ量」が同じでも
「データ件数」が少なければによるものでしょうか?
もし、データ件数によるものであれば、1レコードに全てのレコードを
まとめて登録させ、1件のレコードを取得するように対処すれば応答時間が
早くなるのでは無いかと考えてます。

▼質問3
例えば、データベースのように1つのアプリに複数のテーブルや、ビューを
持つことは可能でしょうか?
1つアプリの全レコードを取得することで応答時間が掛かるので、情報を
別のテーブルに分けておけば、1度に取得するレコードのデータ量を減らすことが
できるので、応答時間を改善することができると考えてます。
別のテーブル情報を保持したい場合は、別のアプリを作成するしか
方法は無いでしょうか?

田口さん

RDBと違って、数万件の処理は苦労しますね。

▼質問1

・kintone 的対策

処理内容は、たぶん何らかの集計だと思われます。一度に数万件の処理は、kintonに向いていません。
対策としては処理内容によりますが、集計処理をトランザクションレコードの追加・更新・削除処理内で行います。
あらかじめ集計用アプリに集計データを作成すると、集計キーが月ならば、集計レコードは数十分の1になります。

・APIのパラレル処理

APIをパラレル処理すること、および必要な項目のみに絞ることで、処理時間の短縮が可能だと思われます。

レコード取得APIのパラレル処理
レコード取得APIの fields 指定の効果
取得条件にルックアップコピー項目や関連レコードの項目を指定して、必要なレコードを絞り込む工夫も有用だと思います。

・外部にRDBでレコードの複製を作成
パフォーマンス優先で考えると、RDBを使うのが手っ取り早いと思われます。
kintoneアプリのレコードの追加・更新・削除イベント処理で、外部RDBも同時に更新します。

▼質問2

処理時間については、データ件数とデータサイズ以外にいろいろ要因がありますので、試してみなければ分かりません。
レコード取得APIのパラレル処理で、ログに処理時間を記録していますので、同様に試してみてください。

ただ、構造的に無茶なつくりになりますので、やめた方がいいのではないでしょうか。
kintone のメリットがまったく無くなります。

▼質問3

取得時のデータ量を減らすのは、前述のように fields 指定で可能です。
kintone アプリは、RDBの正規化されていないテーブルのようなものだと考えられます。
あまり細かく正規化すると、やはりkintoneアプリの使いやすさが損なわれます。
しくみとしては、ルックアップや関連レコード機能で、アプリ間のリレーションを持っています。

どれも利便性とパフォーマンス、開発コストといろいろ課題があります。
何を優先するかで、対応方法をご検討されてはいかがでしょうか?

 

rex0220 様

非常に分かりやすいご回答、情報提供を頂けて大変助かります!

ご案内頂きました以下の対策方法を早速検証してみます。

・ 集計用の別アプリを作成

・ レコードAPIのパラレル処理

・ field指定

確認結果につきましては、後日こちらに投稿させて頂きます。

rex0220 様

ご報告が遅くなりまして申し訳ございません。

 

お陰様でご案内頂いた内容を元に、全体的に仕様を見直し、fields指定やqueryで取得条件を絞り出来る限り不要なデータは取得しないようにしました。

また、事前に集計をしておけるものは別アプリに集計をしておくなどして、オペレータの操作時に余計な処理が実行されないように対応をさせて頂きました。

具体的な対策前後の処理時間の差は記載しておりませんが、いずれもオペレータは1~2秒の待ち時間となり、ストレスを感じ無いようになりました。

ご助言ありがとうございました!

 

ご指摘頂きましたとおり、サイボウズのサポートご担当者様からも同様にサーバーに負荷の掛かるようなパラレル処理は遠慮して欲しいと言われておりましたので、

今回は下記の対策を実施することとさせて頂きました。

 

(1) fields指定

▼目的: 取得するデータの項目(field)数をできるかぎり減らして、処理時間を短くする。

▼対策: すべてのGETコマンドのオプションにfields:指定をして、取得する項目を限定することで取得する。

▼結果: 取得するデータ量が減り、処理に掛かる時間が短縮されました。

//■ 修正前 ■-----------------------------------------
kintoneUtility.rest.getAllRecordsByQuery({
    app:kintone.app.getId(),

    totalCount:true,

    isGuest:false

}).then(function (response) {

    varrecords=response.records;

}).catch(function (error) {

    console.log(error.message);

});

//■ 修正後 ■-----------------------------------------

kintoneUtility.rest.getAllRecordsByQuery({

    app:kintone.app.getId(),

     // ↓ 取得するフィールドコード指定:指定しない場合はアクセスできるすべてのフィールドが返却される

    fields: [‘商品名’, ‘料金’, ‘種別’],

    totalCount:true,

    isGuest:false

}).then(function (response) {

    varrecords=response.records;

}).catch(function (error) {

    console.log(error.message);

});

 

(2) 一覧に表示されているレコードのみを処理

▼目的: 取得するレコード数をできるかぎり減らして、処理時間を短くする。

▼対策: 表示に関するGETコマンド処理を実行する箇所は、kintone.app.getQuery()や、queryでlimit指定をして、一覧に表示されるレコードのみを限定して取得する。

▼結果: 取得するレコード数が一覧に表示されている20件~最大で100件までに減ったため、処理に掛かる時間が短縮されました。

//■ 修正前 ■-----------------------------------------

kintoneUtility.rest.getRecords({

    app:kintone.app.getId(),

    totalCount:true,

    field: [‘商品名’, ‘料金’, ‘種別’],

    isGuest:false

}).then(function (response) {

    records=response.records;

}).catch(function (error) {

    console.log(error.message);

});

//■ 修正後 ■-----------------------------------------

kintoneUtility.rest.getRecords({

    app:kintone.app.getId(),

    // ↓ kintone.app.getQuery() 一覧のクエリ文字列を取得(order by, limit, offset付き)

    query:kintone.app.getQuery(),

    totalCount:true,

    field: [‘商品名’, ‘料金’, ‘種別’],

    isGuest:false

}).then(function (response) {

    records=response.records;

}).catch(function (error) {

    console.log(error.message);

});

 

(3) 集計用の別アプリを作成

▼目的:  集計結果を別アプリから取得するだけの処理にして、処理時間を短くする。

▼対策: 特定の操作の都度、集計をするのでは無く集計用の別アプリに集計結果を記録する。

▼結果: オペレータの操作時は結果のみ取得するだけとなり、処理時間を短縮できました。

(4) 各レコードの状態フラグ管理用のチェックボックス項目を追加し、定期的にフラグを更新する処理を追加

▼目的:  バックグラウンドで定期的にレコードの状態だけ確認・更新する処理を実行させて、オペレータに処理待ち待ちさせないようにする。

▼対策: 全レコードの状態を確認する必要のある機能は、各レコードごとの状態フラグ管理用のチェックボックス項目を用意して、

            外部サーバーからかもしくはアプリ内のタイマー処理で定期的にレコードの状態フラグを更新するだけの処理を追加。

▼結果: オペレータによる特定の操作をする際に全レコードの状態を確認する処理をいちいち実行する必要がなくなりました。

//-------------------------------------------------------------------------------------------------------

// タイマー処理:1時間に1回ステータス確認と状態フラグの更新

//-------------------------------------------------------------------------------------------------------

functionRecordStatusUpdate() {

vartimer=setInterval(function () {

//レコード全件取得

kintoneUtility.rest.getAllRecordsByQuery({

app:kintone.app.getId(),

fields: ['商品名', '料金', '種別'],

totalCount:true,

isGuest:false

}).then(function (response) {

varrecords=response.records;

 

        //レコードの状態を確認して、フラグを立てるデータを作成

        //var data = {

        //~省略~

        varparam= {

                app:kintone.app.getId(),

                records:data,

                isGuest:false

        };

//レコードのフラグを立てる

        kintoneUtility.rest.putAllRecords(param).then(function (resp) {

        }).catch(function (error) {

                console.log(error.message);

        });

}).catch(function (error) {

 console.log(error.message);

});

}, 3600000); /* 1時間に1回 */

}