Intermedio8 min de lectura

Índices sparse en DynamoDB

Un índice sparse es un índice secundario que guarda solo los items que llevan su atributo de clave — así un subconjunto pequeño y caliente de una tabla enorme se convierte en su propia collection pre-filtrada y lista para Query.

Tienes millones de filas pero la query que lanzas todo el día toca un trozo tiny: los tickets de soporte abiertos, las facturas sin pagar, las cuentas marcadas para revisión.

Filtrar ese trozo sigue escaneando la tabla entera y te factura cada lectura. Un índice sparse hace que el índice mismo sea pequeño en su lugar.

¿Qué es un índice sparse en DynamoDB?

Un índice sparse es un índice secundario que guarda solo los items que llevan su atributo de clave. Como DynamoDB se salta cualquier item al que le falta esa clave, inventas una clave que solo escriben los items que quieres — tickets abiertos, facturas sin pagar — y el índice se convierte en exactamente ese subconjunto. Luego las queries solo lo leen a él, sin filtro, sin capacidad de lectura desperdiciada.

  • Un índice secundario solo indexa items que tienen su clave. Omite la clave en un item y nunca entra al índice — sin placeholder, sin fila null.
  • Así que inventas una clave que solo llevan los items que quieres. Escríbela en los items que consultas, quítala en el resto. El índice se convierte en exactamente ese subconjunto.
  • La query solo lee el subconjunto, sin filtro. Su tamaño trackea el set caliente pequeño, no el total de la tabla.
  • REMOVE es la palanca, no blanquear. Una cadena vacía no es una clave de índice válida — DynamoDB rechaza la escritura entera con un ValidationException — así que debes borrar el atributo.

El problema: filtrar no ahorra lecturas

Si vienes de SQL, asumes que una cláusula WHERE acota el trabajo. El FilterExpression de DynamoDB no. Corre después de que se leen los items, no antes.

Según la AWS Developer Guide, «a Query consumes the same amount of read capacity, regardless of whether a filter expression is present» — pagas por cada item examinado, y luego tiras los no-matches.

Así que si 50 de tus 5 millones de tickets están abiertos, un Query/Scan filtrado lee a través de millones para darte esos 50.

Ese es el pie detrás de cada hilo de «por qué mi scan es tan caro»; query vs. scan tiene el cuadro completo de coste.

Un índice sparse lo esquiva haciendo que el índice mismo sea pequeño.

Cómo funciona la sparsidad

Un índice secundario solo indexa items que de verdad tienen los atributos de clave del índice.

Los docs de AWS sobre índices sparse lo dejan claro: DynamoDB escribe un item a un índice secundario solo cuando ese item lleva los atributos de clave del índice, así que un índice sobre un atributo raramente seteado se queda naturalmente pequeño.

Falta la clave de partición (o sort key) del GSI en un item y DynamoDB simplemente no lo escribe al índice. Sin placeholder, sin fila null — el item está ausente.

Esa «ausencia por defecto» es todo el truco. No indexes un atributo status que cada item lleva. Inventa un atributo que solo los items que quieres consultar llevan en absoluto.

El índice se convierte entonces en una lista limpia de exactamente esos items, y un Query contra él solo los lee a ellos — sin filtro, sin capacidad desperdiciada.

Imagina la tabla base alimentando el índice, donde solo los items que llevan la clave cruzan:

clave eliminadaclave eliminadaGSI sparse (solo open)OpenOpenTabla base (todos los items)Open: tiene claveOpen: tiene claveClosed: sin claveClosed: sin clave

Solo los items con clave (open) se replican al índice; los items closed nunca entran.

Este es el mismo mindset de dar forma a claves que el single-table design: las claves son herramientas que construyes para un access pattern concreto, no espejos fieles de tus datos.

Ejemplo trabajado: «solo tickets abiertos»

Toma una tabla de tickets de soporte. La tabla base está claveada para traer un ticket por id y listar los tickets de un customer:

PKSKattributes
TICKET#a91fDETAILsubject, body, priority, openState
CUSTOMER#88TICKET#a91fsubject, priority, openState

