アルアカ - Arcadia Academia

Arcadia Academiaは「エンジニアリングを楽しむ」を合言葉に日本のデジタル競争力を高めることをミッションとするテックコミュニティです。

kintone REST APIのレコード一覧取得で2回目だけ重複登録が起きる原因と対処

Featured image of the post

「取込スクリプトのテストは1回目は通ったのに、2回目に同じファイルを流したら同じデータが増えてしまった」「重複チェックのロジックは書いたはずなのに、なぜか新規として登録されてしまう」「開発環境では問題なかったのに、本番でレコード数が増えてから急におかしくなった」——kintoneのREST APIで既存レコードを取得して重複を判定する処理を書いたことがあるなら、一度は近い症状に当たったことがあるかもしれません。

kintoneのレコード一覧取得APIは、件数が少ないうちは何も考えずに書いても正しく動きます。しかし1回の呼び出しで返せる件数には上限があり、対象が501件を超えるアプリでは、書き方次第で「取得したつもりの既存レコード」が実は一部しか取れていない状態になります。この記事では、なぜ初回は正常に見えるのか、何件からこの問題が起きるのか、そして全件を確実に取得する実装への直し方を解説します。

💡
30秒で要点
- 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が無い環境向け)

  1. POST /k/v1/records/cursor.json でカーソルを作成する(sizeは最大500)
  2. GET /k/v1/records/cursor.json?id=<cursorId> を、レスポンスのnextがfalseになるまで繰り返す
  3. 終わったら 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. 重複判定のキーが複数フィールドの組み合わせでも同じ対策で良いですか?

同じです。既存レコードの集合に取得漏れがあれば、キーが単一項目でも複数項目の組み合わせでも同じく誤判定するので、まず全件取得側を直すのが先です。

業務効率化・DX推進でお悩みですか?

オンラインセッションで課題を可視化し、最適な解決策をご提案します。

  • DX推進を何から始めればいいかわからない
  • ツール導入を検討している
  • 社内でデジタル人材を育成したい
まずは無料で課題整理

相談は完全無料・オンラインで気軽に

あなたを爆速で成長させるメンタリングプログラムはこちら

メンタープログラムバナー

業務効率化・DX推進のご相談はこちら

伴走支援プログラムの詳細を見る

エンジニアの基礎学習ゲーム

プログラミング学習支援

無料相談はこちら