Python(boto3)에서 DynamoDB GSI 쿼리하기

GSI 쿼리는 평범한 queryIndexName을 더한 것이고, AlbumTitle-indexArtist + SongTitle 테이블 키로는 처리할 수 없는 액세스 패턴인 "앨범별 곡"을 제공합니다. Python에서 달라지는 것은 오류 처리입니다. 가장 흔한 두 가지 인덱스 실수는 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를 추가하면 boto3는 클라이언트 API에서도 리소스 API에서도 이것을 발생시킵니다:

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를 감추지만, 인덱스에서 그것이 무엇을 담는지는 알아 둘 만합니다. 한 앨범에 담긴 곡 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)을 상대로 재현했습니다. 오류 텍스트와 키 목록은 라이브러리 자체의 출력입니다.

설명

  • IndexNameTableName을 대체하지 않습니다. 둘 다 같은 호출에 들어가며, 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 쓰기는 테이블 쓰기 뒤에 도착합니다. 복제가 비동기이므로 인덱스를 상대로 한 쓰기 직후 읽기 경로는 이따금 놓칩니다. 빡빡한 루프로 재시도하면 복제가 빨라지지도 않으면서 용량만 태웁니다.

시각적으로 해보기

DynamoDB Expression Builder는 인덱스 키 조건과 타입이 붙은 값 맵을 boto3에서 바로 쓸 수 있는 Python으로 작성해 주며, 클라이언트 API가 요구하고 리소스 API가 금지하는 {"S": ...} 래퍼까지 포함합니다.

같은 인덱스 쿼리를 폼에서 여러분의 테이블로 겨누고 결과를 페이지 나뉜 그리드로 읽으려면 DynoTable을 다운로드하세요.

관련 예제

참고 자료

이 요청을 시각적으로 만들기

무료 DynamoDB 쿼리 빌더에서 이 작업을 구성하세요 — 키 조건, 필터, 인덱스, Limit, 정렬 순서, 페이지네이션 루프 — 그리고 실행 가능한 SDK v3, CLI, boto3 프로그램으로 다시 복사하세요.

DynamoDB 쿼리 빌더 열기

Console 없이 DynamoDB 작업하기

DynamoDB로는 실행할 수 없는 진짜 SQL(JOINs, GROUP BY, 집계)을 실행하는 빠른 DynamoDB 데스크톱 클라이언트. 시각적 편집과 여러분 자신의 Bedrock 키로 동작하는 AI 에이전트를 제공합니다.

30일 무료 체험, 신용카드 불필요 — 이후 기간 제한 없는 무료 요금제.