DynamoDB — Estudo
Aprofundado
Do modelo de itens e atributos à arquitetura interna de particionamento, passando por modelagem orientada a
access patterns, índices secundários, transações e operação em produção. Cada tópico traz teoria, exemplo
prático em AWS CLI ou boto3, um caso de uso do dia a dia
e link para a documentação oficial da AWS.
aws configureFundamentos
O modelo de dados chave-valor/documento do DynamoDB
Uma tabela é uma coleção de itens, e cada item é uma coleção de atributos (pares nome-valor) — parecido com uma linha numa tabela relacional, mas sem colunas fixas. Além dos atributos que formam a chave primária, cada item pode ter um conjunto totalmente diferente de atributos dos demais itens da mesma tabela.
Usuarios; um usuário com endereço cadastrado tem um atributo endereco a mais que outro que nunca cadastrou, sem precisar de coluna nula em massa.# Criar uma tabela simples (chave primária = apenas partition key)
aws dynamodb create-table \
--table-name Usuarios \
--attribute-definitions AttributeName=userId,AttributeType=S \
--key-schema AttributeName=userId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
Uma chave primária simples usa só a partition key — cada valor precisa ser único na tabela. Uma chave primária composta usa partition key + sort key — vários itens podem compartilhar a mesma partition key, desde que o sort key seja diferente entre eles, e ficam ordenados pelo sort key.
Query em vez de Scan.clienteId como partition key e pedidoId como sort key permite buscar "todos os pedidos do cliente X" com uma única Query eficiente, sem tocar nos pedidos de outros clientes.# Criar tabela com chave primária composta
aws dynamodb create-table \
--table-name Pedidos \
--attribute-definitions \
AttributeName=clienteId,AttributeType=S \
AttributeName=pedidoId,AttributeType=S \
--key-schema \
AttributeName=clienteId,KeyType=HASH \
AttributeName=pedidoId,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
O DynamoDB tem três famílias de tipos: escalares (String, Number, Binary, Boolean, Null), documento (List e Map, que permitem aninhamento como um JSON) e conjunto (String Set, Number Set, Binary Set — valores únicos do mesmo tipo escalar).
Number não é um float de ponto flutuante — é armazenado internamente como uma string decimal de até 38 dígitos de precisão, o que evita os erros de arredondamento comuns em ponto flutuante binário. Isso importa em código que serializa itens para JSON puro: a API de baixo nível representa cada valor com um descritor de tipo ({"S": "..."}, {"N": "..."}), enquanto os SDKs de alto nível (como o DynamoDB.DocumentClient ou o resource do boto3) escondem essa representação e trabalham com tipos nativos da linguagem.L) de Maps (M), cada um com produtoId, quantidade e preco — tudo dentro de um único item, sem tabela separada de "itens do carrinho".{
"userId": { "S": "u-123" },
"idade": { "N": "29" },
"ativo": { "BOOL": true },
"tags": { "SS": ["vip", "beta"] },
"carrinho": { "L": [
{ "M": { "produtoId": {"S":"p-1"}, "quantidade": {"N":"2"} } }
]}
}
As quatro operações item-level são PutItem, GetItem,
UpdateItem e DeleteItem — todas identificam o item pela
chave primária completa (partition key, ou partition key + sort key).
UpdateItem usa update expressions (SET, REMOVE, ADD, DELETE) para modificar atributos específicos in-place, sem reenviar o item inteiro — o que também permite operações atômicas como incrementar um contador (ADD contador :um) sem race condition de leitura-e-escrita.PutItem substitui o item inteiro se já existir um com a mesma chave — atributos que não forem enviados somem. Use UpdateItem para alterar campos pontuais, ou uma ConditionExpression em PutItem para evitar sobrescrever sem querer.# Create aws dynamodb put-item --table-name Pedidos --item \ '{"clienteId":{"S":"c-1"},"pedidoId":{"S":"p-1"},"total":{"N":"149.90"},"status":{"S":"pendente"}}' # Read aws dynamodb get-item --table-name Pedidos \ --key '{"clienteId":{"S":"c-1"},"pedidoId":{"S":"p-1"}}' # Update — expression, não substituição total aws dynamodb update-item --table-name Pedidos \ --key '{"clienteId":{"S":"c-1"},"pedidoId":{"S":"p-1"}}' \ --update-expression "SET #s = :novo" \ --expression-attribute-names '{"#s":"status"}' \ --expression-attribute-values '{":novo":{"S":"enviado"}}' # Delete aws dynamodb delete-item --table-name Pedidos \ --key '{"clienteId":{"S":"c-1"},"pedidoId":{"S":"p-1"}}'
Arquitetura Interna
Como o DynamoDB particiona, escala e replica dados
O DynamoDB distribui automaticamente os dados e o tráfego de uma tabela entre múltiplas partições físicas, usando uma função de hash interna sobre o valor da partition key para decidir em qual partição cada item vive.
ProvisionedThroughputExceededException concentrados, mesmo com capacidade agregada sobrando na tabela, são o sintoma clássico de hot partition — o CloudWatch expõe métricas por partição para diagnosticar isso.No modo Provisioned, você define quanta capacidade de leitura (RCU) e escrita (WCU) a tabela terá, opcionalmente com auto scaling. No modo On-Demand, a capacidade escala automaticamente com a carga e a cobrança é por unidade de requisição realmente consumida.
# On-Demand aws dynamodb update-table --table-name Pedidos --billing-mode PAY_PER_REQUEST # Provisioned, com capacidade explícita aws dynamodb update-table --table-name Pedidos --billing-mode PROVISIONED \ --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5
Por padrão, uma leitura no DynamoDB é eventualmente consistente — pode, raramente, não refletir uma escrita concluída um instante antes. Uma leitura fortemente consistente (opt-in) garante o dado mais atualizado, ao custo de mais capacidade e latência.
aws dynamodb get-item --table-name Contas \
--key '{"contaId":{"S":"c-1"}}' \
--consistent-read
Toda tabela do DynamoDB replica automaticamente seus dados em múltiplas zonas de disponibilidade (AZs) dentro da região, sem qualquer configuração — é o comportamento padrão do serviço. Para replicação entre regiões, existe o recurso Global Tables.
# Adicionar uma réplica em outra região a uma tabela existente aws dynamodb update-table \ --table-name Usuarios \ --replica-updates '[{"Create":{"RegionName":"us-east-1"}}]'
Modelagem & Acesso a Dados
Query, índices e o padrão single-table
Query busca itens por uma partition key específica (e, opcionalmente, uma condição sobre o sort key) — é eficiente porque acessa diretamente a partição correta. Scan percorre a tabela (ou índice) inteira, item por item, e só depois aplica qualquer filtro.
FilterExpression em Query ou Scan é aplicado depois que os dados já foram lidos — ele reduz o que é devolvido para a aplicação, mas não reduz a capacidade (RCU) consumida na leitura. Um Scan com filtro que descarta 99% dos itens ainda cobra pela leitura dos 100%.# Query — eficiente, usa a partition key resp = table.query( KeyConditionExpression=Key("clienteId").eq("c-1") ) # Scan — lê a tabela inteira, filtra depois resp = table.scan( FilterExpression=Attr("status").eq("pendente") )
Um Global Secondary Index (GSI) tem sua própria partition key (e sort key opcional), diferente da tabela base, e pode ser criado ou removido a qualquer momento. Um Local Secondary Index (LSI) mantém a mesma partition key da tabela base, mas com um sort key alternativo — e só pode ser definido na criação da tabela.
Pedidos (chave por cliente) ganha um GSI com partition key status, permitindo consultar "todos os pedidos pendentes do sistema inteiro" — algo que a chave primária original não permite fazer sem um Scan.# Adicionar um GSI a uma tabela existente
aws dynamodb update-table --table-name Pedidos \
--attribute-definitions AttributeName=status,AttributeType=S \
--global-secondary-index-updates \
'[{
"Create": {
"IndexName": "status-index",
"KeySchema": [{"AttributeName":"status","KeyType":"HASH"}],
"Projection": {"ProjectionType":"ALL"}
}
}]'
Single-table design é o padrão de modelar múltiplos tipos de entidade (usuários, pedidos, produtos)
numa única tabela, usando nomes de chave genéricos (tipicamente PK e SK)
"sobrecarregados" com prefixos que identificam o tipo de item — permitindo satisfazer vários access patterns
com o menor número possível de requisições.
PK=USER#123, SK=PROFILE guarda o perfil do usuário e PK=USER#123, SK=ORDER#456 guarda um pedido dele — uma única Query com PK=USER#123 traz o perfil e todos os pedidos numa só chamada.// Perfil do usuário { PK: "USER#123", SK: "PROFILE", nome: "Ana", email: "ana@ex.com" } // Pedido do mesmo usuário — mesma partition key, sort key diferente { PK: "USER#123", SK: "ORDER#456", total: 149.90, status: "enviado" }
O DynamoDB Streams, quando habilitado numa tabela, captura em ordem cada modificação item-level
(INSERT, MODIFY, REMOVE),
retida por 24 horas — funcionando como um log de mudanças que outras partes do sistema podem consumir.
# Habilitar Streams numa tabela (imagem nova + antiga do item)
aws dynamodb update-table --table-name Produtos \
--stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES
O TTL (Time to Live) usa um atributo do tipo Number, guardando um timestamp Unix (epoch, em segundos), para marcar quando um item deve expirar. O DynamoDB remove itens expirados automaticamente em segundo plano.
expiraEm = agora + 86400 num atributo TTL, e o DynamoDB limpa as sessões vencidas sozinho, sem um job agendado de limpeza.# Habilitar TTL apontando para o atributo de expiração
aws dynamodb update-time-to-live --table-name Sessoes \
--time-to-live-specification Enabled=true,AttributeName=expiraEm
Operação, Segurança & Casos de Uso
Transações, cache, backup, custo e quando usar
Uma escrita num único item já é atômica por padrão no DynamoDB. Quando a operação precisa alterar
múltiplos itens (na mesma tabela ou em tabelas diferentes) de forma tudo-ou-nada, existem as
transações explícitas TransactWriteItems (escrita) e TransactGetItems (leitura).
# Transferência atômica entre duas contas
aws dynamodb transact-write-items --transact-items '[
{
"Update": {
"TableName": "Contas",
"Key": {"contaId": {"S": "A"}},
"UpdateExpression": "SET saldo = saldo - :v",
"ExpressionAttributeValues": {":v": {"N": "100"}}
}
},
{
"Update": {
"TableName": "Contas",
"Key": {"contaId": {"S": "B"}},
"UpdateExpression": "SET saldo = saldo + :v",
"ExpressionAttributeValues": {":v": {"N": "100"}}
}
}
]'
O DynamoDB Accelerator (DAX) é um cluster de cache em memória totalmente gerenciado, compatível com a API do DynamoDB — trocar chamadas normais pelo client do DAX exige mudança mínima de código, e reduz leituras de latência de milissegundos para microssegundos em cargas de leitura intensiva.
Um backup sob demanda é uma cópia completa da tabela, feita a qualquer momento, sem impacto na performance de leitura/escrita, e retida até ser excluída manualmente. O Point-in-Time Recovery (PITR) mantém backups contínuos, permitindo restaurar a tabela para qualquer segundo dos últimos 35 dias.
# Habilitar PITR aws dynamodb update-continuous-backups --table-name Pedidos \ --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true # Restaurar para um instante específico (cria tabela nova) aws dynamodb restore-table-to-point-in-time \ --source-table-name Pedidos \ --target-table-name Pedidos-restaurada \ --restore-date-time 2026-08-20T03:00:00Z
O acesso ao DynamoDB é controlado por políticas IAM, que podem ser tão granulares quanto restringir um usuário a enxergar apenas itens ou atributos específicos. Dados ficam criptografados em repouso por padrão e em trânsito via TLS.
dynamodb:LeadingKeys (restringe por valor de partition key) e dynamodb:Attributes (restringe quais atributos um item pode expor).dynamodb:LeadingKeys numa policy IAM para garantir que cada usuário autenticado só consiga ler/escrever itens cuja partition key comece com o próprio ID dele, mesmo que todos compartilhem a mesma tabela.{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:*:*:table/Usuarios",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${aws:userid}"]
}
}
}
No modo Provisioned, a cobrança é por RCU/WCU provisionada por hora (com desconto possível via auto scaling que ajusta a capacidade à demanda real). No modo On-Demand, a cobrança é por milhão de unidades de leitura e escrita efetivamente consumidas, sem provisionamento prévio.
DynamoDB brilha quando os access patterns são conhecidos e estáveis e a aplicação precisa de latência de milissegundos previsível em qualquer escala. Ele não substitui um banco relacional ou um data warehouse para todos os casos.
Modelagem de Dados & Performance
Técnicas avançadas de modelagem e um roteiro prático de otimização
Um item só entra num Global Secondary Index se ele tiver todos os atributos que compõem a chave do índice. Um sparse index aproveita isso de propósito: criando um GSI cuja partition key é um atributo que só existe em alguns itens da tabela, o índice acaba contendo só o subconjunto relevante.
status como chave contendo todos os pedidos (pendentes, enviados, entregues) — um índice grande, a maior parte inútil para essa consulta. Adicionando um atributo separado, como pendenteFlag, que só é gravado quando status = "pendente" (e removido com REMOVE quando o pedido muda de status), o GSI sobre esse atributo fica pequeno e barato, contendo só o que interessa.aguardandoTriagem — só os tickets realmente pendentes aparecem no índice, mesmo numa tabela com milhões de tickets já resolvidos.# O atributo só é gravado quando o pedido está pendente aws dynamodb update-item --table-name Pedidos \ --key '{"clienteId":{"S":"c-1"},"pedidoId":{"S":"p-1"}}' \ --update-expression "SET pendenteFlag = :p" \ --expression-attribute-values '{":p":{"S":"1"}}' # Ao sair do estado pendente, remove o atributo — o item some do índice aws dynamodb update-item --table-name Pedidos \ --key '{"clienteId":{"S":"c-1"},"pedidoId":{"S":"p-1"}}' \ --update-expression "REMOVE pendenteFlag"
Quando um único valor de partition key recebe escrita desproporcional (um contador global, um item viral), a técnica de write sharding anexa um sufixo (aleatório ou calculado) à chave, espalhando as escritas entre várias partições internas em vez de concentrar tudo numa só.
BatchGetItem, por exemplo) em vez de um único GetItem. Essa técnica só vale a pena quando o padrão de escrita realmente teria uma hot partition sem ela — aplicar sharding numa chave que já é bem distribuída só adiciona complexidade sem ganho.video#123#0 … video#123#9); cada visualização incrementa um shard escolhido aleatoriamente, e o total exibido na tela soma os 10 valores — que pode até ser cacheado por alguns segundos para não somar a cada exibição.# Escrita — sufixo aleatório entre 0 e 9 shard = random.randint(0, 9) table.update_item( Key={"PK": f"video#123#{shard}"}, UpdateExpression="ADD visualizacoes :um", ExpressionAttributeValues={":um": 1} ) # Leitura — agrega os 10 shards chaves = [{"PK": f"video#123#{i}"} for i in range(10)] resp = dynamodb.batch_get_item(RequestItems={"Videos": {"Keys": chaves}}) total = sum(item["visualizacoes"] for item in resp["Responses"]["Videos"])
O Adaptive Capacity é um mecanismo interno, sempre ativo, que redistribui automaticamente capacidade não utilizada de partições "frias" para partições "quentes" da mesma tabela, sem qualquer configuração da sua parte.
Ao criar um GSI/LSI, a projeção do índice (KEYS_ONLY,
INCLUDE ou ALL) define quais atributos são copiados da
tabela base para o índice. Já o ProjectionExpression é um parâmetro passado numa chamada
de leitura para escolher quais atributos retornar na resposta.
ProjectionExpression reduz o volume de dados trafegado de volta para a aplicação, mas a capacidade consumida (RCU) é calculada pelo tamanho do item lido internamente, antes da projeção ser aplicada — não economiza RCU. Já a projeção do índice em KEYS_ONLY ou INCLUDE reduz de fato o que é armazenado e lido do índice, porque atributos fora da projeção simplesmente não existem ali — essa é a alavanca real de economia quando o índice só precisa responder "quais chaves existem" ou alguns poucos atributos extras.# Índice enxuto — só guarda as chaves, mais barato de manter e consultar aws dynamodb update-table --table-name Pedidos \ --attribute-definitions AttributeName=status,AttributeType=S \ --global-secondary-index-updates \ '[{ "Create": { "IndexName": "status-index-leve", "KeySchema": [{"AttributeName":"status","KeyType":"HASH"}], "Projection": {"ProjectionType":"KEYS_ONLY"} } }]' # ProjectionExpression — reduz o payload retornado, não o RCU consumido aws dynamodb query --table-name Pedidos \ --key-condition-expression "clienteId = :c" \ --expression-attribute-values '{":c":{"S":"c-1"}}' \ --projection-expression "pedidoId, status"
BatchGetItem busca até 100 itens (por chave conhecida) e BatchWriteItem
grava/remove até 25 itens (até 16 MB) numa única chamada — reduzindo o número de round-trips de rede em
comparação com chamar GetItem/PutItem item a item.
TransactWriteItems, uma operação em lote não é atômica: alguns itens podem ter sucesso e outros falhar (por throttling, por exemplo), retornando em UnprocessedItems (escrita) ou UnprocessedKeys (leitura). A aplicação precisa reenviar esses itens, idealmente com backoff exponencial, até a lista vir vazia.UnprocessedItems é um bug silencioso comum: a chamada retorna sucesso (HTTP 200), mas parte dos itens pode não ter sido gravada.# Grava em lote e reprocessa o que falhar, com backoff resp = dynamodb.batch_write_item(RequestItems={"Produtos": itens_para_gravar}) pendentes = resp.get("UnprocessedItems", {}) espera = 0.1 while pendentes: time.sleep(espera) resp = dynamodb.batch_write_item(RequestItems=pendentes) pendentes = resp.get("UnprocessedItems", {}) espera = min(espera * 2, 5)
Toda resposta de Query ou Scan é limitada a 1 MB
de dados lidos. Quando há mais resultados, a resposta traz um LastEvaluatedKey
que deve ser passado como ExclusiveStartKey na próxima chamada para continuar de onde parou.
TotalSegments) e processa cada um em paralelo com workers separados, cada um informando seu Segment. Isso acelera um Scan completo (útil em exportações e migrações pontuais), mas consome capacidade de forma agressiva e simultânea — rodar em produção sem throttling do lado da aplicação pode competir diretamente com o tráfego real e causar throttling generalizado na tabela.# Paginação simples chave = None while True: kwargs = {"ExclusiveStartKey": chave} if chave else {} resp = table.scan(**kwargs) processa(resp["Items"]) chave = resp.get("LastEvaluatedKey") if not chave: break # Parallel Scan — 4 segmentos processados de forma independente resp = table.scan(Segment=0, TotalSegments=4)
Como o consumo de RCU/WCU é calculado com base no tamanho do item (arredondado para cima em blocos de 4 KB ou 1 KB), reduzir o tamanho médio dos itens reduz custo e aumenta o throughput possível por partição.
n em vez de nomeCompleto) é uma otimização real, embora custe legibilidade. Para campos grandes de texto ou JSON que não precisam ser consultados diretamente, comprimir (gzip) antes de armazenar como Binary reduz o tamanho do item — ao custo de não conseguir mais filtrar ou fazer FilterExpression sobre esse campo sem descomprimir primeiro.--return-consumed-capacity TOTAL (CLI) ou ReturnConsumedCapacity="TOTAL" (SDK) durante o desenvolvimento para medir o custo real de cada operação antes de otimizar às cegas.
O DynamoDB publica métricas no CloudWatch automaticamente — consumo de capacidade
(ConsumedReadCapacityUnits/ConsumedWriteCapacityUnits),
requisições throttled (ThrottledRequests) e latência (SuccessfulRequestLatency).
ThrottledRequests no CloudWatch; ao confirmar throttling, o Contributor Insights aponta exatamente qual cliente ou produto está concentrando as chamadas, direcionando a correção (write sharding, cache, ou revisão da partition key).# Habilitar Contributor Insights numa tabela aws dynamodb enable-contributor-insights --table-name Pedidos # Consultar requisições throttled na última hora aws cloudwatch get-metric-statistics \ --namespace AWS/DynamoDB --metric-name ThrottledRequests \ --dimensions Name=TableName,Value=Pedidos \ --start-time $(date -u -d '-1 hour' +%FT%TZ) --end-time $(date -u +%FT%TZ) \ --period 300 --statistics Sum
Junta os tópicos das cinco fases num roteiro prático — a ordem importa: modelagem correta evita boa parte dos problemas de performance antes mesmo de precisar de otimização fina.
ReturnConsumedCapacity e reduza o tamanho médio dos itens onde fizer diferença real