Node.js (AWS SDK v3) での DynamoDB Query
AWS SDK v3 における完全な Query は、多くのスニペットが見せる単発の client.send() ではなく、以下の do/while です。1 ページは 1 MB で頭打ちになり、パーティションの残りは LastEvaluatedKey を戻して初めて届きます。そもそも Query が正しい読み取りなのかについては Query と Scan の比較を参照してください。
コード
import {DynamoDBClient, QueryCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const items = [];
let lastEvaluatedKey;
do {
const response = await client.send(
new QueryCommand({
TableName: 'Music',
KeyConditionExpression: '#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)',
ExpressionAttributeNames: {
'#hashKey': 'Artist',
'#rangeKey': 'SongTitle'
},
ExpressionAttributeValues: {
':hashKeyValue': {S: 'Arturo Sandoval'},
':rangeKeyValue': {S: 'C'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Found ${items.length} items`);このループが実際にやっていること
1 曲あたり約 3.9 KB の 600 曲がすべて Artist = "Arturo Sandoval" の下にあるフィクスチャに対して、上のループは 3 回リクエストを送ります。
| 往復 | Count | ScannedCount | 読み取りユニット | LastEvaluatedKey |
|---|---|---|---|---|
| 1 | 271 | 271 | 128.5 | あり |
| 2 | 271 | 271 | 128.5 | あり |
| 3 | 58 | 58 | 27.5 | なし |
271 を設定した人は誰もいません。そこで 1 MB が尽きたというだけです。つまりページの境目は、アイテムサイズが変われば動きます。今日は 1 回でページングが終わるパーティションも、属性を 1 つ足せば 2 回になります。そして単発の send() から response.Items を読むコードは、600 曲のうち 271 曲を、エラーも出さずに静かに返します。
同じクエリに Limit: 10 と Year に対する FilterExpression を足してみましょう。
Count: 0 ScannedCount: 10 ConsumedCapacity: 5 LastEvaluatedKey: set10 件を評価し、0 件を返し、それでもリクエストは読み取りキャパシティを消費しました。Limit が制限するのは DynamoDB が 読む 量で、フィルターはその後に走ります。したがって「結果を 10 件ください」という意味で選んだ Limit が返すのは 0〜10 件です。
2026-07-28 に、node v24.18.0 上の @aws-sdk/client-dynamodb 3.1095.0 で DynamoDB Local(amazon/dynamodb-local)に対して測定しました。件数とキャパシティはエンジン自身のレスポンスフィールドです。
解説
ExclusiveStartKey: lastEvaluatedKeyは初回ではundefinedですが、それは意図的です。v3 のシリアライザーはundefinedのメンバーを落とすので、同じオブジェクトリテラルが最初のリクエストにも後続のすべてにも使えます。「先頭から始める」ときの当てずっぽうとして{}を入れると、ValidationException: The provided starting key is invalidで失敗します。@aws-sdk/client-dynamodbは決してマーシャリングしてくれません。値は{S: 'Arturo Sandoval'}として入り、アイテムも同じ形で返ってきます。それが DocumentClient を引き込まないことの代償です。素の JS オブジェクトで書きたいなら、手を伸ばすラッパーは@aws-sdk/lib-dynamodbです。- 数値は往復を文字列のまま生き延びます。
@aws-sdk/util-dynamodbのunmarshallで{N: '9007199254740993'}をアンマーシャルすると、精度の落ちるnumberではなく JS のbigintが返ります。{wrapNumbers: true}を渡せば代わりに{value: '9007199254740993'}になります。いずれにせよ、桁数を確認していない DynamoDB のNにNumber()をかけてはいけません。 KeyConditionExpressionはパーティションキーの等価条件に加えて、ソートキーの条件を最大 1 つ取ります(=、<、<=、>、>=、BETWEEN、begins_with)。それ以外はFilterExpressionに属し、これは読み取りの後に走ります。ScanIndexForward: falseはソートキーの順序を反転します。デフォルトは昇順です。IndexNameを足せば、同じコマンドがセカンダリインデックスに切り替わります。
ビジュアルに行う
DynamoDB クエリビルダーは、この形全体 — キー条件、名前と値のマップ、LastEvaluatedKey のループ — を実行可能な SDK v3 のプログラムとして出力するので、ページネーションが「うっかり忘れる部分」ではなくなります。
GUI で実際のテーブルに対してクエリを実行するには、キー条件のフォームとページング付きの結果グリッドを備えた DynoTable をダウンロードしてください。
関連ガイド
- Query と Scan の比較 —
Queryが正しいデフォルトである理由。 - キー条件式 — 使えるパーティション/ソートキーの演算子すべて。
- 「Query condition missed key schema element」 — キー条件が違う属性を指名しているか、パーティションキーを飛ばしている。
- 「Query key condition not supported」 — contains や 2 つ目のソートキー条件のような、キー条件では使えない演算子。