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 richiestaRound tripUnità di letturaItem restituiti
così com'è3284,58
.limit(25)25288,08

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 400

Year è 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. filterExpression viene eseguito dopo la lettura, quindi quelle due risposte vuote costano il prezzo pieno, e qualsiasi if (page.items().isEmpty()) break segnala una tabella vuota.
  • ddb.scanPaginator(request).items() appiattisce le pagine in un unico Iterable<Map<String, AttributeValue>> e pagina dietro le quinte, il che elimina il ciclo annidato quando vuoi solo gli Item. È un SdkIterable, quindi .stream() funziona — qui .items().stream().count() restituisce 8.
  • ScanIterable riesegue 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 una List, come fa l'esempio.
  • Nel builder i numeri sono String. AttributeValue.builder().n("2010") prende una java.lang.String, perché DynamoDB trasporta i numeri come testo decimale. Passare un int non 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

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.