APIページネーション設計:大量レコードに対応する方法

Ashley Innocent

Ashley Innocent

13 3月 2026

APIページネーション設計:大量レコードに対応する方法

Apidog エンタープライズ

オンプレミスデプロイ

SSO & RBAC

SOC 2 準拠

Apidog Enterpriseを見る

要約

大規模なデータセットでは、オフセットベースのページネーションの代わりにカーソルベースまたはキーセットベースのページネーションを使用してください。オフセットページネーション(?page=1&limit=20)は、数百万のレコードに対してパフォーマンスが低く、データの一貫性を損なう可能性があります。Modern PetstoreAPIは、効率的で一貫した結果を得るために、不透明なトークンとHATEOASリンクを備えたカーソルベースのページネーションを実装しています。

はじめに

あなたのAPIはペットのリストを返します。データベースには1000万匹のペットがいます。クライアントがGET /pets?page=500000&limit=20をリクエストしました。あなたのデータベースはOFFSET 10000000 LIMIT 20を実行します。このクエリには30秒かかり、あなたのAPIはタイムアウトします。

これがオフセットページネーションの問題です。小規模なデータセットでは問題なく機能しますが、大規模になると破綻します。20件の結果しか返さないにもかかわらず、データベースはオフセットに到達するために数百万行をスキャンしなければなりません。

以前のSwagger Petstoreはページネーションに全く対応していません。Modern PetstoreAPIは、数百万のレコードにも一貫したパフォーマンスで対応できるカーソルベースのページネーションを実装しています。

💡
REST APIを構築またはテストしている場合、Apidogはページネーションの動作をテストし、レスポンス形式を検証し、APIが大規模なデータセットを正しく処理することを確認するのに役立ちます。ページネーションのシナリオをシミュレートし、エッジケースをテストし、パフォーマンスを検証できます。
button

このガイドでは、なぜオフセットページネーションが失敗するのか、カーソルベースのページネーションがどのように機能するのか、そしてModern PetstoreAPIが効率的なページネーションをどのように実装しているのかを学びます。

なぜオフセットページネーションは大規模な環境で失敗するのか

オフセットページネーションは最も一般的なアプローチですが、深刻な問題を抱えています。

オフセットページネーションの仕組み

GET /pets?page=1&limit=20    → OFFSET 0 LIMIT 20
GET /pets?page=2&limit=20    → OFFSET 20 LIMIT 20
GET /pets?page=3&limit=20    → OFFSET 40 LIMIT 20

データベースはoffset行をスキップし、limit行を返します。

問題1:ページ番号が増加するにつれてパフォーマンスが低下する

1ページ目:

SELECT * FROM pets OFFSET 0 LIMIT 20;
-- 高速:20行をスキャン

1000ページ目:

SELECT * FROM pets OFFSET 20000 LIMIT 20;
-- 低速:20,020行をスキャンし、20行を返す

500,000ページ目:

SELECT * FROM pets OFFSET 10000000 LIMIT 20;
-- 非常に低速:10,000,020行をスキャンし、20行を返す

データベースは、破棄する行であっても、オフセットまでのすべての行をスキャンしなければなりません。パフォーマンスはページ番号に比例して低下します。

問題2:結果の一貫性がない

クライアントが結果をページングしている間に、データが変更されると:

リクエスト1:

GET /pets?page=1&limit=2
Returns: [Pet A, Pet B]

誰かがPet Zを追加しました(アルファベット順で最初にソートされる)

リクエスト2:

GET /pets?page=2&limit=2
Returns: [Pet B, Pet C]  ← Pet Bが2回出現!

新しいペットが挿入されたため、Pet Bが両方のページに表示されました。逆に、削除が発生した場合は、ペットがスキップされる可能性があります。

問題3:深いページネーションは高価である

ユーザーが10ページ目以降に進むことはほとんどありません。しかし、APIが?page=1000000を許可する場合、それに対応しなければなりません。深いページネーションクエリは高価であり、サービス拒否攻撃に利用される可能性があります。

オフセットページネーションが許容される場合

公開APIや大規模なデータセットには、カーソルベースのページネーションを使用してください。

カーソルベースのページネーションの解説

カーソルベースのページネーションは、結果セット内の位置を示すために不透明なトークンを使用します。

仕組み

リクエスト1:

GET /pets?limit=20

レスポンス1:

{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9",
    "hasMore": true
  }
}

リクエスト2:

GET /pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20

カーソルは、位置をエンコードする不透明なトークン(通常はbase64エンコード)です。クライアントはそれを解析せず、単にAPIに渡すだけです。

利点

1. 一貫したパフォーマンス

