Back to Main
Learn System Design
Question Breakdowns
Advanced Topics
Common Problems
Design YouTube Top K
Scaling Writes
Stefan Mai
hard
Published Jul 21, 2024
Understanding the Problem
Let's assume we have a very large stream of views on YouTube (our stream is a firehose of VideoIDs). At any given moment we'd like to be able to query, precisely, the top K most viewed videos for a given time period (say 1 hour, 1 day, 1 month, all time) together with their counts.
Our interviewer might give us some quantities to help us understand the scale of the system: Youtube Shorts had 70 billion views per day and approximately 1 hour of Youtube content is uploaded every second. Big!
Functional Requirements
Core Requirements
- Clients should be able to query the top K videos (max 1000) for a given time period.
- Time periods should be limited to 1 {hour, day, month} and all-time.
Below the line (out of scope):
- Arbitrary time periods.
- Arbitrary starting/ending points (we'll assume all queries are looking back from the current moment).
Non-Functional Requirements
Core Requirements
- We'll tolerate at most 1 min delay between when a view occurs and when it should be tabulated.
- Our results must be precise, so we should not approximate. (Note: This would be unusual for most production systems)
- Our system should be able to handle a massive number (TBD - cover this later) of views per second.
- We should support a massive number (TBD - cover this later) of videos.
- We should return results within 10's of milliseconds.
- Our system should be economical. We shouldn't need a 10k host fleet to solve this problem.
Here's how it might look on your whiteboard:
Requirements
Scale Estimation
We've earmarked two quantities important to our design: (a) the number of views per second, and (b) the total number of videos. The first will help us understand the overall throughput of the system while the second is important for bounding the storage we'll need.
First let's look at throughput:
Woo, that's a lot. We're definitely going to need to look for ways to shard this across many different hosts.
Now, let's talk storage. First we need the number of videos:
With that let's estimate how big a naive table of IDs and counts would be:
Ok, probably something we can keep in memory if we're clever, especially if we use a number of hosts.
The Set Up
Planning the Approach
Based on our requirements, we know we're going to make some observations for our interviewer:
- First, we need to index data from a very high volume stream. Most quantities will need to be precomputed in order to meet the latency requirements.
- Next, problems like this typically have bottlenecks that are hidden behind bottlenecks: solving one problem creates (at least) one more. So we'll aim to solve the simplest problem first, and then add complexity as we go.
- Finally, we'll note that the sliding time window adds more challenge. So we'll start with all-time and then try to figure out the rest.
Our rough plan is thus:
- Generate a basic (but not scalable solution) to the all-time top K problem.
- Solve the primary issues of our basic solution.
- Add a solution for the time period inputs.
- Deep dive remaining bottlenecks until we run out of time.
Defining the Core Entities
In our problem, we have some basic entities we're going to work with to build our API:
- Video
- View
- Time Window
From a conceptual perspective this problem is straightforward so we're not going to spend any more time here. We might even skip this section to save time.
API or System Interface
Our API guides the rest of the interview, but in this case it's really basic too! We simply need an API to retrieve the top K videos.
We're not going to dawdle here and keep moving on to the meat of the interview.
High-Level Design
1) A Basic Solution for All-Time
Let's start with a simple solution for all-time top K videos which we'll build on a gigantic single host, then we can start to whittle away at optimization.
We can do this by maintaining a table of video IDs and counts. This gets us an up-to-date count of every video, but iterating over all 4B keys to find the largest values is untenable, so we'll keep a heap of the top K videos which we can update with each increment. The vast majority of views will never touch this heap since they'll be below the threshold of the top 1000 (the max K we established in our functional requirements).
Basic Solution
The basic function is this: when a request comes in, we atomically increment the counter in the hash table with the incoming ID. We retrieve the updated count and test it against the floor of our heap. If the count is higher than our floor (i.e. the video belongs in the top 1,000) we update/insert it into the heap and heapify. Our clients query directly from that heap to retrieve the top K videos.
This is really simple and fast because we run it on a single host. And while this is possible conceptually, in memory, on a single host, we wouldn't want to do that. First, because the throughput we can support is likely more than an order of magnitude shy of the 700k TPS we need and secondly because that host becomes a single point of failure. What to do here?
2) Primary Issues of the Basic Solution
We have two issues we need to address: how to maintain reliability in the presence of failures and how to scale the write throughput of the system. Let's talk about them in order.
In order for our system to be reliable, we need to be able to gracefully handle node failures. In our single-node system we're going to be in the job search again if that host fails. No good.
Ok, with some replicas and snapshots we're in a much more fault-tolerant state. Next, we need to scale the write throughput of our system as our replicas don't solve for the problem of having a massive firehose of incoming data. Your mind should immediately go to sharding/partitioning here.
Pattern: Scaling Writes
Sharding and partitioning are your first line of defense when it comes to scaling writes. The Scaling Writes pattern describes repeatable strategies you can use across problems where write volume is high.
Ok cool, now we have a basic in-memory solution which is both fault-tolerant and (somewhat) scalable. But we haven't solved all our functional requirements yet. On to those pesky time windows.
Potential Deep Dives
1) Handling Time Windows
While our "All-Time" solution conveniently can aggregate views forever, to handle time windows we need to age out views that happened outside that window. As an example, if a video got a single view at time T=0, if our time window is 1, by T=2 we need to make sure that video has a count of 0.
One advantage we have is that the time windows we're working with are fixed and small: we only have 3. One disadvantage is they are very different granularities: from 1 minute to 1 month.
This is complicated so our best strategy is to start with something basic and probably bad then use it as inspiration to try come up with alternative solutions.
Your interviewer is going to be looking for how you can think through this problem, not (necessarily) that you get the optimal answer. Identifying pinch points, solving them, and not getting stuck is critical. But if you can think of the best solution go for it!
2) Large number of incoming requests
So far we've been talking about how to handle a lot of views/writes, but what about reads? Given we have 1 minute between when a view happens and when it needs to be tabulated, the most natural solution is to add a cache. We can put a 1 minute TTL on the cache so results are never more stale than our requirement. When a request comes in, we can either serve it from cache or we query all Counters for the given heap of the request and then store the merged values back in the case.
Full design with cache
What is Expected at Each Level?
Ok, that was a lot. You may be thinking, "how much of that is actually required from me in an interview?" Let's break it down.
Mid-level
Breadth vs. Depth: A mid-level candidate will be mostly focused on breadth (80% vs 20%). You should be able to craft a high-level design that meets the functional requirements you've defined, but many of the components will be abstractions with which you only have surface-level familiarity.
Probing the Basics: Your interviewer will spend some time probing the basics to confirm that you know what each component in your system does. For example, if you add an API Gateway, expect that they may ask you what it does and how it works (at a high level). In short, the interviewer is not taking anything for granted with respect to your knowledge.
Mixture of Driving and Taking the Backseat: You should drive the early stages of the interview in particular, but the interviewer doesn't expect that you are able to proactively recognize problems in your design with high precision. Because of this, it's reasonable that they will take over and drive the later stages of the interview while probing your design.
The Bar for Top K: For this question, an Mid-Level candidate will be able to come up with an end-to-end solution that probably isn't optimal. They'll have some insights into pinch points of the system and be able to solve some of them. They'll have familiarity with relevant technologies.
Senior
Depth of Expertise: As a senior candidate, expectations shift towards more in-depth knowledge — about 60% breadth and 40% depth. This means you should be able to go into technical details in areas where you have hands-on experience. It's crucial that you demonstrate a deep understanding of key concepts and technologies relevant to the task at hand.
Advanced System Design: You should be familiar with advanced system design principles. For example, knowing about how to use consistent hashes to elastically scale partitioned data. You'd also be expected to understand how log-based event streaming (e.g. like implemented via Kafka or Redis Streams) functions. Your ability to navigate these advanced topics with confidence and clarity is key.
Articulating Architectural Decisions: You should be able to clearly articulate the pros and cons of different architectural choices, especially how they impact scalability, performance, and maintainability. You justify your decisions and explain the trade-offs involved in your design choices.
Problem-Solving and Proactivity: You should demonstrate strong problem-solving skills and a proactive approach. This includes anticipating potential challenges in your designs and suggesting improvements. You need to be adept at identifying and addressing bottlenecks, optimizing performance, and ensuring system reliability.
The Bar for Top K: For this question, a Senior candidate should be able to come up with an end-to-end solution that is near optimal. They'll identify most bottlenecks and proactively work to resolve them. They'll be familiar with relevant technologies and might even weigh the pros and cons of each.
Staff+
Emphasis on Depth: As a staff+ candidate, the expectation is a deep dive into the nuances of system design — I'm looking for about 40% breadth and 60% depth in your understanding. This level is all about demonstrating that, while you may not have solved this particular problem before, you have solved enough problems in the real world to be able to confidently design a solution backed by your experience.
You should know which technologies to use, not just in theory but in practice, and be able to draw from your past experiences to explain how they'd be applied to solve specific problems effectively. The interviewer knows you know the small stuff (caches, key-value stores, etc) so you can breeze through that at a high level so you have time to get into what is interesting.
High Degree of Proactivity: At this level, an exceptional degree of proactivity is expected. You should be able to identify and solve issues independently, demonstrating a strong ability to recognize and address the core challenges in system design. This involves not just responding to problems as they arise but anticipating them and implementing preemptive solutions. Your interviewer should intervene only to focus, not to steer.
Practical Application of Technology: You should be well-versed in the practical application of various technologies. Your experience should guide the conversation, showing a clear understanding of how different tools and systems can be configured in real-world scenarios to meet specific requirements.
Complex Problem-Solving and Decision-Making: Your problem-solving skills should be top-notch. This means not only being able to tackle complex technical challenges but also making informed decisions that consider various factors such as scalability, performance, reliability, and maintenance.
Advanced System Design and Scalability: Your approach to system design should be advanced, focusing on scalability and reliability, especially under high load conditions. This includes a thorough understanding of distributed systems, load balancing, caching strategies, and other advanced concepts necessary for building robust, scalable systems.
The Bar for Top K: For a staff+ candidate, expectations are high regarding depth and quality of solutions, particularly for the complex scenarios discussed earlier. A staff candidate will expand to cover deep dives that we haven't enumerated.
Test Your Knowledge
Take a quick 15 question quiz to test what you've learned.
Mark as read
The best mocks on the market.
Now up to 25% off
On This Page
Understanding the Problem
Functional Requirements
Non-Functional Requirements
Scale Estimation
The Set Up
Planning the Approach
Defining the Core Entities
API or System Interface
High-Level Design
1) A Basic Solution for All-Time
2) Primary Issues of the Basic Solution
Potential Deep Dives
1) Handling Time Windows
2) Large number of incoming requests
What is Expected at Each Level?
Mid-level
Senior
Staff+
Schedule a mock interview
Meet with a FAANG senior+ engineer or manager and learn exactly what it takes to get the job.
CasualBrownCrow397
Hi Stefan, can you please share the resources you use to come up with solutions for system design. I know that experience and company blog posts plays a vital role here but it would help us all if you could share resources that you lean on when it comes to system design.
1
Stefan Mai
Experience was crucial for me. The long way to learn system design is to familiarize yourself with fundamentals: where does data live, how does a processor work, how does the networking stack work, etc. and slowly use that to build up. The shorter path is to read how people solved problems and work backwards. The best engineers I know use a combination of both: they can glean clever approaches from recent applications but can fall back on deep fundamentals to reason through them.
24
ContinuedChocolateScorpion255
Can you update the elastic partitioning diagram? I believe it still shows fixed partitioning.
0
Stefan Mai
Good call-out, fixing.
0
SeriousScarletLungfish451
For Meta, would this fall under only system design (as opposed product design)?
0
ZoophagousCyanGuppy118
Unfortunately, I was asked this question on my product architecture.
0
dib
I am not able to wrap my head around how the falling edge is working. For falling edge we need to decrement our counter from all heaps, but couldn’t understand the following text ”For the falling edges we'll start reading from the stream with the appropriate offset (e.g. 1 hour, 1 month) and pause consuming when we get to an entry which is more recent than our current time minus that duration” Could you pls help me understand?
1
dib
So we will have 2 pointers for each window(1m, 1h and 1day). Let's call them head and tail. With each passing second we increment both pointers. when we increment the head pointer and if there is an event in that second we add it to all three buckets. at the same time we advance the tail pointer and if there was any event that happened during that point , we decrement it from all the 3 . buckets. Is my understanding correct?
0
Stefan Mai
Very close. You're only decrementing on the tail from the bucket that tail is associated with. Deploying a worked example shortly, reproducing it here for you.
Let's work an example. Assume that the following sequence of actions are going to take place:
Now we can walk through the state of the system at different time points to drive this home.
7
PetiteCoffeeScallop473
Hi Stefan, the solution is more like sliding windows, but we just need to maintain a falling edge timestamp it the queue, as time past, we remove the stale data point. right?
0
InjuredGrayVole692
is TSDB better choice to get the log and to feed to top-k as the dta is shorted over time? we can just query for the falling edge to current time
0
Stefan Mai
Yeah, we're maintaining a sliding window of times!
0
GivenApricotHalibut713
Not sure if this is explicitly called out, but I think we also need to maintain a third data structure in addition to heap and HashMap of counts. We need to have a chronologically ordered linked list of video view expiry times that are in the heap. When new view comes in, we first expire all the views for videos in the list that are before the current time window slot. We decrement count associated with the video in the HashMap and Heap and if count goes to 0, remove the video. Then if the view is going to be added to heap, we compute the views expiry time and add it to the list.
5
Yuxing Ma
One problem is that as time goes by, the count of a video could drop a lot within a short period of time. To get an accurate top K list, we need to scan through all the video counts from the table, and find a candidate, and push to the queue to kick out the video with low count. This is expensive
1
Daisy
Hi Stefan, your solution only updates the counts for the falling edges but not the heap.
0
Kavish Dhamija
thanks for reply, I somehow deleted the original question 😅 but building up on two pointers above at implementation is it equivalent to have a consumer for all time top-k which will increment counter for all types. Then for all other top-k we can have consumers which will read at there respective offset : if the timestamp at offset has a gap of more than hr min or day from current time offset will be moved by 1 and we do a decrement otherwise consumer will just wait for 1 min to poll again?
i.e the rising and falling edge aren't something we need to store for implementation?
1
InevitableAmaranthVulture961
Hey Stefan,
Regarding this example, the view at 00:40 should be for B, not C. Your explanation mentions that the counter for B increases from 1 to 2. You might want to correct the article "Detailed Explanation" section for "Use two pointers".
0
gopesh khandelwal
Thanks, this gave me a understanding of the concept as I was also confused in this. We can think of it as for each granularity we are maintaining a linked list to preserve the stream with the time at which the video count came, a HashMap to track counts of the video and a priority queue to maintain top K videos. When the head node moves out of the given window, we proceed forward the head pointer, remove the previous head node, and decrement the count of previous head node in the HashMap. While decrementing the count we would need to update the node in the Heap too which will take O(k) time to get the node and O(log k) time taken for the updation of the heap. We could optimise this further by using a sorted set, which reduces the complexity to O(log k). There is one more problem with this , when decrementing a video’s count, if it drops below the count of another video ranked lower in the list, we would need to promote that video which was ranked lower. We can make the size of sorted set to 2*k which will fix the above problem ( going with the extreme case when all the k videos are no longer the candidates).
3
drorbrook1
Hey Stefan, For falling edge when decrement our counter from all heaps, technically speaking - is that been done by consumeing another kafka event which we publish 1 hour or 1 day later?
2
EnthusiasticAzureBasilisk385
kafka allows to read different offsets from one topic by different consumer groups.
2
XerothermicBlueGibbon404
But that implies we move the counters and heaps to external store which is common to all consumers
0
TechnicalScarletJellyfish241
For elastic partition how does the heap works. a particular partition may not have an element on top 1000 as the count is low but over all the count may be high for a video id. For example floor of partition-1 for video-1 count may be 1000 and floor of partition-2 for video-2 may be 10001 but the heap did not add count of video-1 to heap as the count was 100 only. If we dont partition by video id how do we do merge k-sorted heap to get top K efficiently?
1
TechnicalScarletJellyfish241
I know there is something with zookeeper here but can you please clarify how this is handled
0
Stefan Mai
The top-k service needs to know how many shards exist and which servers are housing them. The shards/Counters each need to know which modulo they're owning. Zookeeper basically functions as the registry for this.
1
Stefan Mai
If each partition keeps at least k elements in its local heap, you're guaranteed to have the global top k when they're merged.
0
TechnicalScarletJellyfish241
That makes perfect sense to me in the "Fixed partition by id" solution what threw me off is if we do elastic partitioning the count for a specific video_id is scattered right? meaning the heap in each partition will not be accurate. If i want to know total count of a video view i have to 1st aggregate before deciding the local heap?
0
Stefan Mai
Not if you’re reading from a stream. Each shard owns a subset of video IDs and has the complete set of views for them.
2
Stefan Mai
This would be the case if e.g this was just processing the requests as they came in and during the resharding you had no way to recover views. Although interesting question on how you might handle that situation…
0
TechnicalScarletJellyfish241
Thanks a lot for answering. Make sense.
0
Anand Singh
@stefan - If each shard owns subset, won't we get into hot partition issue?
4
RubberGoldOpossum751
@stefan - I'm having a hard time justifying this. For simplicity assume 4 ads and 2 partitions/nodes. If we want top 2, we can hold a heap of just 1 per node, but if the top 2 ads are assigned to the same partition/node wouldn't we only store the top ad only. The other node will store the third largest. So the service in combination will return the top 1 and 3.
I suspect we'll need another aggregation for global top k, and each node will have to store at least K amount. Worse case is all the top K ads are assigned to the same partition.
0
Stefan Mai
If you want top 2, you need 2 in each heap.
1
RubberGoldOpossum751
Thanks for the quick reply! That's what I was thinking. This wasn't explicitly mentioned in the deep dives write up but on another glance I think it's implied by the TopK service to do the aggregation.
0
Stefan Mai
It's actually explicitly mentioned in the quote you have :)
2