Three services that all answer one question β "what should this user see, and when?" A recommendation feed the
user pulls, notifications the backend pushes, and a content management system whose article-author-tag-category web finally makes graph databases the
obvious choice. All three are wired together by the same change-data-capture-into-Kafka backbone.
π§
How to read this
This page merges three small course sections that share a spine. Feed
and Notifications
are near-twins β both react to a new row in VideoMetadata, both fan it out to subscribers β the difference is pull vs. push. CMS
is a standalone argument that builds, step by step, to why a graph database
beats both relational and document stores for deeply connected content. Anything marked [+]
is context I added; everything else is rewritten from the seven teaching lessons.
Part A Β· Video / News Feed Service
01
The feed starts with Change Data Capture
The VOD home page pulls its feed from an API. That feed has two sources: (1)
videos from channels the user follows, and (2)
videos matched to the user's watch behavior (plus trending/popular). Both compute their results into a
feed cache; the API reads from that cache on request.
Start with source (1). When a channel uploads a video, a row lands in VideoMetadata
β but how does the system know
a row was added? That's Change Data Capture (CDC): monitoring a data source for changes and streaming them in real time to another service. The lesson weighs
four ways to do it:
Doesn't fit β we add new rows, not update existing ones; and frequent polling loads the table.
Table snapshots, compared over time
Storage-intensive; won't scale with heavy writes.
Read the DB transaction log
Viable β changes are read from the log.
Trigger an event on change
β push to Kafka [+] chosen
The approach used: a write triggers an event that moves the change to a Kafka queue
asynchronously.
π
Finding a channel's subscribers, fast
Once the video streams into Kafka, take its ChannelId
and find the channel's subscribers. Instead of querying the ChannelSubscription
bridge table every time, cache it in a key-value store (Redis): key = ChannelId, value = list of subscriber UserIds. A module called the Channel Subscription
Feed
polls the Kafka queue and pushes the video into each subscriber's recommendation feed.
02
The recommendation object, in a key-value store
Store each user's feed in a key-value store
for O(1) access: key = UserId, value = a list of VideoRecommendation objects.
class VideoRecommendation {
int id;
List<Integer> videoIds;
boolean polledByUser = false;
// ...other fields
}
When the Channel Subscription Feed pulls, say, 25 videos to recommend to a user, it adds them to
videoIds, sets polledByUser = false, wraps it in a VideoRecommendation object, and appends it to that user's list. When the user requests
recommendations, one or more objects are returned, polledByUser
flips to true, and once sent they can be discarded from the cache
to free space.
The second source: watch behavior
A Recommendation Engine
computes videos from viewing behavior (trending/popular included). It ingests VideoMetadata
via the same CDC-into-Kafka
flow for new videos. To surface trending content it must re-query the table for updated views/likes/comments β
so keep a copy of VideoMetadata
the engine queries continually, sparing the origin table load. The engine writes its results into the same
VideoRecommendation list. (The actual matching is machine learning, out of scope.)
Figure 1 β
The feed pipeline: CDC β Kafka β two consumers β a per-user feed cache the home-page API pulls from. Original
diagram after Figures 6.2β7.0.
[+]
Swap "video" for "image" and this is exactly how a Pinterest or Instagram feed works too.
Part B Β· Push Notifications Service
03
Notifications: the same flow, reversed
The notification workflow mirrors the feed's: a new VideoMetadata
row triggers an event β Kafka queue β a Notification Service
polls it, takes the ChannelId, looks up subscribers in the key-value store, and generates a notification object per subscriber:
class Notification {
int id;
String notificationMessage;
Integer videoId;
String videoURL;
// ...other fields
}
These land in each user's notification feed in a key-value store (key = UserId, value = list of Notification
objects). The crucial difference from the feed: notifications are pushed, not pulled.
π‘
Push over a persistent connection
The backend pushes notification objects to the client over a persistent connection
(an HTTP push technology). Compare YouTube: refreshing the home page pulls
the recommendation feed, but new-video notifications are pushed. Notifications must arrive in real time as the event happens β you can't wait for the user to ask. What if
they never send a request? Or request a live-event notification after
the event ended? That defeats the purpose.
Figure 2 β
Same underlying pipeline, opposite delivery: the feed is pulled on request; notifications are pushed over a
persistent connection. Original diagram after Figure 7.1.
04
Two problems at ESPN/YouTube scale
Problem 1: resource costA channel with 50M subscribers uploading one video means 50M real-time notifications β for one channel.
YouTube has ~110M channels (~50M active). A server can hold only so many open connections.
Problem 2: user experienceFollow 100+ active channels averaging 5 videos/week each = 500 notifications/user/week (higher with
shorts). That overwhelms people β bad UX.
π
The fix: prioritize notifications
Don't bombard viewers with every event β send a curated few catered to their browsing/watch behavior, plus
the important ones. This is exactly why YouTube introduced the bell icon: you only get notifications for channels where you pressed the bell. If a system has too many notifications
to handle, introduce a similar opt-in, or prioritize by watch behavior with machine learning.
Part C Β· Content Management Service (CMS)
05
Picking a data model: knowledge graphs
On a sports site, content is videos (already covered) plus articles with images. The CMS has two sub-services: a Content Storage Service
(takes content from writers, persists it) and a Content Delivery Service
(serves it to readers). An article has a title, summary, content, images, categories, tags, and one or more
authors β with one-to-many relationships to categories (sport, tournament), tags (club, country, game type), and
authors.
Which database? The lesson eliminates two candidates:
Candidate
Why it struggles
Document DB (denormalized)
The main queries are "articles by author X / category Y / tag Z." To answer fast you'd need a
separate collection per
category, tag, and author β with massive data overlap (a baseball article duplicated across all its
categories' and tags' collections). Undesirable.
Relational
A "better choice" than documents here β but deep, connected queries demand many joins, and latency
climbs with relationship depth.
Graph (knowledge graph)[+] chosen
Store articles, categories, tags, authors as nodes
with edges
as relationships. Purpose-built for connected data.
πΈοΈ
Knowledge graphs in the wild
They power CMSs like Wikipedia and Bloomberg with big, complex data. A Wiki article on a historical war
links continents, countries, battles, locations, commanders, allies, hostile forces β letting a reader
wander from one battle to related events, people, and nations. That deep interlinking drives engagement and
retention. Google and Facebook maintain knowledge graphs to surface relevant data from petabytes at low
latency. Modeling all of history's wars in a document or relational DB would be a nightmare β
denormalization exhausts disks and joins send latency "soaring like a NASA rocket."
06
Modeling the content graph
A graph model has nodes, node properties, directed relationships, and labels. Authors (Ron, Jim, Nancyβ¦), categories (Baseball, Football, Cricket, Boxing), tags (India, Ashes, Yankees,
Cubsβ¦), and articles all become nodes with directed edges between them. In Neo4j, nodes are created/queried with
the Cypher
query language (Cypher is to Neo4j what SQL is to a relational DB).
Figure 3 β
The content graph: query by traversing edges from a node (AUTHORED, IN CATEGORY, TAGGED) β no joins. Original
diagram after Figure 7.3.
Two query types
Query 1
β "all articles by author Ron / in category Baseball / with tag Yankees." Locate the node and traverse its
edges (AUTHORED, HAS AN ARTICLE, etc.).
Query 2
β "articles by Ron and
in Baseball and
tagged Yankees." Locate Ron, traverse to his articles, then scan those articles' relationships to the category
and tag nodes.
β‘
The payoff: no joins
Query 2 on a relational DB needs multiple joins β and the deeper the scan, the more joins. The graph needs
none, so responses are dramatically quicker. That's the whole reason graphs win for connected data.
Tuning the graph
Redundant properties
β if Query 2 is very frequent, add categories/tags as properties
on article nodes (as well as separate nodes), shrinking scan depth. Writes update multiple places, fine since
tags rarely change.
Relationship properties
β edges carry metadata too: an Author βWRITES ABOUTβ Baseball
edge can note long-form / summaries / tweets.
Labels
β group nodes by role (e.g. label all clubs playing football in Asia) to speed frequent queries.
Node properties
β authors carry id/name/designation/experience; articles carry id/length/content/image URLs. In Neo4j
properties are stored key-value (node id β properties). If property data is large/complex, keep it in a
document or relational DB and store a pointer
in the node β a polyglot
model.
π―
Graphs are the basis of recommendations
Connected data links nodes that sit far apart. If Shreya likes author Ron, and Ron reads authors X and Y,
the graph can recommend X and Y's articles to Shreya. Pulling that off relationally in near-real-time would
need an abysmal number of joins β this is where graphs shine.
07
CMS core architecture
Authors write in a UI and hit publish. The article goes to an Article Processor, which:
Triggers a serverless event that, via a Kafka queue, sends the images to an Image Processing Service
β the queue loosely couples the two so image processing works at its own pace.
Persists the article to the graph database.
Meanwhile the Image Processing Service compresses/enhances images and stores them in a cloud object
bucket.
On success, another serverless event updates the image URLs
as properties on the article node in the graph DB.
Once all events succeed, the Article Processor returns a publish-success response, and published content
is cached per business logic.
π
Async or sync β the result is the same
You could also do it synchronously: optimize images, store them, update the article object with URLs, then
persist everything to the graph DB. It doesn't matter β in both cases the Article Processor won't send
success until the node's properties carry the image URLs.
Content delivery
reuses the VOD pattern exactly: reader β edge cache (hit β served) β regional cache β content-serve service at
the origin. No need to reinvent it.
08
Recap
Feed via CDCA new VideoMetadata row triggers an event β Kafka. Chosen over timestamp polling / snapshots / log
reads. Redis maps channel β subscribers.
Recommendation objectKey-value store: UserId β list of VideoRecommendation. polledByUser flips on send, then discarded. A
Recommendation Engine adds behavior-based picks.
Notifications = pushSame CDCβKafka pipeline, but a Notification Service pushes objects over a persistent connection β
real-time, not pull.
Prioritize at scale50M-subscriber channels + 500 notifications/user/week β curate. YouTube's bell icon is opt-in
prioritization; ML can rank the rest.
CMS β graphArticles/authors/categories/tags are deeply connected. Documents duplicate; relational over-joins; a
knowledge graph fits (Wikipedia, Bloomberg).
Graph tuning + archRedundant properties, edge metadata, labels, polyglot pointers. Article Processor + Image Service via
Kafka; delivery reuses the VOD edge stack.
π‘
The single idea underneath it all
These three services are one idea seen from three angles: a write in one place has to become the
right experience for millions of specific people.
Change Data Capture turns a database write into a stream; Kafka fans that stream out without coupling
anyone; and then the shape of the destination
decides everything. A feed the user checks β cache it and let them pull. A moment they can't miss β push it,
but curate so you don't drown them. Content whose value is
its connections β store it as a graph so "related" is a traversal, not a pile of joins. Match the delivery
to how the human actually consumes it.
09
Further reading
Both external references resolve live. The author's own course cross-links (message queues, persistent
connections, document/graph DBs, polyglot persistence) are omitted.
[1]
Cypher
Manual β Neo4jWhy read it: the query language used to create and traverse the content graph in Sections
06β07.
[2]
Graph traversal β
WikipediaWhy read it: DFS/BFS, the algorithms behind how graph queries walk the content graph
(Section 06).