Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】API Pagination Patterns: Offset, Cursor, and Keyset Explained

2026年09月09日に「Dev.to」が公開したITニュース「API Pagination Patterns: Offset, Cursor, and Keyset Explained」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

APIから大量データを効率的に取得する技術「ページネーション」には、3つの主要パターンがある。Offsetは実装が簡単だが、大規模データで性能が低下し不安定。CursorやKeysetはトークンやソートキーで次データを指定し、大規模・動的なデータで高速かつ安定している。データ量や性質に応じて使い分けることが重要だ。

ITニュース解説

API(Application Programming Interface)を開発したり利用したりする際、リスト形式のデータを取り扱うことは非常に一般的である。例えば、ブログ記事の一覧やユーザーの活動履歴など、サーバーから大量のデータを取得したい場面は頻繁に訪れる。しかし、もしこれらのデータを一度にすべて送ってしまうと、以下のような問題が発生する。サーバーは大量のデータを準備するのに時間がかかり、メモリを大量に消費する。また、クライアント側(Webブラウザやモバイルアプリなど)も、一度に受け取った大量のデータを処理し表示するのに苦労し、動作が遅くなったり不安定になったりする可能性がある。このような問題を解決し、APIの応答速度を保ち、サーバーとクライアント双方の負担を減らすための技術が「ページネーション」である。ページネーションとは、大量のデータを小さな「ページ」に分割して、必要な分だけ段階的に取得する仕組みのことである。

ページネーションにはいくつかの一般的なパターンが存在し、それぞれ異なる特性を持つため、プロジェクトの要件に応じて最適なものを選ぶ必要がある。ここでは、主に三つの主要なパターン、「オフセットページネーション」、「カーソルページネーション」、そして「キーセットページネーション」について詳しく解説する。

まず、最もシンプルで理解しやすいのが「オフセットページネーション」である。このパターンでは、クライアントは「何ページ目のデータを、一度に何件取得するか」を指定する。例えば、「2ページ目のデータを20件取得する」といった指定方法である。APIリクエストでは、?page=2&limit=20 のようにページ番号と取得件数を指定したり、あるいは ?offset=40&limit=20 のように「最初のデータから何件分スキップして、その後に続くデータを何件取得するか」を直接指定したりする。サーバー側では、データベースに対して、指定された数の行をスキップし、その後指定された数の行を取得するSQLクエリを実行する。例えば、SELECT * FROM posts ORDER BY id LIMIT 20 OFFSET 40; というクエリは、投稿(posts)テーブルからID順に並べたデータのうち、最初の40件をスキップし、続く20件のデータを取得する。このパターンの大きな利点は、その分かりやすさと実装の容易さである。クライアントは特定のページ番号を指定して直接そのページにジャンプできるため、ユーザーインターフェースに「1ページ、2ページ、3ページ…」といったページ番号を表示するWebサイトなどには適している。また、データ量が比較的小規模であったり、データの追加や削除がほとんど発生しない静的なデータに対しては、十分に機能する。しかし、この方法にはいくつかの欠点がある。特に、データ量が増え、オフセット値が大きくなると、パフォーマンスが著しく低下する可能性がある。これは、データベースがスキップする行も含めて、指定されたオフセットまでのすべての行を読み込む必要があるためである。例えば、10000件のデータをスキップして20件を取得する場合、データベースは最初の10020件をスキャンしなければならない。さらに、APIリクエストを複数回行う間に、データが追加されたり削除されたりすると、「データが重複して表示される」あるいは「一部のデータがスキップされて表示されない」といった問題が発生する可能性がある。これは、オフセットが相対的な位置を指定するため、データの全体的な並び順が途中で変わってしまうと、結果が不安定になるためである。このため、オフセットページネーションは、管理パネルや社内ツールのように、データ量が限定的で頻繁に更新されない場面での利用が適している。

次に、「カーソルページネーション」を見てみよう。このパターンは、オフセットページネーションの欠点、特に大規模データでのパフォーマンス問題やデータの不安定性を解決するために考案された。カーソルページネーションでは、クライアントは特定の「ページ番号」ではなく、前のリクエストで取得した最後のデータが指す「カーソル」と呼ばれるトークンをサーバーに送る。例えば、?cursor=abc123&limit=20 のようにリクエストする。サーバーは、このカーソルが指す場所の「後」にあるデータを指定された件数分返す。そして、次のページを取得するための新しいカーソルもレスポンスに含めて返す。このカーソルは、多くの場合、前のリクエストで返された最後のデータのソートキー(例えば、データベースのIDやタイムスタンプ)をエンコード(暗号化とは異なり、別の形式に変換すること)したものである。サーバー側では、このカーソルを使ってデータベースクエリを構築する。例えば、SELECT * FROM posts WHERE id > :cursor_id ORDER BY id LIMIT 20; のように、カーソルに含まれるIDよりも大きいIDを持つデータを取得する。この方法の最大の利点は、その安定性と高速性にある。カーソルが特定のデータの「位置」ではなく「基準」を直接指定するため、途中で新しいデータが追加されても、すでに取得したデータが重複したり、次のデータが欠落したりすることなく、一貫した結果が得られる。また、データベースはインデックス(検索を高速化するための索引)を利用した範囲クエリを実行するため、オフセットのようにスキップするデータをすべてスキャンする必要がなく、大規模なデータセットでも高いパフォーマンスを維持できる。しかし、カーソルページネーションには、クライアントが特定のページ番号に直接ジャンプできないという制約がある。ユーザーは「次へ」「前へ」のように順番にデータを辿ることはできるが、「5ページ目に飛ぶ」といった操作はできない。また、オフセットページネーションに比べて実装がわずかに複雑になる場合がある。このパターンは、リアルタイムで更新されるニュースフィードやアクティビティログのように、頻繁にデータが追加され、常に最新のデータを表示し続ける必要がある高ボリュームのアプリケーションに非常に適している。

