Kintoneルックアップが遅い時 — 50,000件スケールでの高速化
Kintoneアプリのレコード数が数千件を超えた頃から、ルックアップが「重い」と感じ始めます。1万件を超えると明確に遅く、5万件になるとユーザーがストレスを口にし始めます。この記事では、大規模Kintone環境でルックアップを高速に動かすための具体的な手法を、実際の検証データとともに整理します。
この記事の要点
Kintone標準のルックアップは、1万件を超えると明確に遅くなります。原因は、レコード取得API(/k/v1/records.json)のオフセット制限(10,000件)とUI側のレンダリングコスト。解決策は3つあります:絞り込みを最初に効かせる、プログレッシブローディング、専用プラグインの導入。50,000件規模で検証済みの実装例を解説します。
標準機能が遅くなる理由
Kintoneのルックアップフィールドは、参照先アプリから候補レコードを取得して、ユーザーに選ばせる仕組みです。標準機能の動作フローは:
- ユーザーがルックアップのアイコンをクリック
- Kintoneが参照先アプリのレコードをAPI経由で取得
- 取得したレコード一覧をモーダルで表示
- ユーザーがレコードを選択して、値が取り込まれる
問題は、ステップ2と3です。
API側のボトルネック:offset制限
Kintone REST APIの/k/v1/records.jsonには、offsetの上限が10,000件という制限があります。これは、レコード数が10,000を超えるアプリでは、標準APIでは後半のレコードを取得する方法が実質ない、ということです。
2万件・5万件のアプリをルックアップ先にした場合、「このレコードがあるはずなのに、検索しても出てこない」という現象が発生します。これはプラグインやUIのバグではなく、API仕様の制約です。
UI側のボトルネック:全件ロード
Kintone標準のルックアップモーダルは、候補レコードを一括でロードしようとします。レコード数が多い場合、ブラウザのレンダリングに時間がかかり、「クリックしてから表示されるまで数秒」という遅延が発生します。
実測値(一般的なPCブラウザ・社内ネットワーク):
| レコード数 | ルックアップ表示までの時間 | 体感 |
|---|---|---|
| 1,000件 | 0.3秒 | 即座 |
| 5,000件 | 1.2秒 | 若干待つ |
| 10,000件 | 3~5秒 | 明らかに遅い |
| 30,000件 | 10秒~ | 実用困難 |
| 50,000件 | 検索不能(offset制限) | 標準機能では運用不可 |
解決策1:絞り込みを最初に効かせる
最もシンプルな対策は、ルックアップの呼び出し時に、絞り込み条件を予め適用することです。例えば「顧客マスター」アプリが50,000件あっても、多くの場合、発注伝票作成時に必要な顧客は、現在ログイン中のユーザーの担当顧客だけです。
Kintoneのルックアップ設定で「条件式」に担当営業 = LOGINUSER()を指定すると、取得対象が絞られ、速度が劇的に改善します。
ただし、この方法には限界があります:
- 絞り込み条件がビジネスロジック的に複雑な場合(例:顧客の最終購入日から30日以内、かつ担当ランクがA・B)、条件式では表現できない
- 動的な絞り込み(他のフィールドの値に応じて変更)は条件式では書けない
- 組織全体で共有する「全顧客マスター」のような使い方では、絞り込みが効かない
解決策2:プログレッシブローディング(JavaScript実装)
JavaScript側で、ユーザーの検索入力に合わせてレコードを段階的に取得する方法です。初期状態では0件、ユーザーが2文字以上入力したら検索API呼び出し、結果を100件ずつ返す、という仕組みです。
// 概念コード(実際の実装はもっと複雑)
searchInput.addEventListener('input', debounce(async (e) => {
const query = e.target.value;
if (query.length < 2) return;
const records = await kintone.api('/k/v1/records', 'GET', {
app: LOOKUP_APP_ID,
query: `顧客名 like "${query}" limit 100`
});
renderResults(records);
}, 300));
この実装のポイント:
- debounce処理:ユーザーの入力中に毎回APIを叩かない。入力停止後300ms待ってから検索実行。
- limit指定:1回の取得は100件に制限。それ以上は「もっと表示」ボタンで追加取得。
- query絞り込み:検索クエリを直接
queryパラメータに含めることで、サーバー側で絞り込む。
ただし、この方法もoffset制限の影響を受けます。クエリに一致するレコードが10,000件を超える場合、やはり10,001件目以降は取得できません。
解決策3:cursor API を使った大規模検索
Kintoneの/k/v1/records/cursor.jsonエンドポイントは、offset制限を回避して大量レコードを取得する方法です。
// 1. カーソル作成
const cursor = await kintone.api('/k/v1/records/cursor', 'POST', {
app: LOOKUP_APP_ID,
query: `顧客名 like "${query}"`,
size: 500 // 1回の取得件数
});
// 2. カーソルを使って繰り返し取得
let allRecords = [];
let next = true;
while (next) {
const result = await kintone.api('/k/v1/records/cursor', 'GET', {
id: cursor.id
});
allRecords = allRecords.concat(result.records);
next = result.next;
}
カーソルAPIは、offset制限を超えた大規模検索が可能です。ただし、カーソルの有効期限(10分)、同時オープン可能なカーソル数(1サブドメインあたり10個)、などの制約があります。
解決策4:専用プラグインの導入
上記3つの手法を自前で実装するのは、開発工数が大きく、保守も継続的に必要です。特に、全フィールド横断の検索、サブテーブル対応、モバイル対応、検索結果のプレビュー表示、などの要件が加わると、実装は簡単ではありません。
Smart Lookup等の専用プラグインは、これらを実装済みで、設定だけで利用できます。代表的な機能:
- 全フィールド横断の全文検索(標準の「ルックアップ取得フィールド」だけでなく、指定した全フィールドから検索)
- プログレッシブローディング(検索結果を段階的に表示、初回レンダリング100ms以内)
- サブテーブル内のルックアップにも対応
- モバイル(
/k/m/)でデスクトップと同等のUX - カーソルAPI使用で、レコード数の制限が実質なし
50,000件での実測検証
kinplugのSmart Lookupプラグインについて、50,000件のテストアプリで実際に検証したデータ:
| 操作 | 所要時間 |
|---|---|
| ルックアップモーダル初期表示 | 80~120ms |
| 検索クエリ入力→結果表示 | 250~400ms |
| 100件追加取得(スクロール時) | 150~250ms |
| サブテーブル10行のルックアップ | 100~150ms(各) |
| モバイル環境でも同等パフォーマンス | 概ね同等(ネットワーク依存) |
5万件規模では、標準機能は実用困難な反面、プラグイン側の実装次第では体感的な遅延なしに運用できます。
運用時の注意点
ルックアップ先アプリのレコード増加を想定する
導入時は1万件でも、数年運用すると5万件を超えるケースが多くあります。「今は遅くないから大丈夫」ではなく、将来の成長を想定したアーキテクチャを選ぶことが重要です。
インデックス設計は難しい
Kintoneは内部的にインデックスを持っていますが、カスタマイズは不可能です。クエリが遅い場合、「このフィールドにインデックスを張る」ことはできません。代わりに、アプリの分割やルックアップ先の見直しを検討します。
サブテーブルのルックアップは特に重い
サブテーブルの各行にルックアップフィールドがある場合、行数 × 候補レコード数のレンダリングコストが発生します。10行のサブテーブルで、それぞれ10,000件のルックアップ先を参照している場合、標準機能では実質運用困難です。専用プラグインでの解決がほぼ必須です。
まとめ
Kintoneのルックアップが遅い原因は、API側のoffset制限とUI側のレンダリングコストです。小規模データでは標準機能で十分ですが、10,000件を超えた時点で対策が必要になります。絞り込み・プログレッシブローディング・カーソルAPI・専用プラグインの、いずれか1つ以上を採用することで、50,000件規模でも快適に運用できます。