Iniciante6 min de leitura

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 FilterExpression que 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

QueryScan
LeiturasUma partição (por PK)Todo item da tabela
Capacidade cobradaItens correspondentes na partiçãoTabela inteira, antes de filtrar
FilterExpressionAplicado após a leitura — ainda cobrado pela leituraIgual — filtrar nunca corta o custo
LatênciaConstante conforme a tabela cresceCresce com o tamanho da tabela
Paginação1 MB/página → LastEvaluatedKey1 MB/página; paralelizável
Use paraPadrões de acesso conhecidosExportaçõ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 lidosDados tarifadosUnidades de leitura (eventualmente consistente)
Scan + FilterExpression1,000,000~2 GB~262,000
Query em uma chave correspondente1020 KB3

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: COUNT nã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.
  • Limit limita 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: TOTAL e 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?

SimNãoSimNãoPadrão de acessoChave de partição conhecida?Query uma partiçãoUm GSI consegue chaveá-la?Adicione um GSIScan a tabela inteira

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.

Atualizado