三つ目のパターンとして、「キーセットページネーション」がある。これはカーソルページネーションの一種だが、カーソルの表現方法に違いがある。キーセットページネーションでは、カーソルは前のリクエストで取得した最後のデータのソートカラム(並べ替えの基準となる列)の実際の値として、より「透明に」扱われる。例えば、?after_id=100&limit=20 のように、最後のデータのIDが100であったことをクライアントに明示的に伝え、サーバーはIDが100より大きいデータを要求される。サーバー側では、SELECT * FROM posts WHERE id > 100 ORDER BY id LIMIT 20; のような直接的なSQLクエリが実行される。このパターンの利点は、カーソルが具体的な値であるため、理解しやすくデバッグも容易であることだ。また、ソートに使用するカラムにデータベースのインデックスが適切に設定されていれば、カーソルページネーションと同様に高いパフォーマンスを発揮する。しかし、キーセットページネーションには、データを並べ替えるための「一意で、かつ並べ替え可能なキー」が必要となる。多くの場合、これはデータベースの主キー(Primary Key)が使用される。もし、並べ替えの基準が例えばcreated_at(作成日時)のように一意ではないカラムである場合、同じ作成日時を持つ複数のデータが存在する可能性があるため、それらのデータが欠落しないように、IDなどの別のカラムを「タイブレーカー」として組み合わせて使用する必要がある。多くのAPIでは、内部的にキーセットの考え方を使ってカーソルを生成していることがあり、両者の違いは主に、クライアントにカーソルをどのように見せるかという点にある。

では、これらのページネーションパターンの中から、どのように最適な選択をすればよいのだろうか。一般的な指針としては、以下のように考えられる。もし扱うデータが小規模で、ほとんど更新されない静的なデータであれば、実装が簡単なオフセットページネーションで十分である。必要以上に複雑な仕組みを導入する必要はない。しかし、データ量が非常に大きく、頻繁に更新される動的なデータを扱う場合には、カーソルページネーションかキーセットページネーションを選択すべきである。これにより、データベースへの負荷を大幅に軽減し、APIの応答性を高めることができる。もしクライアントが特定のページに直接ジャンプする必要がある場合は、オフセットページネーションが唯一の選択肢となるが、その場合でも、パフォーマンスが極端に低下するのを避けるために、ジャンプできる最大ページ数やオフセット値に上限を設けることを検討すると良いだろう。また、クライアントアプリケーションの種類も考慮すべき重要な要素である。例えば、モバイルアプリの多くは、ユーザーが画面をスクロールし続けると新しいコンテンツが次々に表示される「無限スクロール」を採用している。このようなインターフェースは、次のデータへのポインタを提供するカーソルページネーションと非常に相性が良い。一方で、ページ番号付きのナビゲーションバーを持つ伝統的なWebダッシュボードなどでは、オフセットページネーションの方がユーザー体験として自然かもしれない。

カーソルページネーションの具体的な実装の一例として、PythonのFlaskフレームワークを使ったシンプルなコードの考え方を紹介する。仮に、postsという投稿テーブルがあり、自動で増加するIDを持つとしよう。APIのエンドポイントでは、limit(取得件数)とcursor(カーソル値)をリクエストの引数から取得する。データベースからデータを取得する際には、まずIDで昇順に並べ替えて、limitで指定された件数に「1」を足した数だけデータを取得する。この「limit + 1」件取得するというテクニックは、次のページが存在するかどうかを判断するためによく用いられる。もしcursorが指定されていれば、そのカーソルが示すIDよりも大きいデータに限定して取得する。取得したデータの件数がlimitより多ければ、それは次のページが存在することを意味し、取得したデータの中から最後のアイテムのIDをnext_cursorとして設定し、クライアントに返す。これにより、クライアントはnext_cursorを使って次のページをリクエストできるようになる。

このように、ページネーションは単にlimitoffsetを追加するだけの単純な話ではない。APIのパフォーマンス、データの整合性、そしてAPIを利用するクライアントの使いやすさに直接影響する重要な設計要素である。まずはオフセットページネーションで試作を始めるのは良い出発点だが、本格的なトラフィックを想定する際には、遅かれ早かれカーソルページネーションやキーセットページネーションへの移行を検討すべきである。将来の自分、そしてAPIの利用者たちも、その選択に感謝するだろう。

関連コンテンツ

関連IT用語