Avanzado8 min de lectura

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 Query contra una partición conocida — nunca un Scan de 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í:

PKSKcreatedAtedgeType
ACTOR#aliceTARGET#bob1718900000FOLLOWS
ACTOR#aliceTARGET#carol1718900100FOLLOWS
ACTOR#daveTARGET#bob1718900200FOLLOWS

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.

GSI1PKGSI1SK(base item)
TARGET#bobACTOR#aliceACTOR#alice → TARGET#bob
TARGET#bobACTOR#daveACTOR#dave→ TARGET#bob
TARGET#carolACTOR#aliceACTOR#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.

Tabla base: PK = ACTOR#aliceSK: TARGET#bobSK: TARGET#carolQuery base a quién sigue alice
GSI1: GSI1PK = TARGET#bobGSI1SK: ACTOR#aliceGSI1SK: ACTOR#daveQuery GSI1 quién sigue a bob

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.

DynoTable mostrando los items de arista de seguimiento de un actor bajo una única partición, con los atributos de clave del GSI visibles como columnas.
DynoTable mostrando los items de arista de seguimiento de un actor bajo una única partición, con los atributos de clave del GSI visibles como columnas.

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.

Actualizado