Node.js (AWS SDK v3) で DynamoDB の GSI をクエリする

GSI のクエリは、普通の QueryIndexName を足したものです。そして 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 400

API リファレンスも同じくらい率直です: "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)に対して再現しました。エラーテキストとキーの形はエンジン自身の出力です。

解説

  • IndexNameTableName の代わりではありません。両方を同じコマンドに入れ、KeyConditionExpression はテーブルではなく インデックスの パーティションキー(AlbumTitle)を指名します。演算子の種類はテーブルのクエリと同じです。
  • 得られるのは射影された内容だけです。GSI のクエリはインデックスが射影しているもの(ALLKEYS_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 をダウンロードしてください。

関連する例

参考資料

このリクエストをビジュアルに組み立てる

この操作を無料の DynamoDB クエリビルダーで組み立て — キー条件、フィルタ、インデックス、Limit、ソート順、ページネーションループ — 実行可能な SDK v3・CLI・boto3 のプログラムとしてコピーして戻れます。

DynamoDB クエリビルダーを開く

Console なしで DynamoDB を扱う

DynamoDB では実行できない本物の SQL(JOINs、GROUP BY、集計)を実行する高速な DynamoDB デスクトップクライアント。ビジュアル編集と、あなた自身の Bedrock キーで動く AI エージェントを備えています。

30日間無料トライアル、クレジットカード不要 — その後は期限のない Free プラン。