Por que um Scan do DynamoDB É Lento e Caro
Um Scan lê cada item da tabela e só filtra depois. É a operação que você recorre por
memória muscular de SQL, e a que silenciosamente engorda sua conta enquanto piora sua
latência além da caixa RDS que você deixou para trás.
Por que meu Scan do DynamoDB é lento e caro?
Um Scan lê cada item da tabela antes de a FilterExpression ser executada, então você paga
para ler a tabela inteira independentemente de quantas linhas voltam, e fica mais lento conforme
a tabela cresce. A correção é quase sempre um Query chaveado — modele o padrão de acesso em
torno de uma chave para que o DynamoDB toque uma partição em vez de tudo.
- Um
Scanlê a tabela inteira, toda vez. O tamanho, não a contagem do seu resultado, decide o que você paga e quanto tempo leva. - A
FilterExpressioné uma mentira sobre custo. Ela roda depois que a leitura é medida, então retornar 12 itens pode cobrar pela leitura de 12 milhões. - Um
Scanfica mais lento conforme você cresce. UmQuerychaveado permanece plano — toca uma partição não importa quão grande a tabela fique. - A correção é quase sempre modelagem, não tuning. Se você faz
Scanpara responder uma pergunta rotineira, está faltando uma chave.
O que um Scan de fato faz
Vindo do SQL, SELECT * FROM events WHERE type = 'checkout' parece de graça — o motor tem
um índice, ou não tem, mas de qualquer jeito você recebe linhas de volta. No DynamoDB não há
query planner decidindo isso por você.
Um Scan percorre a tabela inteira sequencialmente, 1 MB de cada vez, e entrega cada página
à sua FilterExpression. O que o filtro rejeita ainda é lido, ainda é medido, e ainda está
na sua conta. (AWS: Scanning tables)
Essa é a cilada. O filtro parece uma cláusula WHERE, mas muda o conjunto de resultados,
nunca o custo. Um Scan consome a mesma capacidade de leitura havendo ou não um filtro
presente. (AWS: Scanning tables)
Conte as unidades de leitura
O DynamoDB mede leituras em unidades de capacidade de leitura (RCUs). Uma RCU compra uma única leitura fortemente consistente de um item de até 4 KB; leituras eventualmente consistentes custam metade disso. Itens maiores arredondam para o próximo 4 KB. (AWS: Read/write capacity mode)
Pegue uma tabela de analytics, ProductEvents. Cada linha é um evento rastreado:
PK = "TENANT#acme"
SK = "TS#2026-06-23T14:08:55Z#evt_9f3a"
attrs: eventType, sessionId, userId, payloadBytesDigamos que ela contenha 2.000.000 eventos, cada um ~1 KB, todos sob um único tenant ocupado. Você quer os checkouts de hoje. A jogada reflexiva:
Scan ProductEvents
FilterExpression: eventType = "checkout"
Esse filtro pode retornar 40 linhas. Mas o Scan leu todos os 2.000.000 de itens primeiro.
A ~1 KB cada (1 RCU por 4 KB, eventualmente consistente ≈ 0,5 RCU por 4 KB), você mediu algo
em torno de 250.000 RCUs — e paginou por ~500 MB de dados — para devolver 40 itens.
Agora modele o padrão de acesso como uma chave e faça Query em vez disso:
Query ProductEvents
PK = "TENANT#acme"
AND SK begins_with "TS#2026-06-23"
Isso lê só a fatia correspondente de uma partição. Se essas 40 linhas de checkout mais os outros eventos do dia chegam a ~2 MB, você paga por ~2 MB de leituras, não 500 MB. Mesma resposta, uma fração minúscula do custo — e a latência permanece plana conforme a tabela cresce.
Scan vs Query, medido
| Scan + filtro | Query chaveado | |
|---|---|---|
| Leituras | Cada item da tabela | Uma partição, estreitada por SK |
| Capacidade cobrada | Tabela inteira, antes do filtro | Só os itens na sua fatia |
| Nosso exemplo | ~250.000 RCUs (~500 MB) | algumas centenas de RCUs (~2 MB) |
| Latência | Cresce com o tamanho da tabela | Plana conforme a tabela cresce |
| Contagem do resultado | Não decide nada sobre o custo | Corresponde ao que você paga |
A lição que a tabela codifica: num Scan, sua contagem de resultado e sua conta não têm
relação. Num Query, elas se acompanham.
Decida antes de fazer Scan
A maioria dos Scans acidentais vem de uma pergunta: consigo nomear a partição de que
preciso? Se sim, é um Query. Se não, a correção é uma chave, não um filtro maior.
Aqui está a decisão em forma de fluxo.
O caminho quase sempre termina em Query; você só cai para Scan quando nenhuma chave —
presente ou adicionável — encaixa no padrão de acesso.
Se o padrão é real e recorrente mas a tabela base não consegue chaveá-lo, esse é o sinal de
adicionar um Índice Secundário Global para que a pergunta vire um
Query. Modelar suas chaves em torno dos seus padrões de acesso de antemão é o jogo inteiro
— veja single-table design.
Escreva a consulta chaveada, não um filtro
Quando você de fato precisa de uma condição além da chave, construa-a deliberadamente em vez
de despejar tudo numa FilterExpression. O
DynamoDB Expression Builder gera a
KeyConditionExpression e os placeholders de atributo para você, para que a chave de
partição e a de ordenação façam o estreitamento — antes de o DynamoDB medir a leitura, não
depois.
KeyConditionExpression: PK = :tenant AND begins_with(SK, :day)
Quando um Scan de fato é aceitável
Um Scan não é proibido — é só o padrão errado. É a ferramenta certa quando você
genuinamente quer dizer "ler tudo":
- Exports avulsos ou backfills rodados à mão.
- Tabelas minúsculas de config / lookup onde a tabela inteira tem alguns poucos KB.
- Jobs de background que paginam a tabela completa de propósito. Divida-os entre
workers com
Segment/TotalSegments— um scan paralelo — em vez de um único rastreio sequencial longo. (AWS: Scanning tables)
E note que o PartiQL não te salva: SELECT * FROM ProductEvents WHERE eventType = 'checkout' sem um predicado de chave compila direto para um Scan. É a mesma
cilada com roupa de SQL. (Veja Query vs Scan para o detalhamento
completo.)
Quando você realmente precisa de analytics entre itens — um GROUP BY, um JOIN, um
agregado que o DynamoDB não consegue expressar — o SQL Workbench do DynoTable os roda no
lado do cliente sobre um conjunto de resultados limitado, em vez de martelar a tabela com um
Scan completo.
Próximos passos
Estime o que cada padrão custa com a calculadora de capacidade, leia Query vs Scan para o contraste no nível da API, e baixe o DynoTable para rodá-los contra suas próprias tabelas e ver a capacidade consumida você mesmo.