Scan DynamoDB en Python (boto3)
Un scan en boto3, ce sont deux décisions : la requête, et la façon de la paginer. L'extrait ci-dessous utilise le paginateur intégré, qui n'est pas un wrapper écrit par quelqu'un autour de ta boucle. Ce sont cinq lignes de configuration botocore, et ces cinq lignes décident si ton scan est correct et ce qu'il coûte. (Savoir si tu devrais scanner tout court est une autre question.)
Code
import boto3
client = boto3.client("dynamodb")
paginator = client.get_paginator("scan")
items = []
for page in paginator.paginate(
TableName="Music",
FilterExpression="#filter0 >= :filterValue0",
ExpressionAttributeNames={"#filter0": "Year"},
ExpressionAttributeValues={":filterValue0": {"N": "2010"}},
):
items.extend(page["Items"])
print(f"Matched {len(items)} items")Explication
- Le paginateur est de la donnée, pas du code. botocore livre une entrée par opération dans
paginators-1.json; celle deScancontient{"input_token": "ExclusiveStartKey", "output_token": "LastEvaluatedKey", "limit_key": "Limit", "result_key": ["Items", "Count", "ScannedCount"], "non_aggregate_keys": ["ConsumedCapacity"]}. Tout ce qui suit découle de ces clés. PaginationConfig={"PageSize": n}règleLimit, puisqueLimitest lelimit_key.Limitborne les éléments lus, jamais les éléments renvoyés : avec uneFilterExpression, une page peut donc être vide et avoir quand même coûté.MaxItemscompte les éléments desresult_keyet te rend unNextTokenque tu peux repasser commeStartingTokendans un processus ultérieur. Il n'empêche pas la requête de lire au-delà de ton seuil.build_full_result()n'agrège que les champs desresult_key.Items,CountetScannedCountsont sommés ;ConsumedCapacityest unenon_aggregate_key, donc le résultat fusionné rapporte la capacité d'une seule page comme si c'était celle de tout le scan. Somme-la toi-même, page par page, sinon tu sous-estimeras d'un facteur égal au nombre de pages.FilterExpressions'exécute côté serveur après la lecture, donc tu es facturé surScannedCount, pas surCount.#filter0aliaseYearparce que c'est un mot réservé ; sans l'alias, la requête échoue avant même de lire quoi que ce soit.- Toutes les erreurs arrivent sous forme de
botocore.exceptions.ClientError. Branche sure.response["Error"]["Code"]; les classes par erreur n'existent que comme attributs générés sur le client (client.exceptions.ProvisionedThroughputExceededException), jamais comme symboles importables. - L'API resource, c'est l'autre ergonomie.
Table.scanprend des types Python natifs, renvoie les nombres endecimal.Decimalet construit les filtres avecAttr("Year").gte(2010)au lieu de maps de placeholders.
Ce que coûte vraiment une page filtrée
60 éléments d'environ 2 Ko chacun, Year = 2024 en faisant correspondre deux, PageSize=10, exécuté sur DynamoDB Local :
page 1: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 2: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 3: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 4: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 5: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 6: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 7: Count=0 ScannedCount=0 CU=0.0 LastEvaluatedKey=no
total CU across pages: 15.0Quatre des six vraies pages n'ont rien renvoyé, au prix fort. C'est la forme du bug que le paginateur existe pour empêcher : une boucle bricolée à la main qui s'arrête quand Items est vide quitte dès la page 1 et rapporte zéro là où il y a deux morceaux correspondants.
La page 7 est l'autre moitié. La page 6 a atteint son Limit sur le dernier élément de la table, donc DynamoDB a quand même renvoyé un LastEvaluatedKey et le paginateur a dépensé un aller-retour de plus pour apprendre qu'il ne restait rien. Un LastEvaluatedKey signifie « je me suis arrêté », pas « il y en a d'autres ».
Appeler build_full_result() sur le même scan rapporte CapacityUnits: 2.5. Les six pages en ont consommé 15,0.
Paginer sans écrire la boucle
Le DynamoDB Query Builder assemble le filtre, la map d'alias et la boucle de pagination en un seul programme exécutable, si bien que le piège Limit-contre-Count ci-dessus est réglé avant même que tu ne colles le code. Pour paginer une vraie table de façon interactive plutôt que depuis un script, télécharge DynoTable.
Guides liés
- 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.
- Les scans parallèles — découper la table avec
Segment/TotalSegments, un paginateur par segment. - DynamoDB ProvisionedThroughputExceededException — ce qu'un scan de table complète fait à la capacité d'une table provisionnée.
Références
- Scan — Amazon DynamoDB API Reference
- scan — Boto3 DynamoDB.Client Reference
- Scan paginator — Boto3 DynamoDB Reference
- Scanning tables — Amazon DynamoDB Developer Guide
Dernière vérification le 2026-07-28 par rapport à la documentation officielle AWS liée ci-dessus.