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 25

in.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.Items ist []map[string]types.AttributeValue, nicht dein Struct. attributevalue.UnmarshalListOfMaps(items, &songs) konvertiert den Batch in einem Aufruf und macht aus Year ein Go-int; die dynamodbav-Struct-Tags steuern das Mapping.
  • Zahlen sind auf der Leitung Strings. Der rohe Wert ist &types.AttributeValueMemberN{Value: "2010"} — das N-Member hält einen Go-string, strconv sitzt also auf beiden Seiten jeder Rechnung, es sei denn, du unmarshallst in ein typisiertes Struct.
  • FilterExpression lä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.
  • #filter0 ist Pflicht, nicht Stil. Year ist ein reserviertes Wort in DynamoDB; ohne Alias liefert es ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year.
  • Segment / TotalSegments geben 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

Referenzen

Diesen Request visuell bauen

Stelle diese Operation im kostenlosen DynamoDB Query Builder zusammen — Key-Bedingung, Filter, Index, Limit, Sortierreihenfolge und eine Paginierungsschleife — und kopiere sie als lauffähiges SDK-v3-, CLI- oder boto3-Programm zurück.

DynamoDB Query Builder öffnen

Mit DynamoDB ohne die Console arbeiten

Ein schneller DynamoDB-Desktop-Client, der das echte SQL ausführt, das DynamoDB nicht kann — JOINs, GROUP BY, Aggregationen — mit visueller Bearbeitung und einem KI-Agenten auf deinen eigenen Bedrock-Schlüsseln.

30 Tage kostenlos testen, keine Kreditkarte — danach der Kostenlos-Tarif ohne Zeitlimit.