更新しながらのページング走査は offset でなく keyset/searchAfter を使い、ソートキーは不変キーにする
バックエンド
設計判断
検索
非同期処理
知識
判断
結果集合を1ページずつ取得しながら同じレコード群を更新していく一括処理(検索条件で全件選択して更新する等)では、ページング方式の選択が取りこぼし・二重処理を左右する。一覧の純粋な読み取りでは表面化しないが、「読みながら同じ集合を変える」走査では致命的になる。
なぜ offset / ページ番号方式は破綻するか
- 件数スキップ方式は「現在の結果集合の N 件目以降」を毎ページ取り直す。更新によって対象が検索条件から外れて結果集合が縮むと、後続レコードの位置が前方へ詰まり、スキップ境界がずれて未処理レコードを飛ばす(逆に対象が増える方向なら二重処理)。
- つまり走査と変更が同じ集合に対して同時に起きると、絶対位置ベースのページングは前提が崩れる。
keyset / カーソル方式が安全な理由と成立条件
- 直前ページ最終要素のソート値をカーソルにして「そのソート値より後」を取る方式(searchAfter / keyset)は、更新で対象が結果集合から外れても残りレコードのカーソル相対位置が動かないため取りこぼさない。更新済みで条件から外れたレコードはカーソル以前に位置するので再出現もしない。
- 成立条件は「ソートキーが安定(一意で順序が決定的)かつ、その走査で更新する対象外のフィールドであること」。ソートキー自体や検索条件に使うフィールドを更新値に含めると保証が壊れ、更新でレコードがカーソルを飛び越えてスキップ/重複が起きうる。
- 一意性を担保するため、ソートは業務キーにレコード ID 等の tie-breaker を足した複合キーにする。
スナップショット方式との判断
- 「作成時点で対象 ID を全件展開してスナップショット化し、固定リストを chunk 処理する」方式は走査中の変動を完全に排除できるが、ID 保存コスト・作成時の展開負荷・スナップショットの陳腐化を抱える。
- 「処理時にライブ再検索+keyset」方式はスナップショット不要だが、上記のソートキー不変条件と、検索エンジンの refresh 遅延(更新直後は反映前の値が見えうる)を前提に設計する。保存コスト/対象変動の許容度/件数規模で選ぶ。
検証
ソート境界をまたぐ件数(chunk サイズの数倍)で走査しながら、全件が漏れなく1回ずつ更新されること、さらにソートキーやフィルタ対象フィールドを更新値に含めた場合に取りこぼし/重複が再現することを実データ・実検索エンジンで確認する。固定ページを返す mock ではこの不変条件を検出できない。