DynamoDB Scan in Java (AWS SDK v2)
scanPaginator erledigt LastEvaluatedKey für dich — und der eine Regler, zu dem die meisten als Nächstes greifen, .limit(...), macht denselben Scan langsamer und etwas teurer. Wann du Scan ganz vermeiden solltest, steht in Query vs. Scan.
Code
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;
public class ScanExample {
public static void main(String[] args) {
try (DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build()) {
Map<String, String> names = new HashMap<>();
names.put("#filter0", "Year");
Map<String, AttributeValue> values = new HashMap<>();
values.put(":filterValue0", AttributeValue.builder().n("2010").build());
ScanRequest request = ScanRequest.builder()
.tableName("Music")
.filterExpression("#filter0 >= :filterValue0")
.expressionAttributeNames(names)
.expressionAttributeValues(values)
.build();
List<Map<String, AttributeValue>> items = new ArrayList<>();
for (ScanResponse page : ddb.scanPaginator(request)) {
items.addAll(page.items());
}
System.out.println("Matched " + items.size() + " items");
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
}
}
}.limit(25) macht aus 3 Requests 25 — und kostet mehr
Das Fixture hält 600 Songs von jeweils rund 3,9 KB, 8 davon treffen zu. Führe das Beispiel wie geschrieben aus und dann noch einmal mit .limit(25):
| Request-Form | Roundtrips | Leseeinheiten | Zurückgegebene Items |
|---|---|---|---|
| wie geschrieben | 3 | 284,5 | 8 |
.limit(25) | 25 | 288,0 | 8 |
Kapazität wird pro Seite abgerechnet, aufgerundet auf eine 4-KB-Grenze — ein 1-MB-Lesevorgang in 24 kleine zu zerschneiden bezahlt die Aufrundung also 24 Mal. Der 25. Request ist die andere Überraschung: Seite 24 hat die Tabelle beendet und trotzdem einen LastEvaluatedKey zurückgegeben, der Paginator hat also noch einmal gefragt und scannedCount=0 bekommen. DynamoDB signalisiert „keine weiteren Daten", indem es diesen Key weglässt, nicht durch eine kurze Seite — und an einer Limit-Grenze weiß es das noch nicht.
.limit(...) ist ein Regler zur Kapazitätsglättung für einen Hintergrundjob, der eine Live-Tabelle nicht drosseln soll. Es ist kein Weg, einen Scan günstiger oder kürzer zu machen.
Der Alias ist keine Stilfrage
Lass #filter0 weg und filtere direkt auf Year, und das SDK meldet über awsErrorDetails() das hier:
DynamoDbException / ValidationException /
Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year
/ http 400Year ist eines von 573 reservierten Wörtern. Wie jeder Paginator-Fehler in diesem SDK kommt er bei der ersten Iteration der for-Schleife an, nicht beim Aufruf von scanPaginator(request) — das try muss die Schleife also umschließen.
Am 2026-07-28 gegen DynamoDB Local (amazon/dynamodb-local) mit software.amazon.awssdk:dynamodb 2.49.4 auf OpenJDK 26.0.1 gemessen.
Erklärung
- Die ersten beiden Seiten geben null Items zurück. Mit diesem Filter lauten die Ergebnisse pro Seite 0, 0 und 8, bei 128,5, 128,5 und 27,5 Leseeinheiten.
filterExpressionläuft nach dem Lesen, diese beiden leeren Antworten kosten also den vollen Preis, und jedesif (page.items().isEmpty()) breakmeldet eine leere Tabelle. ddb.scanPaginator(request).items()flacht die Seiten zu einem einzelnenIterable<Map<String, AttributeValue>>ab und blättert hinter dir her, was die verschachtelte Schleife zusammenfallen lässt, wenn du nur Items willst. Es ist einSdkIterable,.stream()funktioniert also —.items().stream().count()gibt hier 8 zurück.ScanIterableführt den Scan bei jeder Iteration erneut aus. Es ist lazy, nicht gecacht: Zweimal über dasselbe Objekt zu laufen schickt die Requests zweimal und rechnet zweimal ab. Leere es einmal in eineList, so wie das Beispiel es tut.- Zahlen sind im Builder
String.AttributeValue.builder().n("2010")nimmt einenjava.lang.String, denn DynamoDB transportiert Zahlen als Dezimaltext. Einintzu übergeben kompiliert nicht. .segment(...)/.totalSegments(...)teilen einen Full-Table-Scan auf Worker auf, jeder mit eigenem Paginator. Das teilt die Wanduhrzeit und verbraucht dieselbe Kapazität.
Mach es visuell
Der Reserved-Words-Checker prüft deine Attributnamen gegen die vollständige AWS-Liste und gibt dir die ExpressionAttributeNames-Map zurück — schneller, als eine ValidationException nach der anderen zu lernen, dass Year, Name, Size und Status alle vergeben sind.
Um einen Filter gegen eine echte Tabelle auszuprobieren, bevor du ihn in einen ScanRequest einbaust, lade DynoTable herunter und blättere die Ergebnisse in einem Grid durch.
Verwandte Beispiele
- DynamoDB Scan in Go — derselbe Scan mit AWS SDK for Go v2.
- DynamoDB Query in Java — das günstigere Lesen, 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 exponentieller Backoff sie behandelt.
Referenzen
- Scan — Amazon DynamoDB API Reference
- Use Scan with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- DynamoDbClient — AWS SDK for Java 2.x API Reference
- ScanRequest — AWS SDK for Java 2.x API Reference
- Scanning tables — Amazon DynamoDB Developer Guide
- Reserved words in DynamoDB — Amazon DynamoDB Developer Guide