Batch write su DynamoDB in Python (boto3 batch_writer)

batch_writer() è l'unica chiamata DynamoDB in cui Python richiede meno lavoro degli altri SDK. Mette in buffer put e delete, li taglia in richieste BatchWriteItem da 25 e rimanda da sé gli item non elaborati. Quello che non fa è proteggerti dai due fallimenti che rompono la maggior parte dei caricamenti di massa, ed entrambi emergono al flush anziché sulla riga che ha fornito l'item sbagliato.

Codice

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")

Spiegazione

  • Flush differitobatch.put_item() aggiunge a una lista. Nulla viene validato, serializzato o inviato finché il buffer non raggiunge 25 o il blocco with non termina, quindi il traceback di un item sbagliato arriva dal flush e non dalla chiamata put_item che l'ha fornito. Se stai caricando da un iteratore, tieni un tuo indice di ciò che è finito nel buffer.
  • Valori Python normali — questa è la resource API, quindi scrivi 1994, non {"N": "1994"}. I Decimal sono obbligatori per qualsiasi valore frazionario; un float viene accettato nel buffer e rifiutato al flush.
  • batch_writer() è un metodo di Table. La controparte in lettura non lo è: batch_get_item vive sul ServiceResource, e table.batch_get_item non esiste. Per le letture batch non c'è alcun helper di buffering, chunking o retry.
  • UnprocessedItems, non errori — è l'unico retry che gestisce. Una scrittura sottoposta a throttling viene rimandata; una ValidationException si propaga. Passare invece da client.batch_write_item ti consegna l'intero loop, come nell'esempio Node.js.
  • Non può sollevare i limiti del servizio. 25 scritture per richiesta, 400 KB per item, 16 MB per richiesta, niente condizioni e niente aggiornamenti, e ogni put sostituisce l'intero item memorizzato. Ti serve una guardia, o tutto-o-niente? TransactWriteItems.

Cosa fa davvero batch_writer al flush

Metti in buffer 30 put e guarda le chiamate che effettua. Avvolgendo table.meta.client.batch_write_item e registrando le dimensioni delle richieste, contro DynamoDB Local 3.3.0:

batch sizes sent: [25, 5]

Due richieste, tagliate al limite del servizio, con il resto svuotato da __exit__. Quel flush è incondizionato: solleva un RuntimeError dentro il blocco e gli item in buffer vengono comunque scritti all'uscita. Un caricamento di massa che muore a metà lascia dietro di sé un caricamento parziale, non una tabula rasa.

Ora i due fallimenti. Metti in buffer due volte la stessa chiave, che è quello che succede nel momento in cui i tuoi dati di origine hanno una ripetizione:

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

Nessuno dei due put_item si è lamentato. batch_writer() non deduplica se non glielo chiedi, e chiederlo significa table.batch_writer(overwrite_by_pkeys=["Artist", "SongTitle"]). Fai passare le stesse due put da lì e l'item viene memorizzato come Year: 2 — il buffer tiene l'ultima scrittura per chiave, quindi la deduplicazione è perdita silenziosa di dati se le tue due righe dovevano essere item diversi sotto una chiave che hai sbagliato.

Il secondo è tutto di boto3 e non raggiunge mai DynamoDB:

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

Un Rating di 4.5 resta nel buffer senza proteste ed esplode al flush. Decimal("4.5") fa il round-trip correttamente come {"N": "4.5"}. Leggi un prezzo o una valutazione da JSON con json.loads e ogni numero è un float, quindi questo è un fallimento alla prima esecuzione per la maggior parte degli script di import. Passare parse_float=Decimal a json.loads lo risolve alla fonte.

Se stai passando a mano tra valori Python nativi e il formato di trasporto, il convertitore DynamoDB JSON mostra entrambi i lati dello stesso item, così vedi in cosa si trasforma davvero il tuo Decimal.

Per caricare in blocco da CSV o JSON senza scrivere tu la mappatura dei tipi, scarica DynoTable.

Esempi correlati

Riferimenti

Ultima verifica 2026-07-28 rispetto alla documentazione ufficiale AWS collegata sopra.

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.