Paging Strategies: OFFSET/LIMITとKeyset(カーソルベース)メソッドの比較
Lukas Schneider
DevOps Engineer · Leapcell

はじめに
Webアプリケーションや大規模データセットの分野では、効率的なデータ取得が最優先事項です。数百万件におよぶ可能性のあるレコードを扱う場合、一度にすべてを取得することは、ほとんど実行可能でもパフォーマンスが良いわけでもありません。ここでページネーションが登場し、データを管理しやすいチャンクで表示できるようになります。2つの主要な戦略がこの分野を支配しています。馴染みのあるOFFSET/LIMIT
(またはSKIP/TAKE
)と、それほど一般的ではありませんがしばしばより強力なKeyset Paging(カーソルベース・ページネーションとも呼ばれます)です。OFFSET/LIMIT
は通常、そのシンプルさから最初の選択肢となりますが、データセットが成長し、ユーザーが結果を深くナビゲートするにつれて、重大なパフォーマンスのボトルネックを引き起こす可能性があります。この固有の制限は、応答性とスケーラビリティを向上させるための堅牢な代替手段を提供するKeyset Pagingを詳しく調べることを必要とします。これらの2つのアプローチ間のトレードオフを理解することは、大量のデータを効果的に処理する高パフォーマンスでユーザーフレンドリーなアプリケーションを構築することを目指す開発者にとって不可欠です。この記事では、両方のメソッドを分析し、それらの基盤となるメカニズム、パフォーマンス特性、および理想的なアプリケーションシナリオを比較して、情報に基づいた意思決定を支援します。
本文
比較に入る前に、基本的な用語について共通の理解を確立しましょう。
- ページネーション (Pagination): 大規模なデータセットを小さく、個別のページに分割するプロセスであり、ユーザーがデータを管理しやすいチャンクで表示または処理できるようにします。
- ページサイズ (Page Size): 1ページあたりに表示または取得されるレコードの数。
OFFSET/LIMIT
(またはSKIP/TAKE
): 結果セットから特定の行のサブセットを取得するために使用されるSQL句の組み合わせ。LIMIT
は返す行の最大数を示し、OFFSET
は行を返す前に結果セットの最初からスキップする行数を示します。- Keyset Paging(カーソルベース・ページネーション): 前のページの最後のレコードの特定の列(キー)の値を使用して、次のページの開始点を決定するページネーション技術です。これは、前のレコードのスキャンを回避して、次のページが開始されるべき場所を効果的に「指します」。
- カーソル (Cursor): Keyset pagingのコンテキストでは、カーソルは、後続のレコードの取得をガイドする、最後に表示されたレコードをマークする識別情報(通常は順序付けられた列の値)を指します。
OFFSET/LIMIT ページネーション
原則:
OFFSET/LIMIT
ページネーションは、まず結果セット全体をソートし、次に指定された行数(OFFSET
)をスキップしてから、次の行セット(LIMIT
)を返すことで機能します。これは非常に直感的であり、ページ番号に直接マッピングされます。たとえば、ページあたり10アイテムの3ページ目を取得するには、OFFSET 20 LIMIT 10
を使用します。
実装例(SQL):
id
、name
、price
という列を持つproducts
テーブルがあると仮定します。name
で並べ替えた製品を取得したいとします。
ページ1(最初の10件の製品):
SELECT id, name, price FROM products ORDER BY name LIMIT 10 OFFSET 0;
ページ2(次の10件の製品):
SELECT id, name, price FROM products ORDER BY name LIMIT 10 OFFSET 10;
ページN(10件の製品のNページ目):
SELECT id, name, price FROM products ORDER BY name LIMIT 10 OFFSET ((N - 1) * 10);
長所:
- シンプルさ: 理解しやすく、実装も容易です。
- 直接的なページ番号アクセス: ユーザーが任意のページ番号に直接ジャンプすることを可能にします。
短所:
- パフォーマンスの低下:
OFFSET
値が増加するにつれて、データベースは開始点を特定するために、OFFSET + LIMIT
までのすべての先行行をスキャンしてソートする必要があります。これは、データベースが各後続ページでより多くの無駄な作業を実行するため、大きなオフセットと大規模なデータセットでますます非効率的になります。 - 一貫性のない結果: ページリクエスト間にレコードが追加または削除された場合、レコードが複数のページに表示されたり、完全にスキップされたりする可能性があり、一貫性のないユーザーエクスペリエンスにつながります(例:重複の表示やアイテムの欠落)。新しいレコードが以前に取得されたページ内に収まるように追加された場合、後続のページには予期しないデータが含まれます。
適用可能なシナリオ:
- 小規模から中規模のデータセット: 総レコード数が比較的少ない(数千から数万)場合、および深いページネーションがまれな場合。
- 特定のページジャンプ: ユーザーが頻繁に任意のページ番号(例:「50ページへ移動」)に直接移動する必要がある場合。
- データの一貫性がそれほど重要でない場合: ページロード間にデータ変更によるわずかな不整合が許容されるシナリオ。
Keyset Paging(カーソルベース・ページネーション)
原則:
Keysetページネーションは、前のページの最後のレコードの値を使用して「カーソル」とし、次のレコードセットを取得することでOFFSET
の問題を回避します。レコードをスキップする代わりに、ソートされた順序で次のページが開始されるべき場所を直接特定します。これには、カーソルを定義するために順序付けられ、理想的には一意の列セットが必要です。
実装例(SQL):
'products'テーブルを'name'で順序付けることを続けます。カーソルを一意で安定させるには、タイブレーカーとして一意の識別子(id
など)を含めるのが最善であることがよくあります。
最初の要求(ページ1):
SELECT id, name, price FROM products ORDER BY name ASC, id ASC LIMIT 10;
ページ1で返された最後のレコードがname = 'Laptop'
とid = 105
だったと仮定します。
次のページ要求(ページ2):
次の10件の製品を取得するには、name
が'Laptop'より大きい製品、またはname
が'Laptop'の場合はid
が105より大きい製品を照会します。
SELECT id, name, price FROM products WHERE (name > 'Laptop') OR (name = 'Laptop' AND id > 105) ORDER BY name ASC, id ASC LIMIT 10;
長所:
- 一貫したパフォーマンス: クエリは常に特定のポイントから開始して
LIMIT
行を取得します。スキップされた行のスキャンを回避するため、パフォーマンスはページ番号に依存しません。これは、大規模なデータセットや深いページネーションに特に有益です。 - 安定した結果: 前のページに挿入された新しいレコードや削除は、レコードが複数のページに表示されたりスキップされたりする原因とはなりません。カーソルは常に一貫した論理的な位置を指します。
- インデックスの活用: このメソッドは、順序付けられた列のインデックスを効果的に利用し、データベースがカーソルの位置に直接ジャンプできるため、クエリが非常に高速になります。
短所:
- 直接的なページ番号アクセスなし: ユーザーは、そのページのカーソルを知らずに、通常、任意のページ番号(例:「50ページへ移動」)に直接ジャンプできません。主に「次のページ」/「前のページ」ナビゲーション用に設計されています。
- 複雑さ: カーソル(最後のレコードの値)をリクエスト間で管理する必要があるため、実装はわずかに複雑になります。順序付けられた複数の列とタイブレーカーがある場合、
WHERE
句は複雑になる可能性があります。 - 安定した順序と一意のタイブレーカーが必要: 安定した曖昧さのないカーソルを保証するために、少なくとも1つのユニークな列(またはユニークな列の組み合わせ)に依存します。
適用可能なシナリオ:
- 大規模データセット:
OFFSET/LIMIT
が実行不可能になるほど遅くなる数百万件のレコードを扱う場合。 - 無限スクロール / 「次へ/前へ」ナビゲーション: ユーザーが主に製品リスト、アクティビティログ、ソーシャルメディアフィードなどの結果を前後に移動するインターフェイスに最適です。
- 高同時実行性 / 頻繁なデータ変更: 動的な環境でのより一貫した結果を保証します。
- **APIページネーション:**REST APIがサービス間で結果をページネーションするための一般的なパターンです。
結論
OFFSET/LIMIT
とKeyset Pagingのどちらを選択するかは、特定のアプリケーションのニーズ、データセットのサイズ、およびユーザーのインタラクションパターンを理解することにかかってきます。OFFSET/LIMIT
は即時のシンプルさと直接的なページ番号アクセスを提供しますが、大規模なデータセットや深いページネーションではパフォーマンスが大幅に低下し、動的な環境で一貫性のない結果を生みやすくなります。一方、Keyset Pagingは、インデックス付き列を活用し、高価なOFFSET
操作を回避することで、大規模データセットと「次へ/前へ」ナビゲーションに対して優れたパフォーマンスと安定性を提供します。最終的に、大量のデータを扱う高いスケーラビリティと一貫したパフォーマンスを要求するアプリケーションにとって、Keyset Pagingは、実装の複雑さがわずかに増すとしても、疑いなく優れた選択肢です。
したがって、膨大なデータを扱うほとんどの最新アプリケーションでは、パフォーマンスと安定性のためにKeyset Pagingを優先し、OFFSET/LIMIT
は小規模でパフォーマンスがそれほどクリティカルでないデータセットのために予約してください。