仕組み
ページネーションは、大きな一覧を小分けに取得する方法です。
主な方式は、次の2つです。
| 方式 | 取得位置の指定 | 例 |
|---|---|---|
| オフセット方式 | 先頭から何件飛ばすか | 100件飛ばして20件 |
| キーセット方式 | 前回最後の並び順の値 | IDが100より大きいものを20件 |
元の「キーでセット」は、通常 キーセット:keyset を指します。
オフセット方式
SELECT id, title
FROM articles
ORDER BY id ASC
LIMIT 20 OFFSET 100000;
このクエリは、先頭の10万件を飛ばして20件を返します。
ただしDB内部では、飛ばす行にも処理が必要です。そのため、深いページほど負荷が増える場合があります。また、順序を一意に決めないと、ページごとの結果が安定しません。PostgreSQL:LIMITとOFFSET
もう一つの問題は、ページの途中でデータが増減すると、位置がずれることです。
最初の並び:A B C D E F
1ページ目 :A B C
先頭にXが追加:
X A B C D E F
OFFSET 3で次を取得:
C D E
前のページに出たCが再び現れます。
キーセット方式
「3件飛ばす」ではなく、「前回の最後だったCより後」を取得します。
ID順なら、次のように書けます。
SELECT id, title
FROM articles
WHERE id > :last_id
ORDER BY id ASC
LIMIT 20;
新着順の場合は、作成日時だけでは同じ時刻の行を区別できません。IDなどを追加して順序を一意にします。
SELECT id, title, created_at
FROM articles
WHERE (created_at, id) < (:last_created_at, :last_id)
ORDER BY created_at DESC, id DESC
LIMIT 20;
これはPostgreSQLでの例です。対応する複合インデックスも重要になります。GitLab:キーセットの性能と順序
カーソルとキーセットの違い
カーソルは、クライアントに渡す「次の取得位置を表す値」です。
{
"items": [
{ "id": 120, "title": "記事A" },
{ "id": 119, "title": "記事B" }
],
"nextCursor": "opaque-token"
}
キーセットは、その値を使ってDBから取得する方式です。
カーソルの中に単にオフセットを入れる実装も可能なので、カーソル形式のAPIだから高速、とは限りません。
使い分けと注意点
| 要件 | 選びやすい方式 |
|---|---|
| 任意のページ番号へ移動したい | オフセット |
| 小規模な管理画面 | オフセット |
| 新着一覧を順に読み進めたい | キーセット |
| 大量データを順次取得したい | キーセット |
| 取得開始時点の一覧を厳密に再現したい | 別途スナップショットなどが必要 |
キーセットでも、途中で並び順の値が変更されれば、重複や取りこぼしは起こり得ます。また、ページ番号への直接ジャンプは得意ではありません。GitLab:ページネーション設計
設計上の要点
データ件数だけでなく、ページ移動の方法と、途中の更新をどう扱うかで選びます。