効率的なデータページネーション:キーセット vs. オフセット
Lukas Schneider
DevOps Engineer · Leapcell

はじめに
Webアプリケーションやデータ駆動型システムの世界では、大規模なデータセットを効率的に表示することが基本的な課題です。ソーシャルメディアのフィード、Eコマロデュクトカタログ、ログファイルビューアなどを想像してみてください。これらのシナリオでは、潜在的に数百万件のレコードを取得してユーザーに表示する必要があります。すべてのデータを一度に取得するのは非現実的でリソースを大量に消費するため、ページネーションが事実上の解決策として登場します。ページネーションにより、膨大なデータセットを管理可能なチャンクに分割し、パフォーマンスを向上させ、ロード時間を削減し、ユーザーエクスペリエンスを向上させることができます。この記事では、2つの普及しているページネーション戦略、すなわちオフセットページネーションと、ますます人気が高まっているキーセットページネーション(カーソルページネーションとしても知られる)について掘り下げ、それらの基本的な原則、実装の詳細、およびスケーラブルで応答性の高いアプリケーションを構築するための実践的な意味合いを分析します。
コアコンセプト
各ページネーションメソッドの詳細に入る前に、議論全体で関連するいくつかの重要な用語を定義しましょう。
- ページサイズ(Limit): 1ページあたりに取得するレコードの最大数。
- ページ番号: 現在のページの順序を示す整数(例:ページ1、ページ2、ページ3)。これは通常、オフセットページネーションに関連付けられます。
- オフセット: 目的のページを取得する前に、データセットの先頭からスキップするレコード数を示す整数。これもオフセットページネーションに関連付けられます。
- カーソル: 前のページで取得された最後のレコードの一意の識別子(通常は主キー、タイムスタンプ、または列の組み合わせ)。これはキーセットページネーションの中核です。
- 安定したソート順: データが取得される一貫性があり、予測可能な順序。これは両方のメソッドにとって重要ですが、特にキーセットページネーションにとっては、ページ間で正確で繰り返しのない結果を保証するために不可欠です。通常は
ORDER BY
句で定義されます。
オフセットページネーション
オフセットページネーションは、おそらく最も直感的で広く理解されているページネーション技術です。これは、特定の数のレコード(OFFSET
)をスキップし、次に固定数のレコード(LIMIT
)を取得することによって機能します。
原理
原理は簡単です。N番目のページを取得するには、最初の (N-1) * LIMIT
レコードをスキップし、次に次の LIMIT
レコードを取得します。
実装
id
、name
、price
、created_at
列を持つ products
テーブルを考えてみましょう。
SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10 OFFSET 0; -- 最初のページ(ページ1、ページサイズ=10) SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10 OFFSET 10; -- 2番目のページ(ページ2、ページサイズ=10) SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10 OFFSET 20; -- 3番目のページ(ページ3、ページサイズ=10)
APIコンテキストでは、リクエストは GET /products?page=2&limit=10
のようになるかもしれません。バックエンドはその後 OFFSET
を (page - 1) * limit
として計算します。
利点
- シンプルさ: 理解と実装が容易です。
- ランダムアクセス: ユーザーは任意のページ番号に直接ジャンプできます(例:ページ100に移動)。
- 総数表示: アイテムの総数が利用可能な場合、「ページX / Y」または番号付きページ付きのページネーションUIを表示するのは簡単です。
欠点
- 大きなオフセットでのパフォーマンス低下:
OFFSET
値が増加するにつれて、データベースはますます多くのレコードをスキャンして破棄する必要があり、クエリ時間が大幅に遅くなります。これは大規模データセットにおける主な欠点です。 - レコードのスキップ/重複: ユーザーがページネーション中に現在のオフセットの前にレコードが挿入または削除された場合、ユーザーは重複レコードを見たり、全体として一部のレコードを見逃したりする可能性があります。これは「ファントム問題」として知られており、一貫性のないユーザーエクスペリエンスにつながる可能性があります。
ユースケース
- 比較的小規模なデータセットを持つ管理ダッシュボード。
- データ変更による一時的な不整合が許容される状況。
- ランダムなページアクセスが重要な要件であり、レコードの総数がそれほど多くないアプリケーション。
キーセットページネーション(カーソルページネーション)
キーセットページネーション、またはカーソルページネーションとも呼ばれるものは、特に大規模データセットにおいて、オフセットページネーションが直面するパフォーマンスおよび一貫性の問題に対して、より堅牢なソリューションを提供します。
原理
固定数の行をスキップする代わりに、キーセットページネーションは、前のページの最後のレコードの値を使用して、「カーソル」として次のページの開始点を決定します。これは、固有の順序付けられた列のセット(「キーセット」)に依存して、次の開始点を定義します。
実装
id
が一意で自動インクリメントされる主キーであると仮定して、products
テーブルの例を続けます。
最初のページを取得するには:
SELECT id, name, price FROM products ORDER BY id ASC LIMIT 10; -- この結果セットの最後のレコードのIDが10であると仮定します。
2番目のページを取得するには(id = 10
の後):
SELECT id, name, price FROM products WHERE id > 10 -- "カーソル"は前のページの最後のIDです ORDER BY id ASC LIMIT 10;
価格、次にIDのタイでのソートなど、より複雑なソートが必要な場合:
SELECT id, name, price FROM products ORDER BY price ASC, id ASC LIMIT 10; -- このページの最後のレコードが {id: 7, price: 9.99} であると仮定します。
次のページを取得するには:
SELECT id, name, price FROM products WHERE (price > 9.99) OR (price = 9.99 AND id > 7) -- price AND id に基づくカーソル ORDER BY price ASC, id ASC LIMIT 10;
APIコンテキストでは、次のページのリクエストは GET /products?limit=10&last_id=10
や GET /products?limit=10&last_price=9.99&last_id=7
のようになるかもしれません。 last_id
または last_price
と last_id
の組み合わせがカーソルとして機能します。
利点
- 一貫したパフォーマンス: 「ページ」番号が増加してもパフォーマンスは低下しません。インデックス付き列(
id
またはキーセットの組み合わせなど)を持つWHERE
句により、データベースは開始点に迅速にジャンプできるため、パフォーマンスはページネーションの深さからほとんど独立しています。 - データ変更に対する堅牢性: 現在のページよりも前の挿入や削除は、現在のページまたは後続ページの整合性に影響しません。カーソルはソートされたデータセットの特定の位置を指すため、ユーザーは重複レコードを見たり、表示されるべきレコードを見逃したりすることはありません。
- スケーラビリティ: 非常に大規模なデータセットや高トラフィックのアプリケーションに非常に適しています。
欠点
- ランダムアクセスなし: ユーザーは任意のページ番号に直接ジャンプできません。彼らは「次」または「前」にのみ移動できます(
WHERE
句とソート順を逆にすることによって)。 - 総数なし: 別途、潜在的に高価な
COUNT(*)
クエリなしで「ページX / Y」を表示するのは困難です。 - 安定したソート順が必要: 一貫性があり一意のソート順(キーセット)が必須です。ソート順が一意でない場合は、キーセットにタイブレーキング列(主キーなど)を追加する必要があります。
- より複雑な実装: 特に複合キーセットや「前のページ」機能を処理する場合、実装が難しいことがあります。
ユースケース
- 無限にスクロールするソーシャルメディアフィード(例:Twitter、Facebook)。
- 新しいデータが常に追加されるログ探索ツール。
- パフォーマンスと一貫性が最優先されるEコマース製品リスト。
- ユーザーが主にシーケンシャルにナビゲートする、大量の頻繁に更新されるデータセットを扱うあらゆるアプリケーション。
結論
オフセットページネーションとキーセットページネーションはどちらも、大規模なデータセットを管理可能なチャンクに分割するという目的を果たしますが、それぞれ異なるシナリオで優れています。オフセットページネーションはシンプルさとページへの直接アクセスを提供し、小規模なデータセットや深いページでのパフォーマンスが重要ではない特定の管理インターフェイスに適しています。しかし、オフセットが増加するにつれてパフォーマンスが低下し、同時変更中のデータの一貫性に対する脆弱性があります。
一方、キーセットページネーションは、大規模で動的なデータセットや高いスケーラビリティとシームレスなユーザーエクスペリエンスを必要とするユーザー向けアプリケーションにとって、優れたパフォーマンスの一貫性とデータ変更に対する堅牢性を提供します。ランダムなページアクセスとシンプルさを犠牲にしますが、効率とデータ整合性における利点は、最新のデータ集約型環境ではこれらの欠点を上回ることがよくあります。最終的に、これら2つのメソッドの選択は、特定のプロジェクト要件、データセットサイズ、および想定されるユーザーインタラクションパターンに依存します。大幅なデータを扱うほとんどの最新Webアプリケーションでは、キーセットページネーションがはるかにパフォーマンスが高く信頼性の高いユーザーエクスペリエンスにつながります。