Scan do DynamoDB em Go (AWS SDK v2)
O NewScanPaginator é um cursor, não uma coleção: ele começa otimista, termina esgotado e tira o tamanho de página de um lugar que o ScanInput à sua frente não mostra. Para saber quando evitar o Scan por completo, veja Query vs. Scan.
Código
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))
}O que o HasMorePages() de fato retorna
Contra um fixture de 600 músicas de aproximadamente 3,9 KB cada, das quais 8 casam com Year >= 2010 e ficam por último na ordenação:
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"O HasMorePages() é true antes da primeira chamada, então o laço sempre dispara ao menos uma vez. Você não pode usá-lo para testar se uma tabela está vazia. Duas páginas então retornaram zero itens enquanto o scan ainda estava rodando, o que significa que if len(page.Items) == 0 { break } reporta resultado vazio em uma tabela que tem 8 correspondências. E o paginador é de uso único: depois do laço o valor está gasto, e o NextPage retorna um erro em vez de recomeçar. Escanear de novo significa chamar NewScanPaginator de novo.
O Limit mora em dois lugares, e a opção ganha
O Go divide o tamanho de página entre a struct de entrada e as opções do próprio paginador. Defina os dois e a opção tem precedência:
ScanInput.Limit = 500, ScanPaginatorOptions.Limit = 25 -> ScannedCount 25Atribuir in.Limit = aws.Int32(500) e esperar páginas de 500 itens é um erro silencioso quando uma opção funcional em outro lugar já definiu 25. Se você mexe só no ScanInput, o paginador o respeita.
Medido em 2026-07-28 contra o DynamoDB Local (amazon/dynamodb-local) com aws-sdk-go-v2/service/dynamodb v1.62.1 no go1.26.5.
Explicação
page.Itemsé[]map[string]types.AttributeValue, não a sua struct. Oattributevalue.UnmarshalListOfMaps(items, &songs)converte o lote em uma chamada e transformaYearem umintdo Go; as struct tagsdynamodbavcontrolam o mapeamento.- Números são strings na rede. O valor cru é
&types.AttributeValueMemberN{Value: "2010"}— o membroNguarda umastringdo Go, então ostrconvfica dos dois lados de qualquer aritmética, a menos que você faça unmarshal para uma struct tipada. - A
FilterExpressionroda depois da leitura, e é por isso que as duas páginas vazias ainda custaram 128,5 unidades cada. A referência da API é explícita ao dizer que filtrar "does not consume any additional read capacity units", e o corolário é que também não economiza nenhuma. #filter0é obrigatório, não estilístico.Yearé uma palavra reservada do DynamoDB; sem alias ele retornaValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year.Segment/TotalSegmentsdão a cada goroutine a sua própria fatia da tabela, e cada uma precisa do seu próprio paginador. O paralelismo corta o tempo de relógio e gasta a mesma capacidade.
Faça isso visualmente
O construtor de consultas do DynamoDB emite o formato do programa inteiro — filtro, mapas de nome e de valor e o laço do paginador — para que as partes que esta página mede sejam geradas em vez de lembradas.
Para ver quantos itens um filtro realmente toca antes de você entregar o scan a um serviço, baixe o DynoTable e rode-o contra a tabela em uma grade.
Exemplos relacionados
- Scan do DynamoDB em Java — o mesmo scan com o AWS SDK for Java 2.x.
- Query do DynamoDB em Go — a leitura mais barata a que você normalmente deveria recorrer.
- Query vs. Scan — quando (raramente) um
Scanse justifica. - Por que meu Scan do DynamoDB é lento e caro? — o modelo de custo e como evitá-lo.
- DynamoDB ProvisionedThroughputExceededException — o que um scan de tabela inteira faz com a capacidade de uma tabela provisionada.
- DynamoDB ThrottlingException — o outro throttle, e como o backoff exponencial lida com ele.