DynamoDB Batch Write en Python (boto3 batch_writer)

batch_writer() es la única llamada de DynamoDB en la que Python da menos trabajo que los demás SDK. Almacena en búfer las escrituras y los borrados, los trocea en peticiones BatchWriteItem de 25 y reenvía él mismo los Items no procesados. Lo que no hace es protegerte de los dos fallos que rompen la mayoría de las cargas masivas, y ambos afloran en el vaciado del búfer y no en la línea que suministró el Item defectuoso.

Código

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

Explicación

  • Vaciado diferidobatch.put_item() añade a una lista. No se valida, serializa ni envía nada hasta que el búfer llega a 25 o el bloque with termina, así que el traceback de un Item defectuoso viene del vaciado y no de la llamada put_item que lo suministró. Si cargas desde un iterador, lleva tu propio índice de lo que entró en el búfer.
  • Valores Python planos — esta es la API de recurso, así que escribes 1994, no {"N": "1994"}. Los Decimal son obligatorios para cualquier cosa fraccionaria; un float se acepta en el búfer y se rechaza al vaciarlo.
  • batch_writer() es un método de Table. Su contrapartida del lado de la lectura no lo es: batch_get_item vive en el ServiceResource, y table.batch_get_item no existe. No hay ningún ayudante de búfer, troceado ni reintento para las lecturas por lotes.
  • UnprocessedItems, no errores — ese es el único reintento que gestiona. Una escritura limitada se reenvía; una ValidationException se propaga. Ir por client.batch_write_item en su lugar te entrega el bucle entero, como en el ejemplo de Node.js.
  • No puede levantar los límites del servicio. 25 escrituras por petición, 400 KB por Item, 16 MB por petición, sin condiciones y sin actualizaciones, y cada put reemplaza el Item almacenado entero. ¿Necesitas una guarda, o todo o nada? TransactWriteItems.

Qué hace realmente batch_writer al vaciar

Mete 30 puts en el búfer y observa las llamadas que hace. Envolviendo table.meta.client.batch_write_item y registrando los tamaños de petición, contra DynamoDB Local 3.3.0:

batch sizes sent: [25, 5]

Dos peticiones, cortadas en el límite del servicio, con el resto vaciado por __exit__. Ese vaciado es incondicional: lanza un RuntimeError dentro del bloque y los Items en búfer se escriben igualmente al salir. Una carga masiva que muere a mitad de camino deja detrás una carga parcial, no borrón y cuenta nueva.

Ahora los dos fallos. Mete la misma clave dos veces en el búfer, que es lo que ocurre en cuanto tus datos de origen tienen una repetición:

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

Ninguno de los dos put_item protestó. batch_writer() no deduplica salvo que se lo pidas, y pedírselo es table.batch_writer(overwrite_by_pkeys=["Artist", "SongTitle"]). Pasa esos mismos dos puts por ahí y el Item se guarda como Year: 2 — el búfer conserva la última escritura por clave, así que la deduplicación es pérdida silenciosa de datos si tus dos filas debían ser Items distintos bajo una clave que escribiste mal.

El segundo es exclusivo de boto3 y nunca llega a DynamoDB:

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

Un Rating de 4.5 se queda en el búfer sin protestar y revienta al vaciarlo. Decimal("4.5") va y vuelve correctamente como {"N": "4.5"}. Lee un precio o una valoración de un JSON con json.loads y todos los números son float, así que este es un fallo de primera ejecución para la mayoría de los scripts de importación. Pasar parse_float=Decimal a json.loads lo arregla en el origen.

Si mueves a mano valores nativos de Python al formato de cable y viceversa, el conversor de DynamoDB JSON muestra ambos lados del mismo Item para que veas en qué se convierte de verdad tu Decimal.

Para cargar en bloque desde CSV o JSON sin escribir tú el mapeo de tipos, descarga DynoTable.

Ejemplos relacionados

Referencias

Verificado por última vez el 2026-07-28 contra la documentación oficial de AWS enlazada arriba.

Trabaja con DynamoDB sin la Consola

Un cliente de escritorio rápido para DynamoDB que ejecuta el SQL real que DynamoDB no puede — JOINs, GROUP BY, agregaciones — con edición visual y un agente de IA con tus propias claves de Bedrock.

Prueba gratuita de 30 días, sin tarjeta — después, el plan Free sin límite de tiempo.