API設計

カーソルページネーション かーそるぺーじねーしょん

ページネーションカーソルREST APIオフセット無限スクロールデータ取得
カーソルページネーションについて教えて

簡単に言うとこんな感じ!

本の「しおり」を使ってデータを分割して取得する方法だよ!「101件目から」みたいなページ番号ではなく、「このレコードの次から」っていう目印(カーソル)を使って続きを読み込むんだ。SNSのタイムラインを下にスクロールしたときに新しい投稿が続々出てくる、あれがまさにコレ!


カーソルページネーションとは

カーソルページネーション(Cursor Pagination)とは、APIでデータを分割して取得する際に、「どこまで取得したか」を示す目印(カーソル)をもとに次のデータを取得する手法です。従来の「○ページ目を取得」という方式ではなく、「このIDより後のデータ」という形で次の取得位置を指定します。

一般的なページネーションには「オフセット方式」と「カーソル方式」の2種類があります。オフセット方式は LIMIT 10 OFFSET 100 のようにデータベースに直接ページ番号を渡しますが、データ件数が多くなると処理が遅くなったり、取得中にデータが追加・削除されると表示がズレる問題があります。カーソル方式はこの問題を根本的に解決するために使われます。

SNSのタイムライン、チャットの履歴、ECサイトの商品一覧など、大量のデータをリアルタイムに近い状態で表示するサービスではカーソルページネーションが標準的な選択肢になっています。特にデータ量が数十万件・数百万件を超えるシステムや、データの追加・削除が頻繁に起きる環境では、その効果が顕著に表れます。


オフセット方式との比較

比較項目オフセット方式カーソル方式
ページ指定の方法page=3offset=20cursor=abc123 などの目印
ページ番号へのジャンプ✅ できる❌ 前から順番に辿る必要がある
データ追加・削除時の安定性❌ ズレが発生しやすい✅ ズレが起きにくい
大量データでのパフォーマンス❌ ページが深くなると遅い✅ 常に高速
実装の難易度低いやや高い
向いているUIページ番号付きナビゲーション無限スクロール・「次へ」ボタン

しおりのたとえで覚えよう

オフセット方式は「本の100ページを開いて」という指定。カーソル方式は「この文章の続きから読んで」という指定。本の途中でページが増えたり減ったりしても、しおりの位置は変わらないのがカーソル方式の強みです。

カーソル値の種類

カーソルの種類説明
IDベースレコードの一意なIDをそのまま使うcursor=10482
タイムスタンプベース最後に取得したレコードの日時cursor=2024-01-15T09:30:00Z
Base64エンコードIDや複合キーをBase64化して不透明にするcursor=eyJpZCI6MTA0ODJ9
複合カーソル複数カラムの組み合わせ(ソート対応)cursor=MTAyLDEwNDgy

歴史と背景

  • 2000年代前半:Webアプリの黎明期。データ件数が少なく、オフセット方式のページネーションで十分だった
  • 2000年代後半:SNS・UGC(ユーザー投稿コンテンツ)の普及でデータ量が爆増。オフセット方式の限界が顕在化
  • 2010年前後:Twitterが無限スクロールを採用。カーソルベースのAPI(max_id / since_id パラメータ)が注目される
  • 2012年:Facebookが Graph API を公開し、after / before カーソルによるページネーションを標準化
  • 2015年:Facebookが RelayGraphQLクライアント)を公開。edges / node / cursor を使った Connections仕様 が業界標準として広まる
  • 2018年以降:GitHub、Stripe、Shopifyなど主要なAPIがカーソルページネーションをデフォルトに採用。REST・GraphQL両方で普及

APIでの実装パターン

カーソルページネーションの具体的なAPIのやり取りを見てみましょう。

リクエスト例(REST API

GET /api/posts?limit=10&cursor=eyJpZCI6MTA0ODJ9

レスポンス例(JSON)

{
  "data": [
    { "id": 10483, "title": "投稿タイトル1" },
    { "id": 10484, "title": "投稿タイトル2" }
  ],
  "pagination": {
    "next_cursor": "eyJpZCI6MTA0ODR9",
    "has_next_page": true,
    "has_previous_page": true,
    "prev_cursor": "eyJpZCI6MTA0ODJ9"
  }
}

次のページを取得するには、next_cursor の値をそのまま次のリクエストの cursor パラメータに渡すだけです。

GraphQL Connections仕様

GraphQLではFacebook由来の「Connections仕様」が広く使われます。

query {
  posts(first: 10, after: "eyJpZCI6MTA0ODJ9") {
    edges {
      cursor
      node {
        id
        title
      }
    }
    pageInfo {
      endCursor
      hasNextPage
      hasPreviousPage
      startCursor
    }
  }
}

以下の図は、REST方式とGraphQL Connections方式のカーソルページネーションの対応関係を示します。

カーソルページネーション:REST vs GraphQL Connections REST API リクエストパラメータ limit / cursor データ配列 data: [ {...}, {...} ] ページ情報 next_cursor / has_next_page 前ページ取得 prev_cursor パラメータ GraphQL Connections 引数 first / after(または last / before) エッジ(edges) edges: [ { cursor, node } ] ページ情報(pageInfo) endCursor / hasNextPage 前ページ取得 last / before 引数

関連する規格・RFC

規格・RFC番号内容
RFC 5988Web Linking。Link ヘッダーによる next / prev ページリンクの標準仕様(後継はRFC 8288)
RFC 8288Web Linking(RFC 5988の更新版)。REST APIのページネーションで Link: <url>; rel="next" を使う際の根拠となる仕様

関連用語