Scan DynamoDB in Java (AWS SDK v2)
scanPaginator gestisce LastEvaluatedKey al posto tuo, e la manopola a cui la maggior parte delle persone ricorre subito dopo — .limit(...) — rende lo stesso scan più lento e leggermente più costoso. Per capire quando evitare del tutto Scan, vedi Query vs. Scan.
Codice
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) trasforma 3 richieste in 25 e costa di più
La fixture contiene 600 brani di circa 3,9 KB ciascuno, e 8 di essi corrispondono. Esegui l'esempio così com'è, poi rieseguilo con .limit(25):
| Forma della richiesta | Round trip | Unità di lettura | Item restituiti |
|---|---|---|---|
| così com'è | 3 | 284,5 | 8 |
.limit(25) | 25 | 288,0 | 8 |
La capacità viene fatturata per pagina, arrotondata per eccesso a un confine di 4 KB, quindi affettare una lettura da 1 MB in 24 piccole significa pagare l'arrotondamento 24 volte. La 25ª richiesta è l'altra sorpresa: la pagina 24 ha finito la tabella e ha comunque restituito un LastEvaluatedKey, quindi il paginator ha chiesto ancora una volta e ha ottenuto scannedCount=0. DynamoDB segnala "non ci sono più dati" omettendo quella chiave, non restituendo una pagina corta, e su un confine di Limit non lo sa ancora.
.limit(...) è una manopola per spalmare la capacità in un job di background che non vuoi faccia throttling su una tabella live. Non è un modo per rendere uno scan più economico o più breve.
L'alias non è stilistico
Togli #filter0 e filtra direttamente su Year, e l'SDK ti restituisce questo attraverso awsErrorDetails():
DynamoDbException / ValidationException /
Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year
/ http 400Year è una delle 573 parole riservate. Come ogni errore del paginator in questo SDK, arriva alla prima iterazione del ciclo for, non quando chiami scanPaginator(request), quindi il try deve avvolgere il ciclo.
Misurato il 2026-07-28 su DynamoDB Local (amazon/dynamodb-local) con software.amazon.awssdk:dynamodb 2.49.4 su OpenJDK 26.0.1.
Spiegazione
- Le prime due pagine restituiscono zero Item. Con questo filtro i risultati per pagina sono 0, 0 e 8, a 128,5, 128,5 e 27,5 unità di lettura.
filterExpressionviene eseguito dopo la lettura, quindi quelle due risposte vuote costano il prezzo pieno, e qualsiasiif (page.items().isEmpty()) breaksegnala una tabella vuota. ddb.scanPaginator(request).items()appiattisce le pagine in un unicoIterable<Map<String, AttributeValue>>e pagina dietro le quinte, il che elimina il ciclo annidato quando vuoi solo gli Item. È unSdkIterable, quindi.stream()funziona — qui.items().stream().count()restituisce 8.ScanIterableriesegue lo scan a ogni iterazione. È lazy, non memorizzato: iterare due volte sullo stesso oggetto invia le richieste due volte e le fattura due volte. Svuotalo una sola volta in unaList, come fa l'esempio.- Nel builder i numeri sono
String.AttributeValue.builder().n("2010")prende unajava.lang.String, perché DynamoDB trasporta i numeri come testo decimale. Passare unintnon compila. .segment(...)/.totalSegments(...)dividono uno scan sull'intera tabella tra più worker, ciascuno con il proprio paginator. Questo divide il tempo a orologio e spende la stessa capacità.
Fallo visivamente
Il checker delle parole riservate confronta i nomi dei tuoi attributi con la lista AWS completa e restituisce la mappa ExpressionAttributeNames, il che è più rapido che scoprire una ValidationException alla volta che Year, Name, Size e Status sono tutti occupati.
Per provare un filtro su una tabella reale prima di cablarlo in una ScanRequest, scarica DynoTable e sfoglia i risultati in una griglia.
Esempi correlati
- Scan DynamoDB in Go — lo stesso scan con AWS SDK for Go v2.
- Query DynamoDB in Java — la lettura più economica a cui dovresti solitamente ricorrere.
- Query vs. Scan — quando (raramente) uno
Scanè giustificato. - Perché il mio Scan DynamoDB è lento e costoso? — il modello di costo e come evitarlo.
- DynamoDB ProvisionedThroughputExceededException — cosa fa uno scan sull'intera tabella alla capacità di una tabella con provisioning.
- DynamoDB ThrottlingException — l'altro throttle, e come lo gestisce il backoff esponenziale.
Riferimenti
- 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