LSI vs GSI en DynamoDB: Cuándo usar cada índice secundario

Diseñaste tu tabla DynamoDB con una clave primaria que funciona perfectamente para el patrón de acceso principal, y ahora el equipo de producto pide filtrar por un atributo completamente diferente. Antes de duplicar datos o rediseñar la tabla, entender la diferencia entre un Local Secondary Index (LSI) y un Global Secondary Index (GSI) en DynamoDB puede ahorrarte semanas de refactorización.

TL;DR: LSI vs GSI en DynamoDB

CaracterísticaLSIGSI
Partition keyMisma que la tabla baseCualquier atributo
Sort keyAtributo diferente al de la tablaCualquier atributo (opcional)
CreaciónSolo en el momento de crear la tablaEn cualquier momento
Consistencia de lecturaFuerte o eventualSolo eventual
Límite de tamaño por partition key10 GB por valor de partition keySin límite adicional
CapacidadComparte con la tabla baseCapacidad independiente
Casos de uso típicosConsultas alternativas dentro de la misma particiónPatrones de acceso completamente nuevos

Cómo funcionan los índices secundarios en DynamoDB

DynamoDB almacena datos distribuidos en particiones físicas determinadas por la partition key. Cuando necesitas consultar por un atributo que no forma parte de la clave primaria, DynamoDB no puede enrutar la solicitud a una partición específica — tendría que hacer un full scan. Los índices secundarios resuelven esto manteniendo una proyección ordenada de los datos con una clave diferente.

La distinción fundamental: un LSI comparte la partition key de la tabla base y solo cambia la sort key. Un GSI puede usar cualquier atributo como partition key y sort key, creando efectivamente una vista completamente independiente de los datos.

graph TD TB["Tabla Base PK: UserID | SK: OrderID"] LSI["LSI: OrderDate-index PK: UserID | SK: OrderDate"] GSI["GSI: Status-OrderDate-index PK: Status | SK: OrderDate"] TB -->|"Misma partition key Sort key alternativa"| LSI TB -->|"Partition key diferente Propagación asíncrona"| GSI Q1["Query: órdenes de user-42 ordenadas por fecha"] Q2["Query: todas las órdenes con Status=PENDING"] LSI --> Q1 GSI --> Q2
  1. Tabla base: Los ítems se almacenan particionados por UserID y ordenados por OrderID.
  2. LSI (OrderDate-index): Mantiene la misma partition key (UserID) pero reordena los ítems dentro de cada partición por OrderDate. Solo puedes consultar órdenes de un usuario específico.
  3. GSI (Status-OrderDate-index): Crea una distribución completamente nueva por Status. Puedes consultar todas las órdenes con estado 'PENDING' sin conocer el UserID.
  4. Proyección: Ambos índices almacenan una copia de los atributos proyectados. Esto tiene costo de almacenamiento y escritura.

Local Secondary Index (LSI): consultas alternativas dentro de la misma partición

Un LSI te permite ordenar y filtrar los ítems de una partición usando un atributo diferente a la sort key original. La restricción clave es que la partition key debe ser idéntica a la de la tabla base — no puedes cambiarla.

El límite de 10 GB por valor de partition key aplica al conjunto combinado de la tabla base más todos sus LSIs. Si una partición supera ese límite, las escrituras fallan con un error ItemCollectionSizeLimitExceededException. Este es el motivo principal por el que muchos equipos prefieren GSIs incluso cuando técnicamente un LSI sería suficiente.

La ventaja real del LSI es la consistencia de lectura fuerte. Si tu aplicación necesita leer datos recién escritos con garantía de consistencia inmediata, el LSI es la única opción entre los índices secundarios de DynamoDB.

Crear una tabla con LSI

Los LSIs deben definirse en el momento de crear la tabla. No se pueden añadir después.

aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=UserID,AttributeType=S \
    AttributeName=OrderID,AttributeType=S \
    AttributeName=OrderDate,AttributeType=S \
  --key-schema \
    AttributeName=UserID,KeyType=HASH \
    AttributeName=OrderID,KeyType=RANGE \
  --local-secondary-indexes \
    '[{
      "IndexName": "OrderDate-index",
      "KeySchema": [
        {"AttributeName": "UserID", "KeyType": "HASH"},
        {"AttributeName": "OrderDate", "KeyType": "RANGE"}
      ],
      "Projection": {"ProjectionType": "ALL"}
    }]' \
  --billing-mode PAY_PER_REQUEST \
  --region us-east-1

