El patrón de lista de adyacencia en DynamoDB
Un grafo no es más que nodos y aristas, y el patrón de lista de adyacencia
almacena ambos como items simples en una tabla. Cada arista se convierte en una fila
cuya clave de partición es el nodo origen y cuya clave de ordenación es el destino.
Consultar una partición lista cada vecino — el sustituto en DynamoDB de un JOIN
sobre una tabla de unión.
¿Qué es el patrón de lista de adyacencia en DynamoDB?
El patrón de lista de adyacencia modela un grafo como items de arista en una sola tabla: cada relación (A sigue a B) es una fila con el origen en la clave de partición y el destino en la clave de ordenación. Consultar una partición lista cada vecino, y un GSI invertido invierte la relación — sin joins, sin scans, ambas direcciones en una sola consulta.
- Las aristas son items. Modela cada relación (el usuario A sigue al usuario B) como su propio item con clave de origen en la clave de partición y destino en la clave de ordenación.
- Una dirección es gratis; la otra necesita un GSI. La tabla base responde "¿a quién sigue A?". Un índice invertido responde "¿quién sigue a A?".
- Sin joins, sin scans. Ambas direcciones son un único
Querycontra una partición conocida — nunca unScande tabla completa. - Es la primitiva de muchos a muchos. Seguidores, membresías, likes, amistades — cualquier grafo donde una entidad conecta con muchas otras encaja en esta forma.
Plantéalo como patrones de acceso
Si vienes de SQL, un grafo de seguidores es una tabla de unión: follows(follower_id, followee_id). Para listar los seguidores de alguien indexas una columna; para listar a
quién sigue indexas la otra. DynamoDB no tiene joins, así que diseñas las claves para
servir cada lectura directamente.
Apunta primero las lecturas. Para un grafo social de seguidores:
- ¿A quién sigue el usuario A? (su lista de seguidos)
- ¿Quién sigue al usuario A? (su lista de seguidores)
- ¿Sigue A a B? (una búsqueda puntual)
Las claves existen únicamente para responder esa lista. Acertarlas y cada lectura es un
Query o GetItem.
Modela las aristas como items
Usa nombres de clave genéricos para que la tabla pueda contener más de un tipo de entidad, y codifica el tipo de nodo en el valor. Una arista de seguimiento se ve así:
| PK | SK | createdAt | edgeType |
|---|---|---|---|
| ACTOR#alice | TARGET#bob | 1718900000 | FOLLOWS |
| ACTOR#alice | TARGET#carol | 1718900100 | FOLLOWS |
| ACTOR#dave | TARGET#bob | 1718900200 | FOLLOWS |
PK = ACTOR#alice es el origen de la arista; SK = TARGET#bob es a quién sigue ella.
Un Query PK = "ACTOR#alice" devuelve cada cuenta que Alice sigue en una sola lectura
facturada — toda su lista de seguidos, sin joins.
Cada arista se escribe una vez, en la dirección "a quién sigo". La dirección inversa ("quién me sigue") es la parte que la tabla base no puede responder — todavía.
Recorre la otra dirección con un GSI
La tabla base tiene clave de origen primero, así que no puede responder "¿quién sigue a Bob?" sin escanear. Añade un global secondary index que invierte las claves: proyecta el destino en la clave de partición del índice y el origen en la clave de ordenación del índice.
| GSI1PK | GSI1SK | (base item) | |
|---|---|---|---|
| TARGET#bob | ACTOR#alice | ACTOR#alice → TARGET#bob | |
| TARGET#bob | ACTOR#dave | ACTOR#dave | → TARGET#bob |
| TARGET#carol | ACTOR#alice | ACTOR#alice → TARGET#carol |
Ahora Query GSI1 WHERE GSI1PK = "TARGET#bob" lista a todos los que siguen a Bob —
alice y dave — en una sola lectura. El mismo item de arista sirve ambas
direcciones: la tabla base es seguidos, el índice es seguidores. Escribes cada
arista una vez y obtienes ambas consultas gratis.
Este es exactamente el patrón que AWS documenta en su guía de mejores prácticas de DynamoDB para modelar relaciones de muchos a muchos y datos de grafo — almacena las aristas como items, luego usa un GSI para invertir la relación.
Comprueba una sola arista de forma barata
"¿Sigue Alice a Bob?" no necesita ninguna de las listas. Como la arista tiene clave
PK = ACTOR#alice, SK = TARGET#bob, es un GetItem directo — la lectura más barata
que ofrece DynamoDB, sin Query, sin índice.
Para escribir el seguimiento de forma idempotente y evitar el doble conteo, protege el
PutItem con una condición de que la arista no exista ya:
attribute_not_exists(PK)
Puedes ensamblar esa condición — y los valores de clave marshalled — con el
DynamoDB expression builder en lugar de escribir a
mano el ConditionExpression y ExpressionAttributeValues.
Hazlo en DynoTable
Cuando navegas por la tabla, las aristas de un actor se apilan bajo una única clave de partición como una item collection, y cambiar a la vista del GSI muestra la lista invertida de seguidores — las dos mitades de la relación una al lado de la otra.

Trampas
La partición del famoso. Un usuario con millones de seguidores concentra cada
arista de seguidor bajo una partición GSI1PK = TARGET#<star>. Las lecturas de esa
colección están paginadas y pueden calentarse. Para grafos con mucho fan-out, shardea
la clave caliente (p. ej. TARGET#bob#0..N) o desnormaliza los conteos para no
releer la lista entera.
Almacenar conteos en la arista. Un conteo de seguidores no es una arista — no lo derives leyendo y contando toda la partición en cada vista de perfil. Mantén un atributo contador en el item del usuario y actualízalo transaccionalmente con la arista.
Olvidar que la escritura inversa no es necesaria aquí. Una variante clásica de la lista de adyacencia escribe la arista dos veces con los ids intercambiados. Con un GSI de clave invertida la escribes una vez y dejas que el índice materialice la inversa — menos escrituras, sin desviación entre las dos copias.
Próximos pasos
La lista de adyacencia es el bloque de construcción de relaciones del
single-table design; el índice que invierte es un
GSI, no un LSI, porque la clave de partición cambia. Y cada
lectura aquí es un Query o GetItem sobre una clave conocida — nunca el
error del Scan.
Construye la condición y las expresiones de clave con el DynamoDB expression builder, y descarga DynoTable para modelar un grafo de seguidores contra tu propia tabla y ver ambas direcciones resolverse en una sola lectura.


