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 adiadobatch.put_item() acrescenta a uma lista. Nada é validado, serializado ou enviado até o buffer chegar a 25 ou o bloco with terminar, então o traceback de um item ruim vem do flush e não da chamada put_item que 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; um float é aceito no buffer e rejeitado no flush.
  • batch_writer() é um método de Table. A contraparte do lado da leitura não é: batch_get_item vive no ServiceResource, e table.batch_get_item nã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; uma ValidationException se propaga. Ir por client.batch_write_item em 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 duplicates

Nenhum 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

Referências

Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.

Trabalhe com o DynamoDB sem o Console

Um cliente desktop rápido para DynamoDB que roda o SQL de verdade que o DynamoDB não consegue — JOINs, GROUP BY, agregações — com edição visual e um agente de IA com suas próprias chaves do Bedrock.

Teste grátis de 30 dias, sem cartão de crédito — depois o plano Grátis sem limite de tempo.