Estratégias de Filtragem no DynamoDB
"Filtragem" no DynamoDB significa quatro coisas diferentes usando a mesma palavra. Três
estreitam os dados antes de serem lidos e cobrados; uma — a que se chama Filter —
os estreita depois. Saber qual é qual é a maior parte da habilidade.
Como funciona a filtragem no DynamoDB?
O DynamoDB tem quatro formas de filtrar, e só uma roda depois que você é cobrado. A chave de partição escolhe uma partição, a chave de classificação estreita uma fatia, e um índice esparso filtra por presença de atributo — as três cortam seu custo de leitura antes da medição. Uma FilterExpression roda depois da leitura, então ela encolhe a resposta mas nunca a conta.
- Chave de partição é o filtro mais barato: ela escolhe a partição, então você nunca toca o resto da tabela.
- Chave de classificação filtra dentro de uma partição com
begins_with,between,<,>— ainda antes da cobrança, ainda barato. - Índice esparso filtra por ausência: um item só aparece no índice se tiver o atributo indexado, então o índice é o conjunto filtrado.
FilterExpressioné a cilada: ele roda depois que o DynamoDB mede a leitura, então corta o tamanho da sua resposta mas nunca a sua conta.
Monte o exemplo
Um catálogo de produtos. Uma tabela, chave de partição PK, chave de classificação
SK:
PK = "DEPT#kitchen" SK = "PROD#00194"
Todo produto também carrega price, inStock (um booleano) e clearanceAt (um unix
timestamp, presente só em itens marcados para liquidação). Itens em um departamento
compartilham uma partição, ordenados por id de produto.
Queremos quatro padrões de acesso. Cada um mapeia para uma estratégia de filtragem
diferente — e a escolha errada em qualquer um deles é um Scan que você vai pagar para
sempre.
Filtre por chave de partição
"Me dê todo produto em kitchen." A chave de partição responde isso diretamente:
Query PK = "DEPT#kitchen"
O DynamoDB lê exatamente uma partição. Nada mais na tabela é tocado ou cobrado. Esse é
o único filtro que é grátis no sentido que importa — é a diferença entre
Query e Scan.
Vindo do SQL, isso parece de cabeça para baixo: não há um WHERE department = 'kitchen'
fazendo scan de um índice, você apenas nomeia a partição. Se você não consegue
nomeá-la, isso é um problema de modelagem, não um problema de consulta.
Filtre por chave de classificação
"Me dê produtos de kitchen a partir de PROD#00100." A chave de classificação estreita
dentro da partição, e faz isso antes de a leitura ser medida:
Query PK = "DEPT#kitchen" AND SK between "PROD#00100" AND "PROD#00200"
Condições de chave de classificação são limitadas de propósito: =, <, <=, >,
>=, between e begins_with. Sem OR, sem predicado arbitrário.
Essa restrição é o que mantém a leitura mirada — o DynamoDB percorre uma fatia contígua, não a partição inteira.
A alavanca aqui é como você codifica a chave de classificação. Se o seu padrão é
"por faixa de preço", uma chave de classificação PROD#<id> não ajuda — você assaria o
preço na chave.
Essa é uma decisão de estratégia de chave de classificação, tomada em tempo de design, não em tempo de consulta.
Filtre por índice esparso
"Me dê tudo que está em liquidação no momento." A maioria dos produtos não está, então você não quer ler o catálogo para achar os poucos que estão.
Um índice esparso resolve isso por ausência. Um Global Secondary Index só contém um item se aquele item tiver ambos os atributos de chave do índice.
Defina a chave de partição do GSI como clearanceAt — presente só em itens de
liquidação — e o índice não contém mais nada.
A AWS deixa isso claro: um GSI "só contém itens que têm os atributos indexados", então itens sem o atributo de chave simplesmente não são propagados (AWS — aproveite os índices esparsos).
Agora a consulta lê só os itens de liquidação, cobrada só por eles:
Query ON ClearanceIndex GSI_PK = "CLEARANCE" (sorted by clearanceAt)
A filtragem aconteceu quando você escreveu os dados — ao escolher se definia
clearanceAt ou não. O índice é o conjunto filtrado. Veja
GSI vs LSI para qual tipo de índice encaixa.
Filtre com FilterExpression
"Me dê produtos de kitchen que estão em estoque." inStock não é um atributo de chave,
então você recorre a uma FilterExpression:
Query PK = "DEPT#kitchen"
Filter inStock = true
Eis a cilada. O DynamoDB lê todo item na partição kitchen, mede a capacidade de
todos eles, e depois descarta os fora de estoque.
A regra oficial: uma expressão de filtro é "aplicada depois que um Query termina, mas
antes de os resultados serem retornados", e "não consome nenhuma unidade de capacidade
de leitura adicional" — você já pagou pela leitura completa
(AWS — expressões de filtro para Query).
Então se kitchen tem 10.000 produtos e 12 estão em estoque, você paga para ler
10.000. A resposta é pequena; a conta não é. A FilterExpression encolhe o payload que
cruza o fio, nunca a leitura.
Há uma segunda borda, mais afiada: a paginação é medida antes da filtragem. Uma página é 1 MB de itens lidos, não 1 MB de correspondências.
Um filtro pode retornar uma página vazia com um LastEvaluatedKey definido — o
DynamoDB leu um megabyte completo, não correspondeu a nada, e te entregou um array
vazio. Você continua paginando, e pagou por cada página vazia.
Monte a expressão — nomes, valores e o escape correto de palavras reservadas — com o
DynamoDB Expression Builder para que os
placeholders #inStock/:val estejam corretos na primeira tentativa.
Compare os quatro
| Quando filtra | Corta custo de leitura? | Poder de predicado | Custo de montagem | |
|---|---|---|---|---|
| Chave de partição | Antes da leitura | Sim — uma partição | Só igualdade | Grátis (é a chave) |
| Chave de classificação | Antes da leitura | Sim — uma fatia | Intervalo / begins_with | Design da chave de classificação |
| Índice esparso | Antes da leitura | Sim — só índice | Presença de um atributo | GSI extra + custo de escrita |
| FilterExpression | Após a leitura | Não | Quase qualquer condição | Nenhum |
Leia a tabela de cima a baixo: o poder de predicado sobe, o controle de custo desce.
A FilterExpression pode expressar qualquer coisa precisamente porque roda em itens já
lidos — essa é a mesma razão pela qual ela não pode te poupar dinheiro.
Veja no DynoTable
Quando você roda um Query com um filtro, a lacuna entre itens lidos e itens
retornados é a história toda. O DynoTable mostra a capacidade consumida ao lado da
contagem de resultados — então um filtro lendo silenciosamente a partição inteira fica
visível, não escondido na sua conta mensal.
Para perguntas genuínas entre itens que um filtro não pode responder — "preço médio por
departamento", "produtos em estoque juntados às suas avaliações" — o SQL Workbench do
DynoTable roda GROUP BY, JOIN e agregações no lado do cliente sobre um conjunto de
resultados limitado, em vez de compilar para um Scan de tabela inteira.
Ciladas e próximos passos
- Não use
FilterExpressioncomo seu caminho de acesso primário. Se um padrão é comum, modele-o em uma chave ou um índice esparso. Um filtro é para o último pedacinho de estreitamento, não para o grosso dele. - Atente para páginas vazias. Uma consulta filtrada pode paginar por muito tempo
retornando nada. Respeite o
LastEvaluatedKey; não assuma que uma página vazia significa "acabou". - Um índice esparso não é grátis. Ele custa capacidade de escrita e armazenamento para cada item que cai nele — barato quando o atributo é raro, menos quando não é.
Estime quanto uma leitura filtrada vai realmente custar com a calculadora de capacidade, e experimente o DynoTable para observar a capacidade consumida contra as linhas retornadas nas suas próprias tabelas.