Intermedio8 min de lectura

Relaciones muchos a muchos en DynamoDB

Un estudiante se matricula en muchos cursos; un curso tiene muchos estudiantes. En SQL vas a una tabla de join y un JOIN de tres tablas.

DynamoDB no tiene joins, así que la relación tiene que vivir en las claves — y el truco es guardar cada edge de matrícula en una forma que ambos lados puedan Query directamente.

Esta guía recorre el problema estudiantes ↔ cursos de punta a punta: los patrones de acceso, el patrón que los resuelve, un key schema original que puedes copiar y cómo leer ambas direcciones de vuelta sin escanear nunca la tabla.

¿Cómo modelas una relación muchos a muchos en DynamoDB?

DynamoDB no tiene joins, así que modelas una relación muchos a muchos con el patrón : guarda cada enlace como su propio item edge claveado por un lado, luego añade un GSI invertido que intercambia las claves. Un solo edge, escrito una vez, responde luego a queries de ambas direcciones de forma barata.

  • Guarda cada matrícula como su propio item edge, no como un atributo lista en ninguno de los lados.
  • Clavea el edge por el estudiante (PK = STU#…, SK = ENROLL#CRS#…) para que un solo Query devuelva toda la lista de cursos del estudiante.
  • Añade un invertido que intercambia los roles (GSI1PK = CRS#…) para que el mismo edge también responda «¿quién está en este curso?».
  • Un edge, escrito una vez, se lee barato en ambos sentidos — ese es todo el juego.

Enmarca primero los patrones de acceso

El modelado en DynamoDB es access-pattern-first: decides las lecturas antes de elegir un solo nombre de atributo. Una relación muchos a muchos casi siempre tiene dos lecturas simétricas más los lookups de entidad:

  • Traer el perfil de un estudiante, y listar cada curso en el que ese estudiante está matriculado.
  • Traer los metadatos de un curso, y listar cada estudiante matriculado en ese curso.
  • Buscar un solo edge de matrícula — para actualizar una nota o abandonar el curso.

Las dos lecturas de lista apuntan en direcciones opuestas a través del mismo conjunto de edges. Un diseño ingenuo sirve una barata y fuerza un Scan para la otra — el pie exacto cubierto en Query vs Scan.

El trabajo es hacer que ambas direcciones sean un solo Query.

Usa el patrón adjacency-list

La propia guía de DynamoDB para relaciones es la adjacency list: modela cada relación como un item cuya clave de partición es un endpoint y cuya clave de ordenación es el otro.

AWS lo documenta en la página Best Practices for Managing Many-to-Many Relationships del DynamoDB Developer Guide.

¿Por qué claves y no una segunda tabla? Porque la primitiva que te da DynamoDB es un Query contra una sola partición.

Un Query lee un rango contiguo de valores de sort-key bajo una clave de partición en una sola operación facturada — ese es el único "join" que ofrece el motor.

Para obtener una relación que se lea barata desde ambos lados, duplicas el edge: escríbelo una vez claveado por el estudiante, luego usa un índice secundario para proyectar el mismo edge claveado por el curso.

Es el pensamiento de claves sobrecargadas de Single-Table Design, aplicado a una relación en vez de a una jerarquía padre-hijo.

La forma son dos vistas apiladas del mismo edge — la tabla base claveada por estudiante, el GSI invertido claveado por curso:

mismo edge, clavesintercambiadasmismo edge, clavesintercambiadasGSI1 invertido claveado por cursoGSI1PK CRS#math204GSI1SK STU#a91GSI1PK CRS#cs101GSI1SK STU#a91Tabla base claveada por estudiantePK STU#a91SK ENROLL#CRS#math204PK STU#a91SK ENROLL#CRS#cs101

Cada edge se escribe una vez en la tabla base y se proyecta al GSI con sus claves intercambiadas, así que un Query contra cualquiera de las particiones lee la relación barato.

El linaje vuelve al paper Amazon Dynamo de 2007: la clave de partición es la unidad de distribución, y el acceso por clave única es el camino rápido.

Las relaciones en DynamoDB son un ejercicio de doblar lecturas muchos-a-muchos hacia ese camino rápido.

Trabaja el ejemplo: estudiantes ↔ cursos

Usa una tabla con claves genéricas, PK y SK, y codifica el tipo de entidad en el valor. El edge de matrícula es el corazón:

PKSKattributes
STU#a91PROFILEname, year, major
STU#a91ENROLL#CRS#math204 enrolledOn, grade
STU#a91ENROLL#CRS#cs101enrolledOn, grade
CRS#math204METADATAtitle, credits, term
CRS#cs101METADATAtitle, credits, term

Un solo Query PK = "STU#a91" devuelve el perfil del estudiante y cada matrícula en una lectura. Estrecha con SK begins_with "ENROLL#" para obtener solo los edges de curso. Eso resuelve "listar los cursos de un estudiante".

Pero "listar los estudiantes de un curso" apunta al otro lado — y la tabla base no puede responderlo, porque el id del estudiante está en la clave de partición, no en la de ordenación.

Añade un índice secundario global invertido que intercambia los roles. Dale a los items edge un par genérico GSI1PK/GSI1SK con el curso en el lado de partición y el estudiante en el de ordenación:

PKSKGSI1PKGSI1SK
STU#a91ENROLL#CRS#math204CRS#math204STU#a91
STU#b30ENROLL#CRS#math204CRS#math204STU#b30
STU#a91ENROLL#CRS#cs101CRS#cs101STU#a91

Ahora Query GSI1 WHERE GSI1PK = "CRS#math204" lista cada estudiante de ese curso — la lectura que la tabla base no podía servir. Un item edge, escrito una vez, responde ambas direcciones.

Tiene que ser un GSI, no un LSI: la partición del curso es totalmente distinta de la partición del estudiante, y un LSI comparte la clave de partición de la tabla base.

El índice abarca varias particiones, así que debe ser global — mira GSI vs LSI.

Los GSI en DynamoDB se rellenan de forma asíncrona. Una matrícula recién creada puede tardar un momento en aparecer en la dirección CRS#….

Trata la lectura del roster del curso como — lo que el Developer Guide señala explícitamente para los índices secundarios globales.

Escríbelo y léelo en DynoTable

Escribir la matrícula significa poner cuatro atributos de clave más los datos propios del edge. La condición que evita que un estudiante se matricule dos veces en el mismo curso es una guarda attribute_not_exists(PK) sobre la clave compuesta.

Ese es exactamente el tipo de condición que puedes montar visualmente con el DynamoDB Expression Builder en vez de escribir a mano los ExpressionAttributeNames y los valores placeholder.

En DynoTable apuntas un Query a GSI1, pones GSI1PK = "CRS#math204", y el roster vuelve como una tabla que puedes leer, ordenar y editar in place — ambas direcciones de la relación explorables desde un solo schema.

Consultando el GSI invertido en DynoTable para listar cada estudiante matriculado en un curso.
Consultando el GSI invertido en DynoTable para listar cada estudiante matriculado en un curso.

Escollos y próximos pasos

  • No guardes un lado como atributo lista. Un array courseIds en el item estudiante parece ordenado hasta que un curso necesita su roster, el array choca el techo de 400 KB del item, o dos matrículas compiten y se pisan. Items edge discretos escalan y se actualizan de forma independiente.
  • Mantén los datos del edge en el edge. El grade y el enrolledOn de la matrícula pertenecen al item edge, no duplicados en el estudiante o el curso — hay exactamente una fila por par (estudiante, curso) que actualizar.
  • Cuida la propagación del GSI. La dirección del índice invertido es de consistencia eventual, así que una lectura justo después de una matrícula puede ir por detrás una fracción de segundo.
  • Proyecta solo lo que el roster necesita. Una proyección KEYS_ONLY o estrecha mantiene el GSI pequeño cuando la vista del roster solo necesita ids.

Para ir más a fondo en los patrones de alrededor, lee Single-Table Design para claves sobrecargadas y GSI vs LSI para cuándo el índice invertido tiene que ser global. Y para partir de tus propias relaciones, la herramienta de Single-Table Design gratis convierte una lista de patrones de acceso como "listar los cursos de un estudiante / listar los estudiantes de un curso" en un plan PK/SK/GSI con items de ejemplo.

Luego descarga DynoTable para modelar el schema estudiantes ↔ cursos de verdad — escribe los edges, construye la condición con el Expression Builder, y consulta ambas direcciones de la relación sin un solo scan. Y cuando quieras la vista clásica de JOIN de tres tablas de todos modos, el SQL Workbench de DynoTable la ejecuta sobre tus tablas en vivo.

Actualizado

Prueba este diseño de forma interactiva

Esboza tus entidades y patrones de acceso en la herramienta gratuita de diseño de tabla única de DynamoDB: sugiere plantillas de claves PK/SK, previsualiza las colecciones de Items y muestra qué patrones necesitan un GSI.

Abrir la herramienta de diseño de tabla única