Écriture par lots DynamoDB en Python (boto3 batch_writer)

batch_writer() est le seul appel DynamoDB où Python demande moins de travail que les autres SDK. Il met en tampon les puts et les deletes, les découpe en requêtes BatchWriteItem de 25, et renvoie lui-même les éléments non traités. Ce qu'il ne fait pas, c'est te protéger des deux échecs qui cassent la plupart des chargements en masse — et tous les deux apparaissent au vidage du tampon, pas à la ligne qui a fourni le mauvais élément.

Code

import boto3

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Music")

songs = [
    {"Artist": "Arturo Sandoval", "SongTitle": "Cubano Chant", "AlbumTitle": "Danzon", "Year": 1994},
    {"Artist": "Arturo Sandoval", "SongTitle": "A Mis Abuelos", "AlbumTitle": "Danzon", "Year": 1994},
    {"Artist": "Arturo Sandoval", "SongTitle": "Groovin' High", "AlbumTitle": "Swingin'", "Year": 1996},
]

with table.batch_writer() as batch:
    for song in songs:
        batch.put_item(Item=song)
    # batch_writer buffers deletes too — target a key you're NOT also putting
    # (two writes to the same key in one batch are rejected as a duplicate)
    batch.delete_item(Key={"Artist": "Ella Fitzgerald", "SongTitle": "Misty"})

print(f"Buffered {len(songs)} puts + 1 delete; the batch flushes on exit")

Explication

  • Vidage différébatch.put_item() ajoute à une liste. Rien n'est validé, sérialisé ni envoyé tant que le tampon n'atteint pas 25 ou que le bloc with ne se termine pas : la trace d'un élément invalide vient donc du vidage, et non de l'appel put_item qui l'a fourni. Si tu charges depuis un itérateur, garde ton propre index de ce qui est entré dans le tampon.
  • Des valeurs Python natives — c'est l'API resource, donc tu écris 1994, pas {"N": "1994"}. Les décimaux exigent Decimal ; un float est accepté dans le tampon puis rejeté au vidage.
  • batch_writer() est une méthode de Table. Son pendant côté lecture ne l'est pas : batch_get_item vit sur le ServiceResource, et table.batch_get_item n'existe pas. Il n'y a aucun helper de mise en tampon, de découpage ou de reprise pour les lectures par lots.
  • UnprocessedItems, pas les erreurs — c'est la seule reprise qu'il gère. Une écriture throttlée est renvoyée ; une ValidationException remonte. Passer par client.batch_write_item te rend toute la boucle, comme dans l'exemple Node.js.
  • Il ne peut pas lever les limites du service. 25 écritures par requête, 400 Ko par élément, 16 Mo par requête, ni conditions ni mises à jour, et chaque put remplace l'élément stocké en entier. Besoin d'une garde, ou du tout-ou-rien ? TransactWriteItems.

Ce que batch_writer fait réellement au vidage

Mets 30 puts en tampon et observe les appels qu'il émet. En enveloppant table.meta.client.batch_write_item pour enregistrer la taille des requêtes, sur DynamoDB Local 3.3.0 :

batch sizes sent: [25, 5]

Deux requêtes, coupées à la limite du service, avec le reliquat vidé par __exit__. Ce vidage est inconditionnel : lève une RuntimeError dans le bloc et les éléments en tampon sont quand même écrits à la sortie. Un chargement en masse qui meurt à mi-parcours laisse derrière lui un chargement partiel, pas une ardoise propre.

Passons aux deux échecs. Mets deux fois la même clé en tampon, ce qui arrive dès que tes données source contiennent un doublon :

with table.batch_writer() as batch:
    batch.put_item(Item={"Artist": "Dup", "SongTitle": "Key", "Year": 1})
    batch.put_item(Item={"Artist": "Dup", "SongTitle": "Key", "Year": 2})
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the
BatchWriteItem operation: Provided list of item keys contains duplicates

Aucun des deux put_item n'a bronché. batch_writer() ne déduplique pas sauf si tu le demandes, et le demander s'écrit table.batch_writer(overwrite_by_pkeys=["Artist", "SongTitle"]). Fais passer les deux mêmes puts par là et l'élément est stocké avec Year: 2 — le tampon garde la dernière écriture par clé, donc la déduplication est une perte de données silencieuse si tes deux lignes étaient censées être des éléments différents sous une clé que tu as mal choisie.

Le second échec appartient à boto3 seul et n'atteint jamais DynamoDB :

TypeError: Float types are not supported. Use Decimal types instead.

Un Rating à 4.5 reste dans le tampon sans provoquer la moindre plainte, puis explose au vidage. Decimal("4.5") fait l'aller-retour correctement sous la forme {"N": "4.5"}. Lis un prix ou une note depuis du JSON avec json.loads et chaque nombre est un float : c'est donc un échec dès la première exécution pour la plupart des scripts d'import. Passer parse_float=Decimal à json.loads corrige le problème à la source.

Si tu passes à la main des valeurs Python natives au format de transport, le convertisseur DynamoDB JSON affiche les deux faces du même élément pour que tu voies ce que ton Decimal devient vraiment.

Pour charger en masse depuis du CSV ou du JSON sans écrire toi-même le mappage de types, télécharge DynoTable.

Exemples liés

Références

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

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.