A lo largo de la vida de la tabla, la mayoría de los tickets acaban closed. Pero la query del dashboard que tus agentes golpean todo el día es «muéstrame cada ticket abierto, el más viejo primero» — unos cientos de filas escondidas dentro de millones.

Define un con clave de partición openBucket y sort key openedAt, y solo escribe openBucket en tickets abiertos. Setéalo cuando se crea el ticket; hazle REMOVE cuando el ticket se resuelve.

PKSKopenBucketopenedAt
TICKET#a91fDETAILOPEN2026-06-23T09:14:00Z← open: in the index
TICKET#b02cDETAILOPEN2026-06-22T16:40:00Z← open: in the index
TICKET#77deDETAIL(absent)2026-05-30T11:02:00Z← closed: NOT in the index

Los tickets a91f y b02c llevan openBucket, así que viven en el GSI. El ticket 77de se resolvió y se le quitó openBucket, así que salió en silencio. El dashboard es ahora un Query barato:

Query  IndexName = "open-tickets-index"
KeyConditionExpression: openBucket = "OPEN"
ScanIndexForward: true        # oldest first

Esto solo lee tickets abiertos. Conforme los tickets se cierran, el índice se encoge solo — su tamaño trackea la población open, nunca el total.

Un valor de partición estático ("OPEN") está bien aquí precisamente porque el set se queda pequeño. Un set open enorme necesitaría una clave de partición shardeada, pero el índice de «subconjunto pequeño» es exactamente donde un solo valor es la llamada correcta.

La transición que lo hace funcionar es una sola — quitar el atributo cuando el ticket se resuelve.

Prototipa esa cláusula REMOVE y la key condition tipada del lado de lectura en el DynamoDB Expression Builder, en lugar de montar a mano ExpressionAttributeNames y placeholders :val.

Hazlo en DynoTable

La parte dura de un índice sparse es ver qué items llegaron al índice frente a cuáles cayeron en silencio.

DynoTable te deja cambiar una vista de tabla a un índice secundario y ver exactamente el subconjunto poblado. Así puedes confirmar que un ticket resuelto de verdad dejó open-tickets-index en lugar de quedarse con una clave stale.

La tabla de tickets de soporte vista a través de su GSI sparse de open-tickets en DynoTable, mostrando solo los items que llevan la clave openBucket.
La tabla de tickets de soporte vista a través de su GSI sparse de open-tickets en DynoTable, mostrando solo los items que llevan la clave openBucket.

Escollos y próximos pasos

Unas cuantas cosas a vigilar:

  • Quita la clave, no la blanquees. Una cadena vacía no es una clave de índice válida — escribir openBucket = "" falla con un ValidationException, así que el item nunca se indexa con ella. Para sacar un item del índice debes hacer REMOVE del atributo.
  • El índice es de . Los GSI se actualizan de forma asíncrona, así que un ticket recién resuelto puede seguir apareciendo un momento — las lecturas de GSI solo soportan consistencia eventual. No confíes en él para «¿está este ticket abierto ahora mismo?».
  • Cuida los atributos . Un Query sobre el índice solo devuelve los atributos proyectados en él. Si el dashboard necesita subject y priority, proyéctalos — o paga un GetItem extra por el item base completo.
  • Tanto GSIs como LSIs pueden ser sparse — la palanca es la misma: omite la sort key del índice en los items que no quieres indexar. Un GSI suele ser el mejor fit, eso sí: puedes añadirlo después de crear la tabla y darle su propio key schema y capacidad. GSI vs. LSI desglosa el trade-off.

Los índices sparse son una de las ideas más viejas del modelo. El paper de Amazon Dynamo de 2007 construyó el store alrededor de servir access patterns conocidos y de alto volumen de forma barata.

Un índice sparse es exactamente eso: da forma a las claves para que la query común no lea nada que no necesite.

Para construir e inspeccionar uno de verdad, descarga DynoTable, apúntalo a tu tabla y cambia la vista de datos a tu GSI sparse — mira cómo se actualiza el subconjunto conforme los items ganan y pierden la clave del índice.

Actualizado