Our recommendation logic at ViralVidVault used to be a 280-line SQLite query with six self-joins. Every time a European visitor finished a viral clip, we wanted to answer one question: what should play next? The honest version of that question is a graph traversal — "find videos watched by people who watched this video, weighted by recency and region" — but we were forcing it through a relational engine that treats relationships as join tables. The query took 400ms on a warm cache and timed out on cold ones. Worse, adding a new signal (creator affinity, tag overlap, watch-completion rate) meant another join and another 50ms.

The relational model wasn't wrong, it was just the wrong shape for the data. Recommendations are inherently a graph problem: users, videos, creators, and tags are nodes, and "watched", "published", and "tagged" are edges you want to walk. So we moved that one subsystem to SurrealDB, kept everything else on our PHP 8.4 / SQLite WAL stack, and the next-video query dropped to a consistent 12-30ms. This post is the practical version of how that works, including the GDPR constraints that shaped the schema.

Why a graph database for "watch next"

The core recommendation pattern is collaborative filtering by traversal. Given video A, you want: