Í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.
REMOVEes 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:
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:
| PK | SK | attributes |
|---|---|---|
| TICKET#a91f | DETAIL | subject, body, priority, openState |
| CUSTOMER#88 | TICKET#a91f | subject, 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.
| PK | SK | openBucket | openedAt | |
|---|---|---|---|---|
| TICKET#a91f | DETAIL | OPEN | 2026-06-23T09:14:00Z | ← open: in the index |
| TICKET#b02c | DETAIL | OPEN | 2026-06-22T16:40:00Z | ← open: in the index |
| TICKET#77de | DETAIL | (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.

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 hacerREMOVEdel 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
Querysobre el índice solo devuelve los atributos proyectados en él. Si el dashboard necesita subject y priority, proyéctalos — o paga unGetItemextra 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.


