Python(boto3)で DynamoDB の GSI をクエリする
GSI へのクエリは、通常の query に IndexName を足しただけのものです。AlbumTitle-index はアルバム別の曲を返してくれます。これは Artist + SongTitle というテーブルキーでは満たせないアクセスパターンです。Python で変わるのはエラー処理です。インデックス周りでもっともよくある 2 つの間違いは、boto3 の別々の層で失敗し、例外クラスで捕まえられるのはそのうち一方だけです。
コード
import boto3
client = boto3.client("dynamodb")
paginator = client.get_paginator("query")
items = []
for page in paginator.paginate(
TableName="Music",
IndexName="AlbumTitle-index",
KeyConditionExpression="#hashKey = :hashKeyValue",
ExpressionAttributeNames={"#hashKey": "AlbumTitle"},
ExpressionAttributeValues={":hashKeyValue": {"S": "Danzon"}},
):
items.extend(page["Items"])
print(f"Found {len(items)} songs on the album")except ValidationException は捕まえる以前にコンパイルすら通らない
上のクエリに ConsistentRead=True を足すと、クライアント API でもリソース API でも、boto3 はこれを送出します。
botocore.exceptions.ClientError: An error occurred (ValidationException) when
calling the Query operation: Consistent reads are not supported on global
secondary indexes素直なハンドラは except client.exceptions.ValidationException でしょう。それは存在しません。
AttributeError: <botocore.errorfactory.DynamoDBExceptions object> has no
attribute ValidationException. Valid exceptions are: BackupInUseException,
... IndexNotFoundException, ... ProvisionedThroughputExceededException, ...botocore は例外クラスをサービスモデルから生成し、DynamoDB はそのうち 33 個をモデル化しています。ValidationException はプロトコルレベルのエラーでそこには含まれないため、確実に分岐できるのはコードだけです。
except ClientError as exc:
if exc.response["Error"]["Code"] == "ValidationException":
...この非対称性は現実のものです。インデックス名を打ち間違えると IndexNotFoundException が返り、これは モデル化されていて クラスで捕捉できます。整合性フラグを誤用すると、返ってくるのは文字列比較です。どちらもインデックスのエラーですが、型を持つのは一方だけです。
カーソルはテーブルのキーも運ぶ
ページネータは LastEvaluatedKey を隠しますが、インデックス上でそれが何を保持しているかは知っておく価値があります。1 枚のアルバムに 300 曲ある場合です。
page 1: Count 271 capacity 128.5 LastEvaluatedKey ['AlbumTitle', 'Artist', 'SongTitle']
page 2: Count 29 capacity 14.0 LastEvaluatedKey []GSI のキーは一意ではないため、インデックスのキーだけでは読み取りを再開できません。DynamoDB はインデックスのキーとベーステーブルのキーをまとめて返します。インデックスのキーしか保存しない自作のページ送りは、アイテムを重複させるか取りこぼします。
2026-07-28 に、CPython 3.14.6 上の boto3 1.43.58 で DynamoDB Local(amazon/dynamodb-local)に対して再現しました。エラーテキストとキーの一覧はライブラリ自身の出力です。
解説
IndexNameはTableNameの代わりではありません。両方を同じ呼び出しに入れ、KeyConditionExpressionは インデックスの パーティションキー(AlbumTitle)を、テーブルへのクエリと同じ演算子セットで指定します。- 得られるのは射影された分だけです。インデックスが返すのは射影したもの(
ALL、KEYS_ONLY、またはINCLUDEのリスト)だけです。API リファレンスによれば「global secondary index queries cannot fetch attributes from the parent table」。属性が足りなければ、ベースキーへの追加のget_itemか、より広い射影を持つ新しいインデックスが必要になります。 - インデックスキーを持たないアイテムは決して現れません — スパースインデックスのパターンです。
status = "OPEN"に対するインデックスを小さく保てる一方、GSI へのクエリが期待より少なく返しつつ何も送出しない理由でもあります。 - リソース API も同じ
IndexNameを取ります。table.query(IndexName="AlbumTitle-index", KeyConditionExpression=Key("AlbumTitle").eq("Danzon"))のように、ネイティブな Python の値を渡し、Decimalを受け取ります。 - GSI への書き込みはテーブルへの書き込みの後に着地します。レプリケーションは非同期なので、インデックスに対する read-after-write の経路はときどき取りこぼします。それをタイトなループでリトライしても、レプリケーションが速くなることはなく、キャパシティを燃やすだけです。
ビジュアルに行う
DynamoDB 式ビルダーは、インデックスのキー条件と型付きの値マップを、boto3 にそのまま渡せる Python として書き出します。クライアント API が要求し、リソース API が禁じる {"S": ...} のラッパー付きです。
同じインデックスへのクエリを、フォームから自分のテーブルに向けて実行し、結果をページ送り付きのグリッドで読むには、DynoTable をダウンロードしてください。
関連する例
- Node.js で DynamoDB の GSI をクエリする — AWS SDK v3 による同じインデックスへのクエリ。
- AWS CLI で DynamoDB の GSI をクエリする — 同じインデックスへのクエリをシェルから。
- Python での DynamoDB Query — ベーステーブルへのクエリ。
- GSI と LSI の比較 — アクセスパターンにどちらのインデックス型が合うか。
- GSI が結果整合性である理由 — レプリケーション遅延の解説。
- 「The table does not have the specified index」 — インデックス名が一致していない(GSI 名は大文字と小文字を区別します)。
- 「Consistent reads are not supported on global secondary indexes」 — GSI で強整合読み取りのフラグが失敗する理由。