Scan DynamoDB en Java (AWS SDK v2)
scanPaginator gère LastEvaluatedKey à ta place, et le seul bouton que la plupart des gens actionnent ensuite — .limit(...) — rend le même scan plus lent et légèrement plus cher. Pour savoir quand éviter Scan complètement, voir 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) transforme 3 requêtes en 25 et coûte plus cher
Le jeu de test contient 600 morceaux d'environ 3,9 Ko chacun, dont 8 correspondent. Lance l'exemple tel quel, puis relance-le avec .limit(25) :
| Forme de la requête | Allers-retours | Unités de lecture | Éléments renvoyés |
|---|---|---|---|
| tel quel | 3 | 284,5 | 8 |
.limit(25) | 25 | 288,0 | 8 |
La capacité est facturée par page, arrondie à la frontière des 4 Ko : découper une lecture de 1 Mo en 24 petites paie donc l'arrondi 24 fois. La 25e requête est l'autre surprise : la page 24 a terminé la table et a quand même renvoyé un LastEvaluatedKey, donc le paginateur a redemandé une fois de plus et a reçu scannedCount=0. DynamoDB signale « plus de données » en omettant cette clé, pas en renvoyant une page courte, et à une frontière de Limit il ne le sait pas encore.
.limit(...) est un bouton de lissage de capacité pour un job d'arrière-plan dont tu ne veux pas qu'il throttle une table en production. Ce n'est pas un moyen de rendre un scan moins cher ou plus court.
L'alias n'est pas stylistique
Supprime #filter0 et filtre directement sur Year : le SDK fait remonter ceci via awsErrorDetails() :
DynamoDbException / ValidationException /
Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year
/ http 400Year fait partie des 573 mots réservés. Comme toute erreur de paginateur dans ce SDK, elle arrive à la première itération de la boucle for, et non à l'appel de scanPaginator(request) : le try doit donc envelopper la boucle.
Mesuré le 2026-07-28 sur DynamoDB Local (amazon/dynamodb-local) avec software.amazon.awssdk:dynamodb 2.49.4 sur OpenJDK 26.0.1.
Explication
- Les deux premières pages ne renvoient aucun élément. Avec ce filtre, les résultats par page sont 0, 0 puis 8, à 128,5, 128,5 et 27,5 unités de lecture.
filterExpressions'exécute après la lecture, donc ces deux réponses vides coûtent plein tarif, et toutif (page.items().isEmpty()) breakrapporte une table vide. ddb.scanPaginator(request).items()aplatit les pages en un uniqueIterable<Map<String, AttributeValue>>et pagine derrière toi, ce qui supprime la boucle imbriquée quand tu ne veux que les éléments. C'est unSdkIterable, donc.stream()fonctionne —.items().stream().count()renvoie 8 ici.ScanIterablerelance le scan à chaque itération. Il est paresseux, pas mis en cache : boucler deux fois sur le même objet envoie les requêtes deux fois et facture deux fois. Vide-le une bonne fois dans uneList, comme le fait l'exemple.- Les nombres sont des
Stringdans le builder.AttributeValue.builder().n("2010")prend unjava.lang.String, parce que DynamoDB transporte les nombres sous forme de texte décimal. Passer unintne compilera pas. .segment(...)/.totalSegments(...)répartissent un scan de table complète entre plusieurs workers, chacun avec son propre paginateur. Ça divise le temps écoulé et dépense la même capacité.
Le faire visuellement
Le vérificateur de mots réservés confronte tes noms d'attributs à la liste complète d'AWS et te rend la map ExpressionAttributeNames, ce qui est plus rapide que d'apprendre que Year, Name, Size et Status sont tous pris, une ValidationException à la fois.
Pour essayer un filtre sur une vraie table avant de le câbler dans un ScanRequest, télécharge DynoTable et parcours les résultats dans une grille.
Exemples liés
- DynamoDB Scan en Go — le même scan avec l'AWS SDK for Go v2.
- Query DynamoDB en Java — la lecture moins chère vers laquelle tu devrais te tourner en général.
- Query vs Scan — quand (rarement) un
Scanse justifie. - Pourquoi mon Scan DynamoDB est-il lent et coûteux ? — le modèle de coût et comment l'éviter.
- DynamoDB ProvisionedThroughputExceededException — ce qu'un scan de table complète fait à la capacité d'une table provisionnée.
- DynamoDB ThrottlingException — l'autre throttle, et comment le backoff exponentiel le gère.
Références
- 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