Query vs Scan no DynamoDB
Query lê uma única coleção de itens pela chave de partição (opcionalmente estreitando a
chave de ordenação); Scan lê a tabela inteira e filtra depois. Eles parecem
similares na API, mas cobram — e escalam — de formas completamente diferentes.
Quando devo usar Query vs Scan no DynamoDB?
Use Query sempre que você conseguir nomear a partição que precisa — ele lê uma única coleção de itens e cobra apenas pelos itens correspondentes. Recorra ao Scan somente para exportações pontuais ou tabelas pequenas; ele lê cada item e cobra pela tabela inteira antes que qualquer FilterExpression seja executada. Em dados reais, o Query vence.
- Query é direcionado: você paga pelos itens da partição correspondente.
- Scan é exaustivo: você paga para ler cada item e depois descarta a maioria com um
FilterExpressionque roda depois que a leitura é tarifada.
Em uma tabela de qualquer tamanho real, um Scan com filtro é a clássica armadilha do "por que
minha conta está enorme e minha latência pior que a do RDS".
Lado a lado
| Query | Scan | |
|---|---|---|
| Leituras | Uma partição (por PK) | Todo item da tabela |
| Capacidade cobrada | Itens correspondentes na partição | Tabela inteira, antes de filtrar |
FilterExpression | Aplicado após a leitura — ainda cobrado pela leitura | Igual — filtrar nunca corta o custo |
| Latência | Constante conforme a tabela cresce | Cresce com o tamanho da tabela |
| Paginação | 1 MB/página → LastEvaluatedKey | 1 MB/página; paralelizável |
| Use para | Padrões de acesso conhecidos | Exportações pontuais, tabelas mínimas |
A pegadinha-chave: um FilterExpression roda depois que o DynamoDB tarifa a leitura, em
ambas as operações. Um Scan que "retorna 10 linhas" pode cobrar pela leitura de um milhão —
filtrar é uma conveniência, nunca um controle de custo.
Quanto um Scan completo realmente custa
Coloque números nisso. O DynamoDB tarifa leituras em unidades de 4 KB: uma leitura
custa uma
por 4 KB, uma leitura
metade disso. Query e
Scan somam o tamanho de cada item que tocam — não de cada item que retornam —
e arredondam para cima até o próximo múltiplo de 4 KB.
Pegue uma tabela de 1 milhão de itens com média de 2 KB por item (~2 GB de dados) e um padrão de acesso que precisa de 10 desses itens:
| Itens lidos | Dados tarifados | Unidades de leitura (eventualmente consistente) | |
|---|---|---|---|
Scan + FilterExpression | 1,000,000 | ~2 GB | ~262,000 |
Query em uma chave correspondente | 10 | 20 KB | 3 |
Os mesmos 10 itens, cinco ordens de magnitude de distância — e o Scan cobra essas
~262,000 toda vez que roda, quer o filtro corresponda a dez itens ou a nenhum. No
faturamento essas são unidades de requisição direto na
conta; em tabelas um Scan grande compete com o
tráfego de produção por throughput e pode fazê-lo sofrer throttling, resultando em
ProvisionedThroughputExceededException.
Mais três fatos de custo que surpreendem as pessoas:
Select: COUNTnão é grátis. Um Query ou Scan de contagem consome exatamente a mesma capacidade de leitura que ler os itens — só não os retorna.Limitlimita itens avaliados, não itens correspondentes. Combinado com um filtro, uma página pode voltar vazia e ainda assim cobrar uma página inteira de leituras.- Você nunca precisa adivinhar. Passe
ReturnConsumedCapacity: TOTALe cada resposta reporta a capacidade que acabou de consumir.
Confira quanto seus próprios itens pesam com a calculadora de tamanho de item e depois transforme unidades de leitura em uma conta mensal com a calculadora de preços.
Use Query
Query PK = "USER#42" AND SK begins_with "ORDER#"
Se você se pegar recorrendo ao Scan para responder a um padrão de acesso comum, isso
é um sinal de modelagem: adicione um Global Secondary Index para que o
padrão vire um Query.
A escolha se resume a uma pergunta — você consegue nomear a partição que precisa?
Se a chave é conhecida você usa Query; se não, adicione um GSI para torná-la um, e recorra ao Scan só quando nenhuma chave servir.
Quando o Scan é aceitável
Exportações pontuais, tabelas de configuração mínimas e jobs em segundo plano que paginam por
toda a tabela de propósito. Use Segment/TotalSegments para dividir um Scan entre
workers (um — veja
Scans paralelos no DynamoDB) quando você genuinamente
precisa ler tudo, e pagine-o corretamente com LastEvaluatedKey
(guia de paginação). Se um Scan que você já roda é o
problema, por que o Scan é lento e caro
percorre a triagem.
Um SELECT * FROM table reflexivo sobre o DynamoDB é o mesmo anti-padrão em
roupagem PartiQL — ele compila para um Scan. Quando você realmente precisa de
análises entre itens (um GROUP BY, um JOIN, uma agregação), o SQL Workbench do DynoTable os roda
no lado do cliente sobre um conjunto de resultados limitado, em vez de martelar a tabela.
Experimente o DynoTable para rodar e inspecionar essas consultas contra suas próprias tabelas — ele mostra a capacidade consumida de cada operação que executa.