Bei DailyWatch begann unser Panel für „Ähnliche Videos“ mit einer bescheidenen SQLite-Abfrage. Sie verknüpfte drei Tabellen, zählte überlappende Tags und lieferte eine sortierte Liste zurück. Für einen kleinen Katalog war das ausreichend. Ein Kochvideo mit den Tags „italian“ und „pasta“ brachte andere Videos mit denselben Tags zum Vorschein, und die Nutzer klickten darauf. Das Ergebnis wirkte relevant, weil die Metadaten sauber waren und die Bibliothek überschaubar war.
Dann wuchs der Katalog und die Erwartungen des Publikums änderten sich. Die Leute wollten nicht einfach nur mehr Videos mit identischen Tags. Sie wollten den Clip, den vierzig Prozent der Zuschauer unmittelbar nach dem aktuellen Video sahen. Sie wollten den Nischenkanal, der in denselben Spätabend-Viewing-Sessions immer wieder auftauchte, obwohl dessen Beschreibung nichts über das Startvideo sagte und seine Tags spärlich waren. Dies sind Verhaltensmuster, keine Metadaten-Übereinstimmungen. SQLite konnte diese nicht ausdrücken, ohne in ein unwartbares Geflecht aus Self-Joins und rekursiven Common Table Expressions zu verfallen. Jedes neue Signal, das wir zu modellieren versuchten – sei es Co-Viewership oder Session-Adjazenz –, erhöhte die Latenz und den kognitiven Aufwand. Wir hatten ein Graph-Problem, das in ein relationales Gewand gepresst wurde.
Ich habe die Empfehlungsschicht auf Apache AGE umgestellt.
Apache AGE ist eine PostgreSQL-Erweiterung, die openCypher-Graphabfragen zu der Datenbank hinzufügt, die Sie bereits betreiben. Es ist kein separater Server. Es ist kein Sidecar. Es läuft innerhalb von Postgres, was bedeutete, dass wir ein Co-View-Netzwerk aufbauen konnten, ohne einen dedizierten Neo4j-Cluster aufsetzen oder ein völlig neues operationales Playbook erlernen zu müssen. Für ein kleines Team ohne Database Reliability Engineer war dieser Unterschied enorm wichtig.
Warum eine Erweiterung eine neue Datenbank schlägt
Eine Graphdatenbank in den eigenen Stack zu integrieren, ist auf dem Whiteboard einfach, aber in der Produktion teuer. Sie benötigen neue Monitoring-Dashboards, neue Backup-Verfahren, neue Connection-Pools und eine neue Failover-Logik. AGE umgeht all das, da es innerhalb Ihrer bestehenden Postgres-Instanz lebt.
Es gibt vier praktische Gründe, warum das für uns funktioniert hat.
- Keine neue Infrastruktur. Da AGE eine Erweiterung ist, funktionieren Ihr aktueller
pg_dump-Zeitplan, Ihre bestehenden Replikate und Ihre Standard-Postgres-Health-Checks weiterhin. Sie müssen das Operations-Team nicht davon überzeugen, einen weiteren Datenspeicher zu betreuen. - Gemischte Workloads in einer Abfrage. AGE ermöglicht es Ihnen, Cypher innerhalb von SQL zu schreiben. Sie können einen Graph-Traversal ausführen, um Kandidaten-Videos zu finden, und das Ergebnisset dann mit Ihrer relationalen
users-Tabelle verknüpfen, um regionale Inhaltsbeschränkungen durchzusetzen, oder mit einer relationalensponsorships-Tabelle, um bestimmte Kanäle herabzustufen. Ein Round-Trip. Zwei Abfragesprachen, die zusammenarbeiten. - Portabilität. Cypher ist eine offene, gut dokumentierte Graphabfragesprache. Falls DailyWatch über AGE hinauswächst und später zu Neo4j oder Memgraph migrieren muss, lässt sich die Abfragelogik mit minimalem Umschreiben übertragen. Sie sind nicht an ein proprietäres Dialekt gebunden.
- Kompatibilität. Da die Daten letztendlich in PostgreSQL liegen, ändert sich Ihr bestehendes PHP- oder Python-Tooling nicht. Sie verbinden sich mit demselben Treiber, verwenden dieselben Connection-Strings und rufen Zeilen auf dieselbe Weise ab. Die Graph-Logik liegt in der Abfrageschicht, nicht in der Anwendungsschicht.
Modellierung von Co-Viewership
Die Implementierung ist unkompliziert. Wir haben Knoten für die relevanten Entitäten definiert: Video und Channel. Dann haben wir Kanten für die Beziehungen zwischen ihnen definiert. Eine PUBLISHED-Kante verknüpft einen Channel mit einem Video. Eine CO_VIEWED-Kante verknüpft ein Video mit einem anderen und trägt eine weight-Eigenschaft, die angibt, wie häufig die beiden Videos in derselben Viewing-Session auftauchten.
Dieses Modell erfasst etwas, das tag-basiertes SQL nicht leisten kann: die implizite Struktur
