0% completed
YouTube Likes Counter: High-Level Design
On This Page
Step 5: High-Level Design
Step 5: High-Level Design
The system is made of several parts, and each part runs on its own. Together they take many requests at the same time, store the data, and do work in the background. Two kinds of requests move through these parts: writes that record a reaction, and reads that fetch counts. Keep that split in mind, because each part below serves one kind or both. We walk through the parts in the order a request meets them. The diagram below shows how they connect.
Clients (web and mobile). Everything begins with a user on the YouTube website or app. When the user clicks like or dislike on a video or comment, the client sends a write request to the backend. When the user views a video or comment, the client asks for the current like and dislike counts. It also asks whether this user has reacted.
API Gateway and Load Balancer. Every request passes through this layer first. It routes each request to the right service cluster. It does two more jobs before any application code runs. It applies coarse rate limiting, for example blocking one IP address that sends too many requests. And it handles auth, meaning it checks that the user is logged in. A request that fails these checks stops here and never reaches the service.
Like/Dislike Service. Behind the gateway sits the core application service. It exposes the endpoints for liking, disliking, and reading counts. Many instances of it run at once, because one instance cannot take this much traffic.
On a like or dislike request, the service authenticates the user and applies the reaction logic. It checks whether the user has already reacted, and it handles a toggle from one reaction to the other. Then it records two things in a database. First, the user's action, like "Adam liked <content_id>". Second, the change to the like or dislike count of that content. The next two parts of the design exist to hold these two records.
On a get count request, the service fetches the like and dislike counts for the video or comment. Those come from a cache or from the database. It also looks up the requesting user's own reaction, so the client can highlight the right button.
The service is stateless, meaning it keeps no data in memory between requests. That choice is what lets it scale: to take more traffic, we add instances. If one instance fails, the others take its traffic.
Database for likes and dislikes (the user-action DB). The first record, the user's action, is stored here. This is a write-optimized database. It stores the latest like or dislike of each user on each video or comment. We use a distributed NoSQL database like Apache Cassandra or DynamoDB.
Each write is an upsert, meaning it inserts a new record or updates the one already there. A first like creates the record, and a toggle from like to dislike updates it. This record is what lets the service tell a new reaction from a repeated one. It holds the reaction the user already has. Doing that check correctly while two requests run at the same time is its own problem, and Step 8 solves it.
We choose NoSQL for scale and for high throughput on writes and reads. Cassandra, for example, is built to grow across many servers with no single point of failure. It is also optimized for a very high write rate. Facebook used it to handle billions of writes per day.
For high availability, this database is distributed and replicated across regions, meaning each record is copied to more than one region. Cassandra and DynamoDB are multi-master or leaderless, so any region can accept a write. The copies then become the same after a short delay, which is called eventual consistency. Each write is local to its region, so it is fast and stays available. It replicates to the other regions in the background. So a write never waits for the copies to agree. If one region's database goes down, the data still exists elsewhere.
Counts Store (aggregated counts). That leaves the second record, the change to a count. The counts themselves are kept here. Two stores hold the same numbers for different reasons, and the rest of this case study uses both names. Redis serves every count read, keyed by content ID, because 250,000 reads per second cannot be sent to a database. ContentStats, a NoSQL table, holds the durable copy. Redis is refilled from it after a cache miss or a node failure, and the recount job in Step 10 corrects it. Redis is the read path; ContentStats is the record.
Next: Step 6, which defines the two tables this design needs.
Reading Progress
0%
On This Page
Step 5: High-Level Design