DynamoDB Scan in Go (AWS SDK v2)
NewScanPaginator ist ein Cursor, keine Collection: Er startet optimistisch, endet erschöpft und nimmt seine Seitengröße von einer Stelle, die das ScanInput vor dir nicht zeigt. Wann du Scan ganz vermeiden solltest, steht in Query vs. Scan.
Code
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))
}Was HasMorePages() tatsächlich zurückgibt
Gegen ein Fixture aus 600 Songs von je rund 3,9 KB, von denen 8 auf Year >= 2010 passen und zuletzt einsortiert sind:
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() ist vor dem ersten Aufruf true, die Schleife läuft also immer mindestens einmal. Du kannst damit nicht prüfen, ob eine Tabelle leer ist. Zwei Seiten lieferten dann null Items, während der Scan noch lief — das heißt, if len(page.Items) == 0 { break } meldet ein leeres Ergebnis auf einer Tabelle, die 8 Treffer enthält. Und der Paginator ist Einweg: Nach der Schleife ist der Wert verbraucht, und NextPage liefert einen Fehler, statt neu zu starten. Erneut scannen heißt, NewScanPaginator erneut aufzurufen.
Limit wohnt an zwei Stellen, und die Option gewinnt
Go teilt die Seitengröße zwischen dem Input-Struct und den eigenen Optionen des Paginators auf. Setzt du beides, hat die Option Vorrang:
ScanInput.Limit = 500, ScanPaginatorOptions.Limit = 25 -> ScannedCount 25in.Limit = aws.Int32(500) zuzuweisen und 500-Item-Seiten zu erwarten, geht still daneben, wenn irgendwo eine funktionale Option bereits 25 gesetzt hat. Fasst du nur ScanInput an, hält sich der Paginator daran.
Am 2026-07-28 gegen DynamoDB Local (amazon/dynamodb-local) mit aws-sdk-go-v2/service/dynamodb v1.62.1 auf go1.26.5 gemessen.
Erklärung
page.Itemsist[]map[string]types.AttributeValue, nicht dein Struct.attributevalue.UnmarshalListOfMaps(items, &songs)konvertiert den Batch in einem Aufruf und macht ausYearein Go-int; diedynamodbav-Struct-Tags steuern das Mapping.- Zahlen sind auf der Leitung Strings. Der rohe Wert ist
&types.AttributeValueMemberN{Value: "2010"}— dasN-Member hält einen Go-string,strconvsitzt also auf beiden Seiten jeder Rechnung, es sei denn, du unmarshallst in ein typisiertes Struct. FilterExpressionläuft nach dem Read, deshalb kosten die beiden leeren Seiten trotzdem je 128,5 Einheiten. Die API-Referenz sagt ausdrücklich, dass Filtern "does not consume any additional read capacity units" — und die Kehrseite ist, dass es auch keine spart.#filter0ist Pflicht, nicht Stil.Yearist ein reserviertes Wort in DynamoDB; ohne Alias liefert esValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year.Segment/TotalSegmentsgeben jeder Goroutine ihr eigenes Segment der Tabelle, und jede braucht ihren eigenen Paginator. Parallelität senkt die Laufzeit und verbraucht dieselbe Kapazität.
Mach es visuell
Der DynamoDB Query Builder gibt die komplette Programmform aus — Filter, Name- und Value-Maps und die Paginator-Schleife —, sodass die Teile, die diese Seite misst, generiert statt erinnert werden.
Um zu sehen, wie viele Items ein Filter wirklich anfasst, bevor du den Scan einem Dienst überlässt, lade DynoTable herunter und führe ihn in einem Grid gegen die Tabelle aus.
Verwandte Beispiele
- DynamoDB Scan in Java — derselbe Scan mit AWS SDK für Java 2.x.
- DynamoDB Query in Go — der günstigere Read, zu dem du normalerweise greifen solltest.
- Query vs. Scan — wann ein
Scan(selten) gerechtfertigt ist. - Warum ist mein DynamoDB-Scan langsam und teuer? — das Kostenmodell und wie du es vermeidest.
- DynamoDB ProvisionedThroughputExceededException — was ein Full-Table-Scan mit der Kapazität einer provisionierten Tabelle macht.
- DynamoDB ThrottlingException — die andere Drosselung und wie exponentielles Backoff sie abfängt.