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 de Scan contient {"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ègle Limit, puisque Limit est le limit_key. Limit borne les éléments lus, jamais les éléments renvoyés : avec une FilterExpression, une page peut donc être vide et avoir quand même coûté.
  • MaxItems compte les éléments des result_key et te rend un NextToken que tu peux repasser comme StartingToken dans 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 des result_key. Items, Count et ScannedCount sont sommés ; ConsumedCapacity est une non_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.
  • FilterExpression s'exécute côté serveur après la lecture, donc tu es facturé sur ScannedCount, pas sur Count. #filter0 aliase Year parce 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 sur e.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.scan prend des types Python natifs, renvoie les nombres en decimal.Decimal et construit les filtres avec Attr("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.0

Quatre 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

Références

Dernière vérification le 2026-07-28 par rapport à la documentation officielle AWS liée ci-dessus.

Construis cette requête visuellement

Compose cette opération dans le Générateur de requêtes DynamoDB gratuit — condition de clé, filtre, index, Limit, ordre de tri et boucle de pagination — et copie-la en retour comme programme exécutable SDK v3, CLI ou boto3.

Ouvrir le Générateur de requêtes DynamoDB

Travaille avec DynamoDB sans la Console

Un client de bureau rapide pour DynamoDB qui exécute le vrai SQL que DynamoDB ne peut pas — JOINs, GROUP BY, agrégations — avec édition visuelle et un agent IA sur tes propres clés Bedrock.

Essai gratuit de 30 jours, sans carte bancaire — ensuite la formule Gratuit, sans limite de durée.