Consultar usando el LSI

aws dynamodb query \
  --table-name Orders \
  --index-name OrderDate-index \
  --key-condition-expression 'UserID = :uid AND OrderDate BETWEEN :start AND :end' \
  --expression-attribute-values \
    '{":uid": {"S": "user-42"}, ":start": {"S": "2024-01-01"}, ":end": {"S": "2024-03-31"}}' \
  --region us-east-1

Nota el parámetro --consistent-read que puedes añadir aquí — no está disponible en GSIs.

Global Secondary Index (GSI): patrones de acceso completamente nuevos

Un GSI proyecta los datos con una partition key completamente diferente. DynamoDB mantiene esta proyección de forma asíncrona, lo que explica por qué las lecturas son siempre eventualmente consistentes — hay un lag de propagación entre la escritura en la tabla base y la actualización del índice.

En producción, ese lag normalmente es de milisegundos, pero no está garantizado. Si tu aplicación escribe un registro y luego inmediatamente lo consulta por el GSI, puede no encontrarlo. Este comportamiento sorprende a equipos que vienen de bases de datos relacionales.

sequenceDiagram participant App as Aplicación participant TB as Tabla Base participant GSI as GSI App->>TB: PutItem (UserID=u42, Status=PENDING) TB-->>App: 200 OK Note over TB,GSI: Propagación asíncrona
(milisegundos, no garantizado) App->>GSI: Query (Status=PENDING) GSI-->>App: Puede no incluir el ítem recién escrito TB->>GSI: Replica el ítem App->>GSI: Query (Status=PENDING) GSI-->>App: Ítem visible
  1. Escritura en tabla base: El ítem se escribe en la partición correspondiente a UserID.
  2. Propagación asíncrona: DynamoDB replica el ítem al GSI de forma asíncrona. El tiempo de propagación no está garantizado.
  3. Lectura del GSI: Una consulta inmediata al GSI puede no ver el ítem recién escrito. Esto es consistencia eventual por diseño.
  4. Capacidad independiente: El GSI consume unidades de capacidad separadas de la tabla base cuando está en modo provisionado.

Añadir un GSI a una tabla existente

A diferencia del LSI, puedes añadir un GSI en cualquier momento. El parámetro --attribute-definitions solo debe incluir los atributos nuevos que son clave del GSI y que no están ya definidos en la tabla.

aws dynamodb update-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=Status,AttributeType=S \
    AttributeName=OrderDate,AttributeType=S \
  --global-secondary-index-updates \
    '[{
      "Create": {
        "IndexName": "Status-OrderDate-index",
        "KeySchema": [
          {"AttributeName": "Status", "KeyType": "HASH"},
          {"AttributeName": "OrderDate", "KeyType": "RANGE"}
        ],
        "Projection": {"ProjectionType": "KEYS_ONLY"}
      }
    }]' \
  --region us-east-1

Si OrderDate ya estaba definido como atributo de clave en la tabla (por ejemplo, como sort key del LSI creado anteriormente), DynamoDB rechazará el comando con un error de atributo duplicado. En ese caso, omite OrderDate de --attribute-definitions y deja solo Status.

Consultar usando el GSI

aws dynamodb query \
  --table-name Orders \
  --index-name Status-OrderDate-index \
  --key-condition-expression 'Status = :s AND OrderDate >= :d' \
  --expression-attribute-values \
    '{":s": {"S": "PENDING"}, ":d": {"S": "2024-01-01"}}' \
  --region us-east-1

El error de diagnóstico más común: confundir el lag del GSI con un bug de aplicación

El patrón que más veces hemos visto en producción: el equipo escribe un pedido, redirige al usuario a una página de 'mis pedidos' que consulta el GSI por UserID... y el pedido no aparece. El primer instinto es buscar un bug en el código de escritura.

El pedido sí se escribió. Está en la tabla base. El GSI simplemente no lo propagó todavía.

Para verificar si el problema es lag del GSI o una escritura fallida, consulta directamente la tabla base con la clave primaria:

aws dynamodb get-item \
  --table-name Orders \
  --key '{"UserID": {"S": "user-42"}, "OrderID": {"S": "order-99"}}' \
  --region us-east-1