データベースはインデックスを使用してカーソル位置を直接見つけます。

SELECT * FROM pets
WHERE id > '019b4132-70aa-764f-b315-e2803d882a24'
ORDER BY id
LIMIT 20;

このクエリは、データセット内の位置に関係なく高速です。これはスキャンではなく、インデックスシークを使用します。

2. 一貫した結果

カーソルは安定しています。リクエスト間でデータが変更されても、一貫した結果が得られます。新しいレコードによって重複やスキップが発生することはありません。

3. 深いページネーション攻撃の防止

クライアントは任意の場所へジャンプできません。シーケンシャルにページングする必要があるため、悪用が制限されます。

カーソル形式

カーソルは通常、base64エンコードされたJSONです。

// デコードされたカーソル
{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "createdAt": "2026-03-13T10:30:00Z"
}

カーソルには、ページネーションを再開するために十分な情報が含まれています。Modern PetstoreAPIの場合、これにはリソースIDとソートフィールドが含まれます。

ソートされたデータのためのキーセットページネーション

キーセットページネーションは、ソートされたデータのためのカーソルベースのページネーションのバリエーションです。

仕組み

不透明なカーソルの代わりに、前のページからの最後の値を使用します。

リクエスト1:

GET /pets?limit=20&sortBy=createdAt

レスポンス1:

{
  "data": [
    {"id": "...", "createdAt": "2026-03-13T10:00:00Z"},
    ...
    {"id": "...", "createdAt": "2026-03-13T10:30:00Z"}
  ]
}

リクエスト2:

GET /pets?limit=20&sortBy=createdAt&after=2026-03-13T10:30:00Z

afterパラメーターは、前のページの最後のcreatedAt値を使用します。

SQLクエリ

SELECT * FROM pets
WHERE created_at > '2026-03-13T10:30:00Z'
ORDER BY created_at
LIMIT 20;

これは`created_at`にインデックスを使用するため効率的です。

キーセットページネーションを使用する場合

Modern PetstoreAPIはデフォルトでカーソルベースのページネーションを使用しますが、時系列データに対してはキーセットページネーションをサポートしています。

Modern PetstoreAPIはどのようにページネーションを実装しているか

Modern PetstoreAPIは、HATEOASリンクを備えたカーソルベースのページネーションを使用しています。

リクエスト形式

GET /pets?limit=20
GET /pets?cursor={token}&limit=20

パラメーター:

レスポンス形式

{
  "data": [
    {
      "id": "019b4132-70aa-764f-b315-e2803d882a24",
      "name": "Fluffy",
      "species": "CAT"
    }
  ],
  "pagination": {
    "limit": 20,
    "hasMore": true,
    "nextCursor": "eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9"
  },
  "links": {
    "self": "https://petstoreapi.com/pets?limit=20",
    "next": "https://petstoreapi.com/pets?cursor=eyJpZCI6IjAxOWI0MTMyLTcwYWEtNzY0Zi1iMzE1LWUyODAzZDg4MmEyNCJ9&limit=20"
  }
}

主要な機能

1. 不透明なカーソル

カーソルはbase64エンコードされています。クライアントはそれを解析しません。

2. HATEOASリンク

linksオブジェクトは、すぐに使用できるURLを提供します。クライアントはページネーションURLを構築する必要がありません。

3. hasMoreフラグ

さらに結果があるかどうかを示します。クライアントはいつページングを停止するかを知ることができます。

4. リミットの検証

最大リミットは100です。クライアントが巨大なページをリクエストするのを防ぎます。

詳細については、Modern PetstoreAPIのページネーションドキュメントを参照してください。

ページネーションのレスポンス形式

Modern PetstoreAPIは、ページネーションされたレスポンスを一貫した構造でラップします。

コレクションラッパー

{
  "data": [...],
  "pagination": {...},
  "links": {...}
}

なぜコレクションをラップするのか?

  1. 拡張性 - クライアントを壊すことなくメタデータを追加できる
  2. 一貫性 - すべてのページネーションエンドポイントは同じ形式を使用する
  3. HATEOAS - リンクはクライアントをページネーションに導く

ページネーションメタデータ

"pagination": {
  "limit": 20,
  "hasMore": true,
  "nextCursor": "...",
  "totalCount": 1000  // オプション、計算コストが高い
}

totalCountは、大規模なデータセットでは計算コストが高いためオプションです。ほとんどのクライアントはこれを必要としません。

Apidogによるページネーションのテスト

Apidogは、ページネーションの動作を包括的にテストするのに役立ちます。

テストシナリオ

1. 最初のページ

GET /pets?limit=20
Expect: 20 results, hasMore=true, nextCursor present

