抜け・重複のないカーソルページネーション:設計の考え方
一言でいうと
ページネーションの契約は、「何件見せるか」ではなく、どの順序のどこまで読んだかです。
なぜ必要なのか
一覧の1ページ目を見たあと、新しい項目が先頭に入ってきたとします。offset=10で次のページをリクエストすると、前から押し出された項目をもう一度見てしまうことがあります。逆に、削除が挟まると、飛ばしてしまうことがあります。一意で変わらないidでソートし、最後のidより大きい行を読めば、この移動の問題を減らせます。ただし、カーソルはデータ全体のスナップショットではありません。すでに通り過ぎた区間にあとから挿入された行までは保証せず、価格順のようにソートの値が変わる一覧には、複合キーと別の契約が必要です。
どう動くのか
전체 행 → 범주 필터 → id 오름차순 → id > cursor → limit + 남은 행 확인
↓
공개 필드 투영 + next_cursor
limit=2で結果が2件だったという事実だけでは、次のページがあるとはいえません。このラボでは、残りの行がlimitより多いときだけ、最後に返したidをカーソルにします。実際のサービスのSQLでは、WHERE id > ? ORDER BY id LIMIT (limit+1)で1行多く読んで、同じ判断をします。ここでは、小さなメモリ上の一覧で、まず契約を検証します。
現場での姿
カーソルをbase64に変換しても、暗号化やアクセス制御にはなりません。デコードしたあとに正の整数かどうかを検査し、実際のサービスでは、フィルターやソートの条件にカーソルを結び付けるかどうかを決める必要があります。このラボのカーソルはidだけを含むので、次のリクエストも同じcategoryを送る必要があります。内部コストのようなフィールドを元の辞書のまま返すと、ページネーションが合っていても、データが漏れます。公開フィールドだけを新しい辞書に入れる射影を、最後に置きます。
次のラボですること
不規則なidの順序、空の一覧、ちょうど最後のページ、不正なlimitとカーソルを確認します。最後のステップでは、TestClientで2ページを続けて読み、重複・欠落・内部フィールドの露出を検査します。次のリクエストのcategoryが変わったり、ソートのキーが変更されたりすると何が変わるかを説明できてはじめて、このラボが終わりです。
参考: FastAPIのテスト