En DailyWatch, nuestro panel de "videos relacionados" comenzó con una humilde consulta SQLite. Unía tres tablas, contaba etiquetas superpuestas y devolvía una lista clasificada. Para un catálogo pequeño, eso era suficiente. Un video de cocina etiquetado como "italian" y "pasta" hacía que aparecieran otros videos con las mismas etiquetas, y los usuarios hacían clic. El resultado parecía relevante porque los metadatos estaban limpios y la biblioteca era poco profunda.

Luego el catálogo creció y las expectativas de la audiencia cambiaron. La gente no quería simplemente más videos con etiquetas idénticas. Querían el clip que el cuarenta por ciento de los espectadores veía inmediatamente después del actual. Querían ese canal de nicho que seguía apareciendo en las mismas sesiones de visualización nocturnas, aunque su descripción no mencionara nada sobre el video inicial y sus etiquetas fueran escasas. Estos son patrones de comportamiento, no coincidencias de metadatos. SQLite no podía expresarlos sin degenerar en un nido inmanejable de self-joins y expresiones de tabla comunes recursivas. Cada nueva señal que intentábamos modelar, ya fuera la co-visualización o la adyacencia de sesión, añadía latencia y carga cognitiva. Teníamos un problema de grafos comprimido en un entorno relacional.

Moví la capa de recomendación a Apache AGE.

Apache AGE es una extensión de PostgreSQL que añade consultas de grafos openCypher a la base de datos que ya utilizas. No es un servidor separado. No es un sidecar. Se ejecuta dentro de Postgres, lo que significaba que podíamos construir una red de co-visualización sin tener que levantar un clúster dedicado de Neo4j ni aprender un manual operativo completamente nuevo. Para un equipo pequeño sin un ingeniero de fiabilidad de bases de datos, esa distinción era enormemente importante.

Por qué una extensión es mejor que una nueva base de datos

Añadir una base de datos de grafos a tu stack es fácil en una pizarra y costoso en producción. Necesitas nuevos paneles de monitoreo, nuevos procedimientos de respaldo, nuevos pools de conexiones y nueva lógica de failover. AGE evita todo eso porque vive dentro de tu instancia de Postgres existente.

Hay cuatro razones prácticas por las que esto nos funcionó.

  • Sin nueva infraestructura. Debido a que AGE es una extensión, tu programación actual de pg_dump, tus réplicas existentes y tus controles de salud estándar de Postgres seguirán funcionando. No necesitas convencer al equipo de operaciones para que cuide otro almacén de datos.
  • Cargas de trabajo mixtas en una sola consulta. AGE te permite escribir Cypher dentro de SQL. Puedes ejecutar un recorrido de grafo para encontrar videos candidatos y luego unir ese conjunto de resultados con tu tabla relacional de users para aplicar restricciones de contenido regional, o con una tabla relacional de sponsorships para reducir la prioridad de ciertos canales. Un solo viaje de ida y vuelta. Dos lenguajes de consulta cooperando.
  • Portabilidad. Cypher es un lenguaje de consulta de grafos abierto y bien documentado. Si DailyWatch crece más allá de AGE y necesita migrar a Neo4j o Memgraph más adelante, la lógica de la consulta se transfiere con una reescritura mínima. No estás atrapado en un dialecto propietario.
  • Compatibilidad. Debido a que los datos viven finalmente en PostgreSQL, tus herramientas existentes de PHP o Python no cambian. Te conectas con el mismo controlador, manejas las mismas cadenas de conexión y obtienes las filas de la misma manera. La lógica del grafo reside en la capa de consulta, no en la capa de aplicación.

Modelado de co-visualización

La implementación es sencilla. Definimos nodos para las entidades que nos interesaban: Video y Channel. Luego definimos aristas para las relaciones entre ellos. Una arista PUBLISHED vincula un Channel con un Video. Una arista CO_VIEWED vincula un Video con otro, portando una propiedad weight que representa con qué frecuencia aparecieron los dos videos en la misma sesión de visualización.

Este modelo captura algo que el SQL basado en etiquetas no puede: la estructura implícita