Node.js (AWS SDK v3) で DynamoDB の GSI をクエリする
GSI のクエリは、普通の Query に IndexName を足したものです。そして 2 つのことがテーブルのクエリのようには振る舞わなくなります。使い慣れた整合性フラグがエラーになり、ページネーションのカーソルが属性を 1 つ増やします。ここでは AlbumTitle-index でアルバム別に曲を取得します。ベーステーブル(Artist + SongTitle)ではスキャンなしにはできないことです。
コード
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',
IndexName: 'AlbumTitle-index',
KeyConditionExpression: '#hashKey = :hashKeyValue',
ExpressionAttributeNames: {
'#hashKey': 'AlbumTitle'
},
ExpressionAttributeValues: {
':hashKeyValue': {S: 'Danzon'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Found ${items.length} songs on the album`);カーソルは 2 つではなく 3 つの属性幅
上のループを、1 枚のアルバムに載った 300 曲に対して実行し、返ってくる LastEvaluatedKey を覗いてみます。
table query -> ['Artist', 'SongTitle']
GSI query -> ['AlbumTitle', 'Artist', 'SongTitle']GSI のキーは一意ではないので、インデックスキーだけではスキャンを再開できません。DynamoDB はインデックスキー と ベーステーブルのキーを一緒に返し、その両方をそのまま ExclusiveStartKey に戻さなければなりません。「最後に見たソートキー」を保存する自作のカーソルが、テーブルでは動くのにインデックスでは静かにアイテムを取りこぼしたり重複させたりするのはこのためです。そして、テーブルのキーが漏らしたくないユーザー ID である場合に、そのキーをクライアントに永続化するのがまずい理由でもあります。
ConsistentRead: true はアップグレードではなく 400
直感的には、強整合読み取りはキャパシティを多く使う代わりに新しいデータが得られるはずです。GSI では、代わりにリクエストそのものを失います。
ValidationException: Consistent reads are not supported on global secondary indexes
HTTP 400API リファレンスも同じくらい率直です: "Strongly consistent reads are not supported on global secondary indexes. If you query a global secondary index with ConsistentRead set to true, you will receive a ValidationException." GSI に対する Scan も、同じフラグを同じメッセージで拒否します。ローカルセカンダリインデックスなら受け付けます。
2026-07-28 に、node v24.18.0 上の @aws-sdk/client-dynamodb 3.1095.0 で DynamoDB Local(amazon/dynamodb-local)に対して再現しました。エラーテキストとキーの形はエンジン自身の出力です。
解説
IndexNameはTableNameの代わりではありません。両方を同じコマンドに入れ、KeyConditionExpressionはテーブルではなく インデックスの パーティションキー(AlbumTitle)を指名します。演算子の種類はテーブルのクエリと同じです。- 得られるのは射影された内容だけです。GSI のクエリはインデックスが射影しているもの(
ALL、KEYS_ONLY、またはINCLUDEのリスト)を返します。API リファレンスによれば "global secondary index queries cannot fetch attributes from the parent table" です。属性が足りなければ、ベースキーに対する追加のGetItemを打つか、射影を広げてインデックスを作り直すことになります。 - インデックスキーを持たないアイテムは決して現れません。これがスパースインデックスのパターンで、これは機能です。
status = "OPEN"の行だけをインデックスに載せれば、GSI は小さいままです。同時に、GSI のクエリが予想より少ないアイテムを返し、それでいてエラーを出さない理由でもあります。 - レプリケーションは非同期なので、たった今テーブルに着地した書き込みがまだインデックスに入っていないことがあります。read-after-write の経路では、タイトループでリトライするのではなく、その分を織り込んで設計しましょう。
ビジュアルに行う
後から GSI を足すのは、これを学ぶ高くつくやり方です。シングルテーブル設計プランナーは、アクセスパターンを受け取り、そのうちどれがインデックスキーを必要とし、どれはベーステーブルで足りるのかを割り出します。
テーブルのインデックスを閲覧し、ページング付きのグリッドでフォームから GSI クエリを実行するには、DynoTable をダウンロードしてください。
関連する例
- Python で DynamoDB の GSI をクエリする — boto3 での同じインデックスクエリ。
- AWS CLI で DynamoDB の GSI をクエリする — シェルからの同じインデックスクエリ。
- Node.js での DynamoDB Query — ベーステーブルへのクエリ。
- GSI と LSI の比較 — アクセスパターンにどちらのインデックス型が合うか。
- GSI が結果整合性である理由 — レプリケーション遅延の解説。
- 「The table does not have the specified index」 — インデックス名が一致していない(GSI 名は大文字小文字を区別します)。
- 「Consistent reads are not supported on global secondary indexes」 — GSI で強整合読み取りのフラグが失敗する理由。