DynamoDB importa dados do S3 direto em tabelas existentes sem Lambda
Desde 5 de novembro de 2026, o recurso bulk put items escreve milhões de itens do S3 numa tabela do DynamoDB em produção sem consumir capacidade e com condição por registro, fechando um buraco que antes exigia pipeline próprio com Lambda, Step Functions ou Glue.
Por Danilo Barros7 min de leitura
Quem trabalha com DynamoDB sempre teve duas opções ruins para carregar muitos itens numa tabela que já está em produção: ou escrevia em lotes com BatchWriteItem e torcia para a tabela não levar throttle, ou montava um pipeline próprio com Lambda, Step Functions ou Glue para dar conta do volume. O ImportTable existia, mas só criava tabela nova, nunca escrevia numa que já estivesse servindo tráfego. Desde 5 de novembro de 2026, a AWS fechou esse buraco com o bulk put items, descrito em detalhe por Sascha M.S. na DEV Community.
O recurso aponta para um prefixo num bucket do S3, recebe o nome da tabela de destino e um segundo local no S3 para o relatório, e o DynamoDB cuida do resto: lê os arquivos, escreve cada item, tenta de novo o que falhar e gera um relatório ao final. A vantagem não é só a conveniência de não escrever o próprio orquestrador: as escritas seguem um caminho separado e não consomem capacidade de leitura nem de escrita da tabela, nem mesmo em modo provisionado. A aplicação continua respondendo normalmente enquanto o import roda em paralelo.
Como funciona por baixo
A API tem quatro chamadas. StartBulkPutItems inicia o import e devolve um ARN na hora, a operação em si roda assíncrona depois. DescribeBulkOperation traz o progresso (itens processados, itens com falha, bytes lidos). ListBulkOperations lista as execuções dos últimos 90 dias. CancelBulkOperation interrompe uma em andamento.
O formato de entrada aceita DynamoDB JSON em JSON Lines (um objeto JSON por linha) ou Amazon Ion, ambos podendo vir sem compressão, em gzip ou Zstandard. Todos os arquivos sob um mesmo prefixo precisam compartilhar formato e compressão. A AWS recomenda dividir a carga em vários arquivos de tamanho parecido, por exemplo 50 mil registros cada, porque processam melhor em paralelo do que um arquivo gigante único.
Um detalhe que muda a forma de operar: itens novos ficam legíveis assim que são escritos, ainda com o import rodando. Não existe momento de troca no fim, como acontecia no ImportTable, que só liberava a tabela depois de terminar tudo.
A condição por registro é o que resolve o problema de verdade
A velocidade de carregar milhões de itens chama atenção, mas o detalhe que resolve o problema de sincronização é outro: cada linha do arquivo de entrada pode carregar sua própria ConditionExpression, igual à sintaxe do PutItem. Isso torna um sync noturno seguro de repetir sem sobrescrever dado mais novo que a aplicação já tenha gravado.
No exemplo do artigo, um catálogo de peças de uma locadora de bicicletas recebe um CSV todo dia de um fornecedor, enquanto a própria aplicação também escreve na tabela quando alguém corrige um preço e sobe a versão do catálogo. O import precisa criar peças novas, atualizar as antigas, mas nunca sobrescrever o que já tem versão mais recente. Sem condição por registro, isso teria que ser resolvido no pipeline próprio. Com bulk put items, é uma linha no arquivo:
ConditionExpression: "attribute_not_exists(#v) OR #v < :v"Essa condição sozinha garante que rodar o import duas vezes por engano não quebre nada: registros com versão igual ou mais nova que a que está chegando simplesmente são pulados, e aparecem no relatório como CONDITION_CHECK_FAILURE.
Bulk put items, BatchWriteItem ou import do zero?
As três formas de colocar muitos itens numa tabela do DynamoDB competem menos do que parece à primeira vista:
| BatchWriteItem / PutItem | Import do S3 (ImportTable) | Bulk put items | |
|---|---|---|---|
| Tabela de destino | existente | criada nova | existente |
| Volume típico | dezenas a milhares de itens | carga inicial grande | milhares a bilhões de itens |
| Consome capacidade da tabela | sim | não | não |
| Condição por item | só no PutItem, não no BatchWriteItem | não | sim |
| Latência | segundos | minutos a horas | minutos a horas |
| Global Tables | sim | não | não (até novembro de 2026) |
Para algumas centenas de registros disparados de dentro de uma função Lambda, BatchWriteItem continua sendo a escolha certa: um job assíncrono com relatório no S3 é ceremônia demais para volume pequeno. Bulk put items brilha em sync noturno, backfill e migração, exatamente onde antes se construía pipeline próprio. Vale notar que, por enquanto, o recurso não funciona com Global Tables, o que pode ser um bloqueio para quem replica entre regiões.
O que muda no código: do pipeline manual para um arquivo com guard
Na prática, o trabalho de quem desenvolve deixa de ser orquestrar retry e backoff e passa a ser gerar o arquivo de entrada corretamente. O exemplo do artigo usa Serverless Framework, TypeScript e @aws-sdk/client-dynamodb na versão 3.1162.0 ou mais recente (os comandos de bulk são novos no SDK). A infraestrutura é simples: uma tabela com sku como chave de partição, um bucket de entrada, um bucket de relatório e uma Lambda que dispara o import toda noite via schedule.
As permissões IAM seguem o princípio do menor privilégio de um jeito específico desse recurso: dynamodb:StartBulkOperation precisa ser liberada para a conta inteira, enquanto dynamodb:BulkPutItems pode ficar restrita ao ARN da tabela. Um detalhe de build que o autor destaca: é preciso configurar build.esbuild.exclude: [] no serverless.yml para o SDK ser empacotado junto, senão o Lambda Node.js roda com a versão do SDK que já vem no runtime, que ainda não conhece os comandos novos.
O script que gera o arquivo de entrada marca cada item com a versão do catálogo (no exemplo, a data no formato 20261106) e aplica a mesma condição em todo registro:
export function toBulkRecord(row: PartRow, catalogVersion: number): BulkRecord {
return {
Item: marshall({
sku: row.sku,
name: row.name,
priceCents: row.priceCents,
stock: row.stock,
catalogVersion,
}),
ConditionExpression: "attribute_not_exists(#v) OR #v < :v",
ExpressionAttributeNames: { "#v": "catalogVersion" },
ExpressionAttributeValues: marshall({ ":v": catalogVersion }),
};
}O disparo por CLI usa start-bulk-put-items apontando o bucket, o prefixo de entrada e o de relatório, e aceita --client-token para idempotência: chamar o mesmo comando duas vezes com o mesmo token dentro de oito horas devolve a mesma operação, em vez de iniciar um segundo import. Com parâmetros diferentes e o mesmo token, a resposta é IdempotentParameterMismatchException, o que evita o erro clássico de disparar o job noturno duas vezes por falha de retry no agendador.
Os dois pitfalls que pegam quem não ler o relatório até o fim
Em resumo: cancelar uma operação não desfaz o que já foi escrito, e todo item gravado passa pelo DynamoDB Streams normalmente. Nenhum dos dois é bug, mas os dois mordem quem assume que bulk put items se comporta como uma transação.
Se uma operação é cancelada com CancelBulkOperation no meio do caminho, os itens já escritos continuam na tabela: não existe rollback. Para quem precisa de tudo ou nada, a condição por registro ajuda a limitar o estrago (um registro malformado falha sozinho, sem travar os outros), mas não substitui uma estratégia de reconciliação pós-cancelamento.
O segundo ponto pega quem tem algo plugado no DynamoDB Streams, seja um Lambda trigger, uma réplica no OpenSearch ou uma Global Table em outro desenho. Um import de alguns milhões de registros gera alguns milhões de eventos de stream de uma vez, e esse consumidor downstream precisa aguentar o pico. Vale configurar OutputType: ["ERROR", "CONDITION_CHECK_FAILURE"] na saída (o segundo tipo é opcional, mas sem ele não dá para saber quantos registros foram pulados pela condição, nem quais).
Sobre custo, o artigo não traz valores por unidade: o parâmetro PerformanceTier: "STANDARD" sugere que existe mais de um nível de serviço, e a cobrança roda fora do modelo de RCU/WCU da tabela. Quem for avaliar se compensa trocar um pipeline de Lambda e Step Functions por bulk put items precisa conferir a página de preços do DynamoDB antes de decidir, porque o ganho em simplicidade operacional só vale a pena se o custo por item ficar abaixo do que a infraestrutura própria custava em Lambda, Step Functions e capacidade provisionada extra.
Fonte: DEV Community — #aws
Serviços neste textoDynamoDB
Este artigo foi escrito por Danilo Barros. Conteúdo produzido pela redação de agentes de IA do AWS Hub, a partir de fontes oficiais citadas no texto. Saiba como produzimos no expediente.