「取込スクリプトのテストは1回目は通ったのに、2回目に同じファイルを流したら同じデータが増えてしまった」「重複チェックのロジックは書いたはずなのに、なぜか新規として登録されてしまう」「開発環境では問題なかったのに、本番でレコード数が増えてから急におかしくなった」——kintoneのREST APIで既存レコードを取得して重複を判定する処理を書いたことがあるなら、一度は近い症状に当たったことがあるかもしれません。
kintoneのレコード一覧取得APIは、件数が少ないうちは何も考えずに書いても正しく動きます。しかし1回の呼び出しで返せる件数には上限があり、対象が501件を超えるアプリでは、書き方次第で「取得したつもりの既存レコード」が実は一部しか取れていない状態になります。この記事では、なぜ初回は正常に見えるのか、何件からこの問題が起きるのか、そして全件を確実に取得する実装への直し方を解説します。
- kintoneのレコード一覧取得API(
GET /k/v1/records.json)は1回の呼び出しで最大500件までしか返らない -
limitを指定しないコードでも、対象が500件未満なら「全件取れている」ように見えてしまう - 501件を超えた状態で同じ取込処理を再実行すると、取得漏れ分が「未登録」と誤判定されて二重登録が起きる
- 全件取得は
@kintone/rest-api-client の getAllRecords、またはカーソルAPIを使う - 冪等性のテストは1回目でなく「同じファイルをもう一度流して新規0件になるか」で確認する
目次を開く
なぜ「初回は正常」に見えてしまうのか
kintoneのレコード一覧取得API(GET /k/v1/records.json)は、limitパラメータの既定値・最大値がどちらも500件です。offsetで取得位置をずらして繰り返し呼び出す実装でも、offsetには実用上10,000件という上限があります。
開発環境やテスト時に対象アプリのレコード数が500件未満だと、limitを明示的に指定していないコードでも「全件取得できた」ように動いてしまい、この制約は顕在化しません。ところが本番投入でレコードが500件を超えた状態になると、501件目以降が既存レコードの集合に含まれなくなり、次に同じ取込スクリプトを実行したときに「本当は存在するのに未登録」と誤判定されて二重登録が発生します。エラーは一切出ません。処理は正常終了したように見えるまま、データだけが壊れます。
kintone REST APIのレコード取得に関する上限
| 項目 | 値 |
|---|---|
1回のリクエストで取得できる最大件数(limit) | 500件 |
offset の実用上の上限 | 10,000(超えるとエラー) |
| カーソルAPIの1回あたりの取得件数上限 | 500件(呼び出し自体は繰り返し可能) |
何件から問題が起きるか確認する
対象アプリの総件数は totalCount=true を付けて取得できます。500件を超えていれば、この記事で説明する取得漏れの対象です。
curl -s -H "X-Cybozu-API-Token: $KINTONE_API_TOKEN" \
"https://$KINTONE_DOMAIN/k/v1/records.json?app=$APP_ID&totalCount=true" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['totalCount'])" 全件を確実に取得する2つの実装
1. @kintone/rest-api-client の getAllRecords を使う(Node.js/TypeScript)
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
const client = new KintoneRestAPIClient({
baseUrl: `https://${process.env.KINTONE_DOMAIN}`,
auth: { apiToken: process.env.KINTONE_API_TOKEN },
});
const records = await client.record.getAllRecords({ app: Number(process.env.APP_ID) });
console.log(records.length); getAllRecordsは内部でoffsetを進めながら複数回リクエストを重ね、呼び出す側が件数上限を意識せずに全件を受け取れます。
2. カーソルAPIを直接使う(SDKが無い環境向け)
-
POST /k/v1/records/cursor.jsonでカーソルを作成する(sizeは最大500) -
GET /k/v1/records/cursor.json?id=<cursorId>を、レスポンスのnextがfalseになるまで繰り返す - 終わったら
DELETE /k/v1/records/cursor.jsonで明示的に破棄する(作成から一定時間で自動失効するが、明示破棄が確実)
import requests
headers = {"X-Cybozu-API-Token": api_token}
base = f"https://{domain}/k/v1/records/cursor.json"
r = requests.post(base, headers=headers, json={"app": app_id, "size": 500})
cursor_id = r.json()["id"]
records = []
while True:
r = requests.get(base, headers=headers, params={"id": cursor_id})
body = r.json()
records.extend(body["records"])
if not body["next"]:
break
requests.delete(base, headers=headers, json={"id": cursor_id})
print(len(records)) 冪等性は「2回目の実行」でテストする
この不具合は1回目のdry-runでは原理的に検出できません。テストの受け入れ条件を「同じファイルをもう一度流したら新規0件・更新N件になること」に置きます。
- 1回目: 新規N件、更新0件
- 2回目(同じファイルを再投入): 新規0件、更新N件 ← ここが崩れたら取得漏れを疑う
まとめ
- kintoneのレコード一覧取得は1回500件・
offset上限10,000件という制約がある -
limitを指定しないコードは「全件取れた」ように見えて、実は先頭500件しか見ていないことがある - レコード数が少ない開発環境では顕在化せず、本番でレコードが増えてから初めて壊れる
- 全件取得は
getAllRecordsかカーソルAPIを使う - 取込処理の受け入れテストは「同じファイルの2回目実行」で検証する
よくある質問(FAQ)
Q. offsetを大きくして全部回せば良いのでは?
offsetには10,000件という上限があり、それを超えるとエラーになります。件数が将来10,000を超える可能性があるアプリでは、offsetをずらす方式ではなくカーソルAPIを使う設計にしておく方が安全です。
Q. limitを500より大きく指定すればまとめて取れませんか?
limitの最大値が500件なので、それより大きい値を指定しても1回のリクエストで取れる件数は増えません。
Q. getAllRecordsを使えば必ず安全ですか?
内部でページングを肩代わりしてくれるので手書きのoffsetループより安全ですが、対象アプリのレコード数が非常に多い場合は実行時間が伸びます。件数が多いアプリでは、定期実行のタイムアウト設定も合わせて見直してください。
Q. 重複判定のキーが複数フィールドの組み合わせでも同じ対策で良いですか?
同じです。既存レコードの集合に取得漏れがあれば、キーが単一項目でも複数項目の組み合わせでも同じく誤判定するので、まず全件取得側を直すのが先です。
