DynamoDB: single-table design
- ⬜🔴 Redis além de cache: streams, pub/sub, scripts(NoSQL + Vector Databases)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
DynamoDB pensa ao contrário
Em Postgres você modela entidades e descobre as queries depois. Em DynamoDB você lista todas as access patterns primeiro e modela as chaves para que cada padrão seja Query (nunca Scan). Isso exige disciplina e quebra a intuição relacional.
Se você não sabe listar os 5-10 access patterns do seu domínio antes de criar a tabela, ainda não está pronto para DynamoDB. Volte ao whiteboard.
Single-table design: exemplo real
Domínio: e-commerce com User, Order e OrderItem. Access patterns: (1) perfil do user, (2) pedidos do user ordenados por data, (3) itens de um pedido, (4) login por email.
- → se a chave concentra
- → sem índice, vira
- Banco de dados
- Conceito de arquitetura
O desenho mostra quatro itens de tipos diferentes na mesma partição — o que num banco relacional seriam quatro tabelas. Essa é a inversão: a estrutura física serve a lista de consultas, e por isso a lista precisa existir antes da tabela.
- 1 · A lista de consultas vem primeiro. No banco relacional você modela as entidades e depois pergunta o que quiser. Aqui é o inverso: escreve-se a lista de acessos e a chave é desenhada para atendê-la. Modelar antes de saber as consultas é o erro estrutural.
- 2 · Entidades diferentes dividem a partição. Perfil e pedidos do mesmo usuário compartilham a chave de partição e se distinguem pela de ordenação. É isso que permite trazer tudo numa leitura, sem junção — que o banco não oferece.
- 3 · O prefixo da chave de ordenação vira filtro. Como os pedidos são ordenados pela chave que começa com a data, pedir um intervalo é pedir um prefixo. Ordem é decisão de modelagem, não índice adicional.
- 4 · O acesso invertido precisa de índice. Chegar do pedido ao usuário não é possível na tabela base — a chave de partição é o usuário. O índice secundário reindexa os mesmos itens com as chaves trocadas.
- 5 · Chave sem variedade concentra tráfego. Usar o dia como chave de partição joga todo o tráfego de hoje numa partição só, enquanto as outras ficam ociosas. A capacidade total parece suficiente e as requisições são recusadas mesmo assim.
- 6 · Consulta não prevista custa varredura. É o sinal de que o modelo não cobre um acesso real: a alternativa é varrer a tabela inteira, que é caro e lento. Aparecendo com frequência, é indício de que o problema pedia um banco relacional.
// Mesma tabela "App", PK e SK sinteticos
// User profile
{ "PK": "USER#42", "SK": "PROFILE", "email": "fe@x.com", "name": "Fernando" }
// Pedidos do user (ordenados por data via SK)
{ "PK": "USER#42", "SK": "ORDER#2026-04-19#o123", "total": 199.0, "status": "paid" }
{ "PK": "USER#42", "SK": "ORDER#2026-04-20#o124", "total": 89.5, "status": "paid" }
// Itens do pedido (PK = ORDER#id)
{ "PK": "ORDER#o123", "SK": "ITEM#sku-001", "qty": 2, "price": 49.9 }
{ "PK": "ORDER#o123", "SK": "ITEM#sku-042", "qty": 1, "price": 99.2 }
// GSI por email (GSI1PK = EMAIL#...)
{ "PK": "USER#42", "SK": "PROFILE", "GSI1PK": "EMAIL#fe@x.com", "GSI1SK": "USER#42" }Queries correspondentes
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb';
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
// 1) Perfil + ultimos 10 pedidos em UMA query
const r = await ddb.send(new QueryCommand({
TableName: 'App',
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :sk)',
ExpressionAttributeValues: {
':pk': 'USER#42',
':sk': 'ORDER#'
},
ScanIndexForward: false, // mais recentes primeiro
Limit: 10
}));
// 2) Login por email (usa GSI1)
const login = await ddb.send(new QueryCommand({
TableName: 'App',
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :e',
ExpressionAttributeValues: { ':e': 'EMAIL#fe@x.com' }
}));
// 3) Itens do pedido o123
const items = await ddb.send(new QueryCommand({
TableName: 'App',
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :it)',
ExpressionAttributeValues: { ':pk': 'ORDER#o123', ':it': 'ITEM#' }
}));Por que esse tipo de banco exige modelar a partir das consultas?
Hot partition: o assassino silencioso
DynamoDB particiona por hash(PK). Limite físico: ~3000 RCU / 1000 WCU por partição. Se sua PK tem baixa cardinalidade (status, tenant_id de cliente gigante), você sofre throttling mesmo pagando capacity alta. Solução: write sharding — adicione sufixo aleatório (status#0..status#9) e faça fan-out na leitura.
// Antes (hot)
PK = 'STATUS#active'
// Depois (sharded em 10 buckets)
PK = 'STATUS#active#' + (hash(itemId) % 10)
// Leitura: faz 10 queries em paralelo e merge
const buckets = await Promise.all(
Array.from({ length: 10 }, (_, i) => query('STATUS#active#' + i))
);Quando NÃO usar DynamoDB
Access patterns evolutivos (startup fase 0): você não sabe como vai consultar. Relatórios ad-hoc. Analytics agregando tabela inteira. Joins frequentes entre entidades não relacionadas. Nesses casos, Postgres ganha. DynamoDB brilha quando a escala é massiva e o acesso é previsível.
DynamoDB bem modelado entrega latência p99 < 10ms em qualquer escala por custo previsível. Mal modelado, vira inferno de operação e custo explosivo.
Perguntas frequentes
❓ O que é desenho de tabela única?
❓ Desenho de tabela única vale sempre?
❓ Como consultar por atributo que não é chave?
Fixando
O que caracteriza o desenho de tabela única?
O que causa uma partição quente e por que ela é chamada de assassina silenciosa?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…