初級読了 2 分

DynamoDB のページネーション

DynamoDB は1回の呼び出しで「すべての」結果を返すことはありません。QueryScan は最大 1 MB のデータを返し、続きから再開するための LastEvaluatedKey を渡します。 ページネーションを正しく行うとは、カウンターではなく、そのキーでループすることです。

ループ

let key;
do {
  const out = await client.send(new QueryCommand({...params, ExclusiveStartKey: key}));
  process(out.Items);
  key = out.LastEvaluatedKey;
} while (key);

LastEvaluatedKeyundefined のとき、末尾に到達しています。それを ExclusiveStartKey として渡し戻すと、次のスライスを取得できます。

制御フローは、キーが存在しないときだけ抜ける単一のループです。

presentabsentQuery / ScanProcess ItemsLastEvaluatedKey?Set ExclusiveStartKeyDone

どのパスも、返ってきたキーから再開するか停止するかのどちらかです — カウンターはありません。

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 を回避するためにページングしている なら、それはたいていインデックスを追加すべきサインです。

DynoTable を試す と、カーソルを代わりに追跡しながらクエリ結果を ビジュアルにページングできます。

更新日