Cómo se almacena internamente un GSI de DynamoDB
Un Global Secondary Index no es un puntero de vuelta a tu tabla. Es una tabla separada y gestionada internamente — sus propias particiones, su propio esquema de claves, su propia capacidad — que DynamoDB mantiene sincronizada copiando las escrituras en ella de forma asíncrona.
Viniendo de SQL, un índice es un B-tree atornillado a la misma tabla física, actualizado dentro de la misma transacción. Un GSI rompe ambas suposiciones, y casi toda sorpresa con un GSI se remonta a ese único hecho.
¿Cómo se almacena un GSI de DynamoDB?
Un GSI de DynamoDB se almacena como una tabla separada y gestionada internamente — con sus propias particiones, su propio esquema de claves y su propia capacidad — no como un puntero hacia la tabla base. DynamoDB copia cada escritura en el índice de forma asíncrona, almacenando solo las claves del GSI, las claves de la tabla base y cualquier atributo proyectado.
- Un GSI es su propia tabla. Tiene un espacio de particiones totalmente independiente con clave por la clave de partición del GSI, no la de la tabla base.
- Las escrituras se replican de forma asíncrona. Tu escritura se confirma primero en la tabla base, luego DynamoDB la propaga a cada GSI por un camino de fondo.
- Solo se almacenan los atributos proyectados. El índice guarda las claves del GSI, las claves base, más cualquier atributo que proyectaste — nada más.
- La clave del GSI no tiene por qué ser única. Múltiples items base pueden compartir una clave de partición/ordenación del GSI; la clave primaria base es el desempate que los mantiene distintos.
Empieza con un item base
Toma un registro de auditoría SaaS. Cada acción privilegiada en un workspace se
convierte en un evento inmutable. La tabla base, WorkspaceEvents, tiene clave de
forma que todos los eventos de un workspace vivan en una colección de items,
ordenados por tiempo:
| EventPK | EventSK | actorId | verb | targetRef |
|---|---|---|---|---|
| WS#orbit-9 | TS#2026-06-23T14:02:11Z | USR#kp | ROLE_GRANTED | USR#mara |
EventPK = "WS#orbit-9" particiona por workspace; EventSK es un timestamp ISO de
forma que una Query devuelve los eventos de un workspace en orden cronológico. Eso
sirve perfectamente "muéstrame la línea de tiempo de este workspace".
No sirve nada más. No puedes preguntar "¿qué hizo USR#kp a través de cada
workspace?" — actorId no es una clave, así que la única forma de responderla en la
tabla base es un Scan completo. Ese es el patrón de acceso
que un GSI existe para añadir.
Añade un GSI y observa aparecer una segunda tabla
Define un GSI, ByActor, que vuelve a particionar los mismos eventos por quién los
realizó:
ByActor (GSI)
GSI1PK = actorId ("USR#kp")
GSI1SK = EventSK ("TS#2026-06-23T14:02:11Z")
DynamoDB ahora mantiene una segunda estructura física. El mismo evento lógico se
almacena dos veces — una en la partición WS#orbit-9 de la tabla base, y otra en
la partición USR#kp del GSI:
| GSI1PK | GSI1SK | EventPK | EventSK | verb |
|---|---|---|---|---|
| USR#kp | TS#2026-06-23T14:02:11Z | WS#orbit-9 | TS#2026-06-23T14:02:11Z | ROLE_GRANTED |
Fíjate en lo que viajó incluido: las claves de la tabla base (EventPK,
EventSK) se almacenan automáticamente en cada item del GSI. Así es como un acierto
en el GSI puede señalarte de vuelta al item completo — y por qué un índice
KEYS_ONLY igual cuesta almacenamiento.
Qué vive realmente en el GSI
El índice no copia el item entero. Cada entrada del GSI guarda exactamente tres cosas, y solo controlas la tercera:
| Almacenado en el GSI | De dónde viene | ¿Opcional? |
|---|---|---|
| Clave de partición + ordenación del GSI | Los atributos que nombraste como claves del GSI | No |
| Clave(s) de la tabla base | Copiada de cada item base | No |
| Atributos proyectados | Tu elección de Projection | Sí |
Projection es KEYS_ONLY, INCLUDE (una lista nombrada) o ALL. Una Query
sobre el GSI solo puede devolver atributos que estén en el índice.
Pide uno que no esté proyectado y DynamoDB no lo obtiene de forma transparente — no recibes nada de vuelta para ese campo. (docs de GSI de AWS)
Esa es la trampa relacional al revés: SQL haría un join de vuelta al heap por la columna ausente. Un GSI nunca lo hace. La proyección es todo el contrato.
Cómo una escritura llega al índice
La replicación es la parte que más rompe la intuición SQL. Una escritura base y su actualización de índice no son una operación atómica.
Cuando haces PutItem, DynamoDB se confirma de forma durable en la tabla base,
reconoce tu escritura y luego propaga el cambio a un camino de fondo que
actualiza cada GSI. El reconocimiento no espera al índice.
Aquí está el orden de los eventos para nuestra escritura de auditoría, de arriba abajo:
El llamante recibe su 200 OK en el paso tres, antes de que terminen los pasos
cuatro a seis — así que una Query sobre ByActor en ese hueco puede perderse un
evento recién creado.
Esa asincronía es por diseño, no un defecto: es el linaje del paper de Amazon Dynamo de 2007, que eligió disponibilidad sobre consistencia síncrona. Las consecuencias completas viven en por qué un GSI es eventualmente consistente.
La clave del GSI no es una clave única
En SQL, un índice secundario no único es el default y uno único es una restricción a la que optas. Un GSI es lo opuesto: no tiene garantía de unicidad, nunca.
Dos eventos de auditoría del mismo actor con timestamps que colisionan compartirían
la misma GSI1PK y GSI1SK. DynamoDB almacena ambos — los desambigua
internamente por la clave primaria de la tabla base, que siempre va incluida.
Así que una Query del GSI para un actor en un instante puede legítimamente
devolver varios items. Si asumiste una-fila-por-clave como te daría un índice único
de SQL, ese es el footgun.
Cuando consultas el índice, el
constructor de expresiones de DynamoDB escribe
la KeyConditionExpression con nombres y valores escapados correctamente — p. ej.
emparejar un actor desde un corte:
KeyConditionExpression: "#a = :actor AND #ts > :since"
ExpressionAttributeNames: { "#a": "actorId", "#ts": "EventSK" }
ExpressionAttributeValues: {
":actor": { "S": "USR#kp" },
":since": { "S": "TS#2026-06-01T00:00:00Z" }
}La capacidad vive con el índice, no con la tabla
Como el GSI es su propia tabla, tiene su propia capacidad de lectura y escritura,
facturada y throttleada por separado de la tabla base. Una lectura de ByActor
consume las unidades de lectura del GSI, nunca las de la tabla.
El acoplamiento inverso es el que muerde: cada escritura de la tabla base también escribe el índice, y si el GSI no puede absorber eso, ejerce contrapresión sobre la escritura base. Ese mecanismo tiene su propia guía — cuándo un GSI throttlea las escrituras de la tabla base.
Por esto también la clave de partición de un GSI importa tanto como la de la tabla base. Una clave de GSI de baja cardinalidad agrupa las escrituras en una partición del índice aunque las escrituras base estén perfectamente repartidas — una partición caliente que creaste al re-clavar.
Trampas y próximos pasos
- No esperes recuperar atributos no proyectados. Una
Querydel GSI devuelve solo lo que el índice almacena. Si necesitas el item completo, proyéctalo u obténlo de la tabla base por las claves incluidas. - No trates una clave del GSI como única. Planifica que una
Querydevuelva más de un item por clave; la clave primaria base es la única identidad real. - No leas un GSI justo después de la escritura que lo alimentó. El camino async significa que el índice puede no mostrar tu escritura aún — lee la tabla base cuando necesites leer tus propias escrituras.
- Dimensiona la capacidad del GSI deliberadamente. Es independiente en lecturas y una dependencia oculta en escrituras.
Todo el juego es elegir formas de clave que sirvan a tus patrones — diseño de tabla única sobrecarga un GSI a través de muchos de ellos; GSI vs LSI cubre cuándo encaja un índice local en su lugar.
Construye y previsualiza tu KeyConditionExpression del GSI en el
constructor de expresiones de DynamoDB, luego
prueba DynoTable para inspeccionar los atributos proyectados de un índice
y observar las escrituras replicarse en el GSI sobre tus propias tablas.