Intermedio8 min de lectura

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.
  • FilterExpression es 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).

NoTabla base todos los productos¿Tiene clearanceAt?Replicado a ClearanceIndexNo está en el índiceQuery al índice = solo items declearance

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:

Construye tu solicitud
Código generado
new ScanCommand({
  "TableName": "AuditLog",
  "FilterExpression": "#filter0 = :filterValue0",
  "ExpressionAttributeNames": {
    "#filter0": "action"
  },
  "ExpressionAttributeValues": {
    ":filterValue0": {
      "S": "delete"
    }
  }
})

Compara las cuatro

Cuándo filtra¿Corta coste de lectura?Poder del predicadoCoste de montaje
Clave de particiónAntes de leerSí — una particiónSolo igualdadGratis (es la clave)
Sort keyAntes de leerSí — un trozoRango / begins_withDiseño de sort-key
Índice sparseAntes de leerSí — solo índicePresencia de un atributoGSI extra + coste de escritura
FilterExpressionDespués de leerNoCasi cualquier condiciónNinguno

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 FilterExpression como 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.

Actualizado