期待:20件の結果、hasMore=true、nextCursorが存在する

2. 以降のページ

GET /pets?cursor={token}&limit=20
Expect: 20 results, hasMore=true/false, nextCursor present/absent

期待:20件の結果、hasMore=true/false、nextCursorが存在/存在しない

3. 最後のページ

GET /pets?cursor={lastToken}&limit=20
Expect: < 20 results, hasMore=false, no nextCursor

期待:20件未満の結果、hasMore=false、nextCursorがない

4. 空の結果

GET /pets?status=NONEXISTENT&limit=20
Expect: 0 results, hasMore=false, no nextCursor

期待:0件の結果、hasMore=false、nextCursorがない

5. リミットの検証

GET /pets?limit=1000
Expect: 400 Bad Request (exceeds max limit)

期待:400 Bad Request(最大リミットを超過)

Apidogテスト設定

// テスト:ページネーション構造
pm.test("Response has pagination", () => {
  pm.expect(pm.response.json()).to.have.property('pagination');
  pm.expect(pm.response.json().pagination).to.have.property('hasMore');
});

// テスト:HATEOASリンク
pm.test("Response has links", () => {
  const links = pm.response.json().links;
  pm.expect(links).to.have.property('self');
  if (pm.response.json().pagination.hasMore) {
    pm.expect(links).to.have.property('next');
  }
});

適切なページネーション戦略の選択

さまざまな戦略が、さまざまなユースケースに適合します。

オフセットページネーション

次の場合に利用:

次の場合に利用しない:

カーソルベースのページネーション

次の場合に利用:

次の場合に利用しない:

キーセットページネーション

次の場合に利用:

次の場合に利用しない:

Modern PetstoreAPIの推奨事項: 公開APIおよび大規模データセットにはカーソルベースのページネーションを使用してください。

結論

ページネーションは、大規模なデータセットを返すAPIにとって不可欠です。オフセットページネーションはシンプルですが、スケーラビリティがありません。カーソルベースのページネーションは、数百万のレコードに対して一貫したパフォーマンスと信頼性の高い結果を提供します。

Modern PetstoreAPIは、不透明なトークン、HATEOASリンク、適切なメタデータを含むカーソルベースのページネーションを実装しています。この設計は効率的にスケーリングし、優れた開発者エクスペリエンスを提供します。

エッジケースを処理し、制限を検証し、一貫した結果を返すことを確認するために、Apidogを使用してページネーションの実装をテストしてください。

主なポイント:

button

よくある質問

ページネーションなしで全件を返すのはなぜいけないのですか?

1つのレスポンスで数百万件のレコードを返すと、メモリの問題、ネットワーク転送の遅延、ユーザーエクスペリエンスの低下につながります。ページネーションは大規模なデータセットにとって不可欠です。

カーソルページネーションでクライアントは特定のページにジャンプできますか?

いいえ、カーソルページネーションはシーケンシャルアクセスを必要とします。ランダムアクセスが必要な場合は、小規模なデータセットにはオフセットページネーションを検討するか、代わりに検索/フィルタリングを実装してください。

フィルタリングとページネーションをどのように扱えばよいですか?

ページネーションリクエストにフィルタパラメーターを含めます:GET /pets?status=AVAILABLE&cursor={token}&limit=20。カーソルは位置とフィルタ状態の両方をエンコードします。

ページネーションレスポンスに総件数を含めるべきですか?

クライアントが必要とし、データセットが小さい場合にのみ含めてください。大規模なデータセットでは総件数の計算はコストが高くなります(別途COUNTクエリが必要になります)。

SQLでカーソルページネーションを実装するにはどうすればよいですか?

カーソル値を含むWHERE句を使用します:SELECT * FROM pets WHERE id > ? ORDER BY id LIMIT 20。ソート列にインデックスがあることを確認してください。

カーソルトークンが無効になった場合はどうなりますか?

エラーメッセージとともに400 Bad Requestを返します。データが削除されたり、ページネーションの状態が期限切れになったりすると、カーソルは無効になる可能性があります。

カーソルはどのくらい有効であるべきですか?

Modern PetstoreAPIのカーソルは、参照されるリソースが存在する限り無期限に有効です。一部のAPIでは、カーソルを24時間後に期限切れにします。

複数のソートフィールドでカーソルページネーションを使用できますか?

はい、可能ですが、カーソルはすべてのソートフィールドをエンコードする必要があります。これによりカーソルはより複雑になります。代わりに単一の複合ソートキーを使用することを検討してください。

ApidogでAPIデザイン中心のアプローチを取る

APIの開発と利用をよりシンプルなことにする方法を発見できる