Escrita em lote no DynamoDB com Python (boto3 batch_writer)
batch_writer() é a única chamada do DynamoDB em que o Python dá menos trabalho que os outros SDKs. Ele acumula puts e deletes em um buffer, corta tudo em requisições BatchWriteItem de 25 e reenvia sozinho os itens não processados. O que ele não faz é te proteger das duas falhas que quebram a maioria das cargas em massa, e ambas aparecem no flush, não na linha que forneceu o item ruim.
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")Explicação
- Flush adiado —
batch.put_item()acrescenta a uma lista. Nada é validado, serializado ou enviado até o buffer chegar a 25 ou o blocowithterminar, então o traceback de um item ruim vem do flush e não da chamadaput_itemque o forneceu. Se você está carregando a partir de um iterador, mantenha seu próprio índice do que entrou no buffer. - Valores Python puros — esta é a API de recurso, então você escreve
1994, e não{"N": "1994"}. Decimals são obrigatórios para qualquer coisa fracionária; umfloaté aceito no buffer e rejeitado no flush. batch_writer()é um método deTable. A contraparte do lado da leitura não é:batch_get_itemvive noServiceResource, etable.batch_get_itemnão existe. Não há nenhum auxiliar de buffer, divisão em blocos ou retry para leituras em lote.UnprocessedItems, não erros — esse é o único retry que ele trata. Uma escrita com throttle é reenviada; umaValidationExceptionse propaga. Ir porclient.batch_write_itemem vez disso te entrega o loop inteiro, como no exemplo em Node.js.- Ele não consegue levantar os limites do serviço. 25 escritas por requisição, 400 KB por item, 16 MB por requisição, sem condições e sem atualizações, e cada put substitui o item armazenado inteiro. Precisa de uma guarda, ou de tudo-ou-nada? TransactWriteItems.
O que o batch_writer realmente faz no flush
Coloque 30 puts no buffer e observe as chamadas que ele faz. Envolvendo table.meta.client.batch_write_item e registrando os tamanhos das requisições, contra o DynamoDB Local 3.3.0:
batch sizes sent: [25, 5]Duas requisições, cortadas no limite do serviço, com o restante descarregado pelo __exit__. Esse flush é incondicional: lance um RuntimeError dentro do bloco e os itens em buffer são escritos mesmo assim na saída. Uma carga em massa que morre no meio do caminho deixa para trás uma carga parcial, não a estaca zero.
Agora as duas falhas. Coloque a mesma chave duas vezes no buffer, que é o que acontece assim que seus dados de origem têm uma repetição:
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 duplicatesNenhum dos dois put_item reclamou. batch_writer() não deduplica a menos que você peça, e pedir é table.batch_writer(overwrite_by_pkeys=["Artist", "SongTitle"]). Passe esses mesmos dois puts por ali e o item é gravado como Year: 2 — o buffer guarda a última escrita por chave, então a deduplicação é perda silenciosa de dados se suas duas linhas deveriam ser itens diferentes sob uma chave que você errou.
A segunda é exclusiva do boto3 e nunca chega ao DynamoDB:
TypeError: Float types are not supported. Use Decimal types instead.Um Rating de 4.5 fica no buffer sem reclamar e explode no flush. Decimal("4.5") vai e volta corretamente como {"N": "4.5"}. Leia um preço ou uma avaliação de um JSON com json.loads e todo número é um float, então essa é uma falha de primeira execução para a maioria dos scripts de importação. Passar parse_float=Decimal para json.loads resolve na origem.
Se você está convertendo à mão entre valores nativos do Python e o formato de transporte, o conversor de DynamoDB JSON mostra os dois lados do mesmo item para você ver no que seu Decimal realmente se transforma.
Para carregar em massa a partir de CSV ou JSON sem escrever você mesmo o mapeamento de tipos, baixe o DynoTable.
Exemplos relacionados
- DynamoDB BatchWriteItem em Node.js — o loop de retry manual que o batch_writer esconde.
- DynamoDB BatchWriteItem com a AWS CLI — a mesma escrita em lote pelo shell.
- DynamoDB PutItem em Python — a escrita de item único que isto agrupa.
- Operações em lote no DynamoDB — limites, falha parcial e quando o lote compensa.
- "Too many items requested for the BatchWriteItem call" — mais de 25 requisições put/delete em um lote.
- "Provided list of item keys contains duplicates" — duas requisições tocando a mesma chave em um lote.
Referências
- Amazon DynamoDB guide (batch_writer) — Boto3 documentation
- BatchWriteItem — Amazon DynamoDB API Reference
- Error handling with DynamoDB — Amazon DynamoDB Developer Guide
Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.