DynamoDB のページネーション
DynamoDB は「すべての」結果を1回の呼び出しで返すことはありません。Query や Scan は最大 1 MB のデータを返し、そこから再開するための LastEvaluatedKey を渡します。ページネーションを正しく行うとは、カウンターではなくそのキーでループすることです。
DynamoDB のページネーションはどう機能しますか?
Query や Scan は1回の呼び出しで最大 1 MB を返し、LastEvaluatedKey を返却します。ページをめくるには、そのキーを次の呼び出しの ExclusiveStartKey として渡し、DynamoDB がキーを返さなくなるまでループします。ページ番号も、総件数もありません。Limit は返される件数ではなく、評価される件数の上限です。
let key;
do {
const out = await client.send(new QueryCommand({...params, ExclusiveStartKey: key}));
process(out.Items);
key = out.LastEvaluatedKey;
} while (key);LastEvaluatedKey が undefined になったら、終端に達しています。次のスライスを取得するには、それを ExclusiveStartKey として渡し返します。
制御フローは、キーが無いときだけ抜ける単一のループです。
各パスは、返されたキーから再開するか、停止するかのいずれかです。カウンターは存在しません。
Limit はページサイズではない
Limit は DynamoDB が評価するアイテム数の上限であって、FilterExpression 適用後に返す数ではありません。フィルタの背後にある Limit: 25 のクエリは、3件を返しつつ LastEvaluatedKey を渡してくることがあります。ページが短く見えても、キーが空になるまでページングを続けなければなりません。空でない LastEvaluatedKey も、より多くの一致するアイテムがあることを約束しません。終端に達したことを証明するのは、キーが無いことだけです。
SDK にページングさせる
どちらの SDK も上記のループをラップしているので、ページを直接反復できます。
// AWS SDK for JavaScript v3
import {paginateQuery} from '@aws-sdk/lib-dynamodb';
for await (const page of paginateQuery({client}, params)) {
process(page.Items);
}# boto3
paginator = client.get_paginator('query')
for page in paginator.paginate(**params):
process(page['Items'])ページ番号はない
DynamoDB には総件数もランダムなページアクセスもありません。カーソルを再生し直さずに「7ページ目」に飛んだり前のページに戻ったりはできません。UI は無限スクロール/「もっと読み込む」を中心に設計し、番号付きページにはしないでください。(Select: 'COUNT' のクエリでも、件数を数えるために一致した全アイテムを読み取り、課金されます。)
API 向けのステートレスなカーソル
LastEvaluatedKey は最後のアイテムのキー属性にすぎません。それを Base64 エンコードし、不透明な nextToken としてクライアントに渡します。次のリクエストでそれを ExclusiveStartKey にデコードして戻します。サーバー側のカーソル状態は不要です。
そのトークンは DynamoDB-JSON です。DynamoDB-JSON コンバーターで目視したり手で組み立てたりできます。そしてScanを回避するためにページングしているなら、それはたいていインデックスを追加すべきシグナルです。
ループを書くこと自体を飛ばすには、クエリビルダーが完全な Query/Scan リクエストを組み立て、実行可能な SDK v3、CLI、boto3 のプログラムを — ページネーションループ込みで — 出力します。
DynoTable を試すと、カーソルを追跡した状態で、クエリ結果をビジュアルにページ送りできます。