Estrategias de filtrado en DynamoDB
«Filtrar» en DynamoDB significa cuatro cosas distintas con la misma palabra.
Tres acotan los datos antes de leerse y facturarse; una — la que se llama
Filter — los acota después. Saber cuál es cuál es la mayor parte de la
habilidad.
¿Cómo funciona el filtrado en DynamoDB?
DynamoDB tiene cuatro formas de filtrar, y solo una corre después de que te facturan. La elige una partición, la sort key acota un trozo y un índice sparse filtra por presencia de atributo — las tres cortan tu coste de lectura antes de medir. Un FilterExpression corre después de la lectura, así que encoge la respuesta pero nunca la factura.
- La es el filtro más barato: elige la partición, así que nunca tocas el resto de la tabla.
- La filtra dentro de una partición con
begins_with,between,<,>— todavía antes de facturar, todavía barato. - Un filtra por ausencia: un item solo aparece en el índice si tiene el atributo indexado, así que el índice es el set filtrado.
FilterExpressiones la trampa: corre después de que DynamoDB mide la lectura, así que corta el tamaño de tu respuesta pero nunca tu factura.
Monta el ejemplo
Un catálogo de productos. Una tabla, clave de partición PK, sort key SK:
PK = "DEPT#kitchen" SK = "PROD#00194"
Cada producto también lleva price, inStock (un boolean) y clearanceAt
(un timestamp unix, presente solo en items marcados para clearance). Los items
de un departamento comparten una partición, ordenados por product id.
Queremos cuatro access patterns. Cada uno mapea a una estrategia de filtrado
distinta — y la elección equivocada en cualquiera de ellos es un Scan que
pagarás para siempre.
Filtrar por clave de partición
«Dame cada producto de kitchen.» La clave de partición responde esto
directamente:
Query PK = "DEPT#kitchen"
DynamoDB lee exactamente una partición. Nada más de la tabla se toca ni se
factura. Este es el único filtro que es gratis en el sentido que importa — es
la diferencia entre Query y Scan.
Si vienes de SQL, esto se siente al revés: no hay un
WHERE department = 'kitchen' escaneando un índice, solo nombras la
partición. Si no puedes nombrarla, eso es un problema de modelado, no de
query.
Filtrar por sort key
«Dame productos de kitchen desde PROD#00100 hacia arriba.» La sort key acota
dentro de la partición, y lo hace antes de que la lectura se mida:
Query PK = "DEPT#kitchen" AND SK between "PROD#00100" AND "PROD#00200"
Las condiciones de sort key están limitadas a propósito: =, <, <=, >,
>=, between y begins_with. Sin OR, sin predicado arbitrario.
Esa restricción es lo que mantiene la lectura dirigida — DynamoDB recorre un trozo contiguo, no la partición entera.
La palanca aquí es cómo codificas la sort key. Si tu patrón es «por banda
de precio», una sort key PROD#<id> no ayuda — meterías el precio en la
clave.
Esa es una decisión de estrategia de sort-key, hecha en tiempo de diseño, no de query.
Filtrar por índice sparse
«Dame todo lo que está ahora en clearance.» La mayoría de productos no lo están, así que no quieres leer el catálogo para encontrar los pocos que sí.
Un índice sparse lo resuelve por ausencia. Un solo contiene un item si ese item tiene ambos atributos de clave del índice.
Setea un flag constante clearance = "CLEARANCE" como clave de partición del
GSI — escrito solo en items de clearance — con clearanceAt como sort key, y
el índice no guarda nada más.
AWS lo deja claro: un global secondary index solo contiene items que tienen los atributos de clave del índice, así que los items a los que les falta el atributo de clave simplemente no se propagan (AWS — Take advantage of sparse indexes).
Ahora la query solo lee los items de clearance, facturados solo por ellos:
Query ON ClearanceIndex GSI_PK = "CLEARANCE" (sorted by clearanceAt)
El filtro pasó cuando escribiste los datos — eligiendo si setear
clearanceAt en absoluto. El índice es el set filtrado. Ver
GSI vs LSI para qué tipo de índice encaja.
Filtrar con FilterExpression
«Dame productos de kitchen que están in stock.» inStock no es un atributo
de clave, así que llegas a un FilterExpression:
Query PK = "DEPT#kitchen"
Filter inStock = true
DynamoDB lee cada item de la partición kitchen, mide la capacidad por
todos ellos y luego tira los que están out-of-stock.
AWS dice que un filter expression se «aplica después de que un Query
termina, pero antes de que se devuelvan los resultados», y «a Query consumes
the same amount of read capacity, regardless of whether a filter expression is
present» — ya pagaste la lectura completa (AWS — Filter expressions for
Query).
Así que si kitchen tiene 10.000 productos y 12 están in stock, pagas por leer
10.000. La respuesta es pequeña; la factura no. FilterExpression encoge el
payload que cruza el cable, nunca la lectura.
Hay un segundo filo más afilado: la paginación se mide antes de filtrar. Una página es 1 MB de items leídos, no 1 MB de matches.
Un filtro puede devolver una página vacía con un LastEvaluatedKey seteado —
DynamoDB leyó un megabyte completo, no matcheó nada y te pasó un array vacío.
Sigues paginando, y pagaste cada página vacía.
Construye la expresión — names, values y el escaping correcto de palabras
reservadas — con el
DynamoDB Expression Builder para que los
placeholders #inStock/:val estén bien a la primera.
El builder de abajo viene preset a un Scan con un FilterExpression — el
anti-patrón exacto de arriba. Fíjate en que el filtro corre sobre la tabla
entera, no sobre un trozo de clave:
Compara las cuatro
| Cuándo filtra | ¿Corta coste de lectura? | Poder del predicado | Coste de montaje | |
|---|---|---|---|---|
| Clave de partición | Antes de leer | Sí — una partición | Solo igualdad | Gratis (es la clave) |
| Sort key | Antes de leer | Sí — un trozo | Rango / begins_with | Diseño de sort-key |
| Índice sparse | Antes de leer | Sí — solo índice | Presencia de un atributo | GSI extra + coste de escritura |
| FilterExpression | Después de leer | No | Casi cualquier condición | Ninguno |
Lee la tabla de arriba abajo: el poder del predicado sube, el control de coste
baja. FilterExpression puede expresar cualquier cosa precisamente porque
corre sobre items ya leídos — esa es la misma razón por la que no puede
ahorrarte dinero.
Míralo en DynoTable
Cuando lanzas un Query con un filtro, el hueco entre items leídos e items
devueltos es toda la historia. DynoTable muestra items escaneados junto a
items devueltos mientras una lectura filtrada hace stream — así un filtro que
lee en silencio la partición entera es visible, no se esconde en tu factura
mensual.
Para preguntas genuinas entre items que un filtro no puede responder —
«precio medio por departamento», «productos in stock joined a sus reviews» —
el SQL Workbench de DynoTable lanza GROUP BY, JOIN y agregados en el
cliente sobre un result set acotado, en lugar de compilar a un Scan de tabla
completa.
Escollos y próximos pasos
- No uses
FilterExpressioncomo tu access path primario. Si un patrón es común, modélalo en una clave o un índice sparse. Un filtro es para el último pedacito de acotado, no para el grueso. - Vigila las páginas vacías. Un Query filtrado puede paginar mucho tiempo
devolviendo nada. Honra
LastEvaluatedKey; no asumas que una página vacía significa «hecho». - Un índice sparse no es gratis. Cuesta capacidad de escritura y storage por cada item que aterriza en él — barato cuando el atributo es raro, menos cuando no lo es.
Estima lo que costará de verdad una lectura filtrada con la calculadora de precios, y prueba DynoTable para ver la capacidad consumida frente a las filas devueltas en tus propias tablas.