Go의 DynamoDB Scan (AWS SDK v2)
NewScanPaginator는 컬렉션이 아니라 커서입니다. 낙관적으로 시작해 소진된 채로 끝나며, 페이지 크기는 눈앞의 ScanInput이 보여 주지 않는 곳에서 가져옵니다. Scan을 아예 피해야 할 때는 Query vs. Scan을 보세요.
코드
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatalf("load config: %v", err)
}
client := dynamodb.NewFromConfig(cfg)
paginator := dynamodb.NewScanPaginator(client, &dynamodb.ScanInput{
TableName: aws.String("Music"),
FilterExpression: aws.String("#filter0 >= :filterValue0"),
ExpressionAttributeNames: map[string]string{
"#filter0": "Year",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":filterValue0": &types.AttributeValueMemberN{Value: "2010"},
},
})
var items []map[string]types.AttributeValue
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
log.Fatalf("scan: %v", err)
}
items = append(items, page.Items...)
}
fmt.Printf("Matched %d items\n", len(items))
}HasMorePages()는 실제로 무엇을 반환하나요
약 3.9 KB짜리 항목 600곡 픽스처에 대해, 그중 8개가 Year >= 2010에 일치하며 정렬상 마지막에 놓입니다:
fresh HasMorePages(): true (before any request)
page 1: len(page.Items)=0 ScannedCount=271 CU=128.5 LastEvaluatedKey set
page 2: len(page.Items)=0 ScannedCount=271 CU=128.5 LastEvaluatedKey set
page 3: len(page.Items)=8 ScannedCount= 58 CU= 27.5 LastEvaluatedKey nil
exhausted HasMorePages(): false
NextPage() after exhaustion: err = "no more pages available"HasMorePages()는 첫 호출 전에 true이므로 루프는 항상 최소 한 번은 실행됩니다. 테이블이 비었는지 확인하는 용도로는 쓸 수 없습니다. 그리고 스캔이 아직 진행 중인데도 두 페이지가 항목을 하나도 반환하지 않았는데, 이는 일치 항목 8개를 가진 테이블에서 if len(page.Items) == 0 { break }가 빈 결과를 보고한다는 뜻입니다. 또 페이지네이터는 일회용입니다. 루프가 끝나면 그 값은 소진되고, NextPage는 다시 시작하는 대신 오류를 반환합니다. 다시 스캔하려면 NewScanPaginator를 다시 호출해야 합니다.
Limit은 두 곳에 있고, 옵션이 이깁니다
Go는 페이지 크기를 입력 구조체와 페이지네이터 자체 옵션으로 나눠 둡니다. 둘 다 설정하면 옵션이 우선합니다:
ScanInput.Limit = 500, ScanPaginatorOptions.Limit = 25 -> ScannedCount 25in.Limit = aws.Int32(500)을 할당하고 500개짜리 페이지를 기대하는 것은, 다른 어딘가의 함수형 옵션이 이미 25를 설정해 둔 경우 조용히 빗나갑니다. ScanInput만 건드린다면 페이지네이터는 그것을 존중합니다.
2026-07-28에 go1.26.5의 aws-sdk-go-v2/service/dynamodb v1.62.1로 DynamoDB Local(amazon/dynamodb-local)에 대해 측정했습니다.
설명
page.Items는 여러분의 구조체가 아니라[]map[string]types.AttributeValue입니다.attributevalue.UnmarshalListOfMaps(items, &songs)가 한 번의 호출로 묶음을 변환하고Year를 Go의int로 바꿔 줍니다. 매핑은dynamodbav구조체 태그로 제어합니다.- 전송 구간에서 숫자는 문자열입니다. 원시 값은
&types.AttributeValueMemberN{Value: "2010"}이며N멤버는 Gostring을 담으므로, 타입이 지정된 구조체로 언마셜링하지 않는 한 어떤 산술에도 양쪽에strconv가 따라붙습니다. FilterExpression은 읽은 뒤에 실행되며, 그래서 항목이 없던 두 페이지도 각각 128.5 단위를 썼습니다. API 레퍼런스는 필터링이 "does not consume any additional read capacity units"라고 분명히 밝히는데, 그 따름정리는 아끼지도 못한다는 것입니다.#filter0은 취향이 아니라 필수입니다.Year는 DynamoDB 예약어이며, 별칭 없이 쓰면ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year가 반환됩니다.Segment/TotalSegments는 각 고루틴에 테이블의 몫을 나눠 주며, 각각 자기 페이지네이터가 필요합니다. 병렬화는 실제 소요 시간을 줄여 줄 뿐 같은 용량을 씁니다.
시각적으로 해보기
DynamoDB 쿼리 빌더는 필터, 이름·값 맵, 페이지네이터 루프까지 프로그램 전체 골격을 내놓으므로, 이 페이지가 측정한 부분들을 기억에 의존하지 않고 생성해서 얻을 수 있습니다.
스캔을 서비스에 태우기 전에 필터가 실제로 몇 개 항목을 건드리는지 보려면 DynoTable을 다운로드해서 테이블에 대해 그리드로 실행해 보세요.
관련 예제
- Java의 DynamoDB Scan — AWS SDK for Java 2.x로 하는 동일한 스캔.
- Go의 DynamoDB Query — 보통 먼저 손이 가야 할 더 저렴한 읽기.
- Query vs. Scan — (드물게)
Scan이 정당화되는 경우. - DynamoDB Scan은 왜 느리고 비싼가요? — 비용 모델과 이를 피하는 방법.
- DynamoDB ProvisionedThroughputExceededException — 테이블 전체 스캔이 프로비저닝된 테이블의 용량에 무슨 짓을 하는지.
- DynamoDB ThrottlingException — 또 다른 스로틀링과 지수 백오프가 이를 처리하는 방법.