Si el ítem aparece aquí pero no en el GSI, es lag de propagación. Si no aparece aquí tampoco, hay un problema real de escritura. Este diagnóstico de dos pasos elimina horas de debugging innecesario.

La solución arquitectural es no leer del GSI inmediatamente después de escribir cuando necesitas consistencia. Usa GetItem o Query directamente en la tabla base para la lectura post-escritura, y reserva el GSI para consultas donde la consistencia eventual es aceptable.

Decidir entre LSI y GSI en DynamoDB

graph TD Start(["Necesito un nuevo patrón de consulta"]) Q1{"¿La partition key del índice es la misma que la tabla base?"} Q2{"¿La tabla ya existe en producción?"} Q3{"¿Necesitas consistencia de lectura fuerte?"} Q4{"¿Alguna partición puede superar 10 GB?"} LSI["Usa LSI (crear tabla nueva)"] GSI["Usa GSI"] WARN["LSI no disponible Usa GSI"] Start --> Q1 Q1 -->|No| GSI Q1 -->|Sí| Q2 Q2 -->|Sí| WARN Q2 -->|No| Q3 Q3 -->|No| GSI Q3 -->|Sí| Q4 Q4 -->|Sí| GSI Q4 -->|No| LSI

La regla práctica: si necesitas consultar datos de un usuario específico (o cualquier entidad con partition key conocida) con un criterio de ordenación diferente, considera el LSI — pero solo si puedes garantizar que ninguna partición crecerá más de 10 GB. En casi todos los demás casos, el GSI es la elección correcta por su flexibilidad y ausencia de límite de tamaño por partición.

Consideraciones de costo y capacidad

Cada GSI consume almacenamiento adicional proporcional a los atributos proyectados. Con ProjectionType: ALL, estás duplicando efectivamente el almacenamiento de la tabla. Con KEYS_ONLY o INCLUDE, reduces el costo pero puedes necesitar un fetch adicional a la tabla base si necesitas atributos no proyectados — lo que consume RCUs adicionales.

En modo provisionado, cada GSI requiere su propia configuración de RCU/WCU. Un GSI con capacidad insuficiente puede throttlear independientemente de la tabla base. En modo PAY_PER_REQUEST, la capacidad escala automáticamente en ambos.

Pricing and limits vary — always check the official AWS documentation for current values.

Verificar el estado de un GSI

Después de ejecutar update-table para añadir un GSI, la tabla entra en estado UPDATING y el índice en estado CREATING. Durante este período, la tabla sigue siendo accesible para lecturas y escrituras, pero el índice no está disponible para consultas hasta que alcanza el estado ACTIVE.

aws dynamodb describe-table \
  --table-name Orders \
  --query 'Table.GlobalSecondaryIndexes[*].{Index:IndexName,Status:IndexStatus}' \
  --region us-east-1

Próximos pasos con LSI y GSI en DynamoDB

Si estás diseñando una tabla nueva, define todos los patrones de acceso antes de crear la tabla — los LSIs no se pueden añadir después. Si ya tienes una tabla en producción y necesitas un nuevo patrón de consulta, el GSI es tu única opción sin rediseñar la tabla.

Para profundizar en el diseño de acceso en DynamoDB, la documentación oficial de índices secundarios y la guía de mejores prácticas para índices son los recursos de referencia.

Glosario

TérminoDefinición
Partition KeyAtributo que DynamoDB usa para determinar en qué partición física se almacena un ítem. Debe ser único en combinación con la sort key.
Sort KeyAtributo secundario de la clave primaria que permite ordenar y filtrar ítems dentro de una misma partición.
LSI (Local Secondary Index)Índice que comparte la partition key de la tabla base pero usa una sort key diferente. Solo se puede crear junto con la tabla.
GSI (Global Secondary Index)Índice con partition key y sort key independientes de la tabla base. Se puede añadir en cualquier momento.
Consistencia eventualModelo de lectura donde los datos pueden no reflejar las escrituras más recientes de forma inmediata, pero convergen con el tiempo.

Related Posts

Comentarios

Entradas populares de este blog

EC2 sin acceso a Internet en VPC personalizada: Internet Gateway y Route Table

Actualizar Contenido en CloudFront: Cómo Crear una Invalidación para Limpiar el Caché del Edge

Aumentar el Timeout de Lambda: Configuración, Límites y Diagnóstico en Producción