DynamoDB Scan in Go (AWS SDK v2)

NewScanPaginator è un cursore, non una collection: parte ottimista, finisce esaurito, e prende la dimensione della pagina da un posto che lo ScanInput che hai davanti non mostra. Per capire quando evitare del tutto Scan, vedi Query vs Scan.

Codice

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))
}

Cosa restituisce davvero HasMorePages()

Su una fixture di 600 canzoni di circa 3,9 KB per Item, di cui 8 soddisfano Year >= 2010 e stanno in fondo all'ordinamento:

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 prima della prima chiamata, quindi il loop parte sempre almeno una volta. Non puoi usarlo per verificare se una tabella è vuota. Poi due pagine hanno restituito zero Item mentre lo scan era ancora in corso, il che significa che if len(page.Items) == 0 { break } riporta un risultato vuoto su una tabella che contiene 8 corrispondenze. E il paginator è monouso: dopo il loop il valore è esaurito, e NextPage restituisce un errore invece di ripartire. Scansionare di nuovo significa chiamare di nuovo NewScanPaginator.

Limit vive in due posti, e vince l'opzione

Go divide la dimensione della pagina tra la struct di input e le opzioni del paginator. Impostali entrambi e ha la precedenza l'opzione:

ScanInput.Limit = 500, ScanPaginatorOptions.Limit = 25  ->  ScannedCount 25

Assegnare in.Limit = aws.Int32(500) e aspettarsi pagine da 500 Item è una svista silenziosa quando una functional option da qualche altra parte ha già impostato 25. Se tocchi solo ScanInput, il paginator lo rispetta.

Misurato il 2026-07-28 su DynamoDB Local (amazon/dynamodb-local) con aws-sdk-go-v2/service/dynamodb v1.62.1 su go1.26.5.

Spiegazione

  • page.Items è []map[string]types.AttributeValue, non la tua struct. attributevalue.UnmarshalListOfMaps(items, &songs) converte il blocco in una sola chiamata e trasforma Year in un int Go; i tag di struct dynamodbav controllano il mapping.
  • I numeri sono stringhe sul filo. Il valore grezzo è &types.AttributeValueMemberN{Value: "2010"} — il membro N contiene una string Go, quindi strconv sta su entrambi i lati di qualsiasi aritmetica se non fai l'unmarshal in una struct tipizzata.
  • FilterExpression gira dopo la lettura, ed è per questo che le due pagine vuote costano comunque 128,5 unità ciascuna. Il riferimento dell'API è esplicito: il filtro "does not consume any additional read capacity units", e il corollario è che non ne risparmia nemmeno.
  • #filter0 è obbligatorio, non stilistico. Year è una parola riservata di DynamoDB; senza alias restituisce ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year.
  • Segment / TotalSegments danno a ogni goroutine la propria fetta della tabella, e ognuna ha bisogno del proprio paginator. Il parallelismo riduce il tempo reale e spende la stessa capacità.

Fallo visivamente

Il query builder DynamoDB emette la forma dell'intero programma — filtro, mappe di nomi e valori, e il loop del paginator — così le parti che questa pagina misura sono generate invece che ricordate.

Per vedere quanti Item tocca davvero un filtro prima di affidare lo scan a un servizio, scarica DynoTable ed eseguilo sulla tabella in una griglia.

Esempi correlati

Riferimenti

Costruisci questa richiesta visivamente

Componi questa operazione nel Generatore di query DynamoDB gratuito — condizione di chiave, filtro, indice, Limit, ordine di ordinamento e un loop di paginazione — e copiala come programma eseguibile per SDK v3, CLI o boto3.

Apri il Generatore di query DynamoDB

Lavora con DynamoDB senza la Console

Un client desktop veloce per DynamoDB che esegue il vero SQL che DynamoDB non può — JOINs, GROUP BY, aggregazioni — con modifica visuale e un agente AI sulle tue chiavi Bedrock.

Prova gratuita di 30 giorni, senza carta di credito — poi il piano Free senza limiti di tempo.