List the key functional requirements for the system (Ask the AI for hints if stuck)...
List the key non-functional requirements (performance, scalability, reliability, etc.)...
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
Assuming 100 million DAU, and on average, each user views and listens to broadcasts 5 times a day, and uploads/shares 1 podcast. also assume peak read QPS is twice of average QPS.
The peak write QPS = 100 million DAU * 1 write per day / 100k seconds per day * 2 = 2k
The peak read QPS = 10k
On storage side, we need to store user metadata, podcast metadata and podcast contents.
For podcast, given we will have 100 million new podcasts per day, and each podcast needs around 2kb for metadata storage, and 50MB for the actual audio in different audio qualities, on each day, we will need additional:
100 million * 2kb = 200GB of metadata storage
and 100 million * 50MB = 5PB of media storage
For data that hasn't been accessed for 1 year, we will put them in cold storage.
On network bandwidth side, assuming at peak, 40% of DAU will be concurrent and listen to podcasts. And each second, 1MB of data is transferred for each user.
The network bandwidth needed is:
40 million * 1MB = 40TB per second
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
For users to publish a podcast:
POST v1/publish_podcast {
user_id: UUID,
publish_time: Timestamp,
podcast_category: String,
podcast_name: String,
podcast_description: String,
podcast_metadata: String,
channel_id: UUID
}
This returns a secured link for uploading the data to CDN to user.
For users to share a podcast:
POST v1/share_podcast {
user_id: UUID,
podcast_id: UUID,
audience_groups: String
}
For users to view details of a podcast:
GET v1/view_podcast {
user_id: UUID,
podcast_id: UUID
}
For users to stream a podcast for playback:
GET v1/playback_podcast {
user_id: UUID,
podcast_id: UUID
}
This returns a signed URL to listen to podcasts from CDN.
For users to subscribe to a podcast channel:
POST v1/subscribe_channel {
user_id: UUID,
channel_id: UUID
}
For content creators to create a channel:
POST v1/create_channel {
user_id: UUID,
created_at: Timestamp,
broadcast_ids: List
channel_name: String,
channel_description: String,
channel_metadata: String
}
For users to search for podcasts:
GET v1/search_broadcast {
user_id: UUID,
search_keyword: String,
filters: List
}
For users to browse broadcast feed:
GET v1/browse_podcast_feed {
user_id: UUID
}
For content creators to read podcast analytics:
GET v1/view_podcast_trends {
user_id: UUID,
podcast_id: UUID
}
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
In this design, we have a write path and a read path. For both paths, all requests first go through load balancers, and then an API gateway. Load balancers distribute traffic using consistent hashing, while API gateway enforces authorization, authentication and rate limiting.
On the write path, here's a sequence of events to upload/share a broadcast:
On the read path:
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
In this design, there are several contents we need to store:
For the podcast/channel/channel subscription/user identity metadata, we want to use a relational database. The benefits are:
The tradeoffs are:
The tradeoffs are acceptable in our use case.
Here are some sample data schemas:
table users {
user_id: UUID,
user_name: String,
user_email: String,
user_password_hashed: String,
user_type: String,
user_joined_at: Timestamp,
user_metadata: String
}
table podcasts {
podcast_id: UUID,
podcast_name: String,
podcast_description: String,
podcast_uploaded_at: String
podcast_metadata: String
}
table channels {
channel_id: UUID,
channel_name: String,
channel_description: String,
channel_uploaded_at: String
channel_metadata: String
}
table channel_podcast_link {
channel_id: UUID,
podcast_id: UUID,
podcast_added_at: Timestamp
}
table channel_user_subscription {
user_id: UUID,
channel_id: UUID,
user_subscribed_at: Timestamp
}
To speed up read latency, we will use a redis cluster as the caching layer.
We will use a read-aside cache. For writes, we write directly to database and invalidate the corresponding cache entry. For reads, we read from cache. If not available, we fetch latest data from database and populate cache with TTL. The benefits are:
The tradeoffs are:
In the redis cache, we will store following data with TTL:
To ensure 99.99% availability and low read latency in case of cache miss, we will create read replicas for database. For any write, we will write to the primary database, which asynchronously propagate to the read replicas. In case of primary failure, any read replica can continue to serve traffic. This is an eventual consistency approach. For any user who has made a recent write, we ensure that their subsequent read traffic is being served by the primary write replica, so they can always see their own writes.
For the actual audio of podcasts, we will use S3 object storage to store the media files, with local CDNs as caching layer.
For the analytics events, we will use an OLAP like snowflake to store the raw events as well as aggregate results for analysis.
For the podcast/channel metadata for searches, we will use an ElasticSearch cluster to store the metadata.
Podcast metadata lives in Postgres, not a NoSQL store, and that's deliberate:
channel_id (consistent hashing) so all episodes of a channel co-locate. The hot "show page" query is a single-shard lookup — no cross-shard joins on the hot path.Sample backup & recovery:
pg_basebackup snapshots to S3, retained 30 days.Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
In this design, there are several contents we need to store:
For the podcast/channel/channel subscription/user identity metadata, we want to use a relational database. The benefits are:
The tradeoffs are:
The tradeoffs are acceptable in our use case.
Here are some sample data schemas:
table users {
user_id: UUID,
user_name: String,
user_email: String,
user_password_hashed: String,
user_type: String,
user_joined_at: Timestamp,
user_metadata: String
}
table podcasts {
podcast_id: UUID,
podcast_name: String,
podcast_description: String,
podcast_uploaded_at: String
podcast_metadata: String
}
table channels {
channel_id: UUID,
channel_name: String,
channel_description: String,
channel_uploaded_at: String
channel_metadata: String
}
table channel_podcast_link {
channel_id: UUID,
podcast_id: UUID,
podcast_added_at: Timestamp
}
table channel_user_subscription {
user_id: UUID,
channel_id: UUID,
user_subscribed_at: Timestamp
}
To speed up read latency, we will use a redis cluster as the caching layer.
We will use a read-aside cache. For writes, we write directly to database and invalidate the corresponding cache entry. For reads, we read from cache. If not available, we fetch latest data from database and populate cache with TTL. The benefits are:
The tradeoffs are:
In the redis cache, we will store following data with TTL:
To ensure 99.99% availability and low read latency in case of cache miss, we will create read replicas for database. For any write, we will write to the primary database, which asynchronously propagate to the read replicas. In case of primary failure, any read replica can continue to serve traffic. This is an eventual consistency approach. For any user who has made a recent write, we ensure that their subsequent read traffic is being served by the primary write replica, so they can always see their own writes.
For the actual audio of podcasts, we will use S3 object storage to store the media files, with local CDNs as caching layer.
For the analytics events, we will use an OLAP like snowflake to store the raw events as well as aggregate results for analysis.
For the podcast/channel metadata for searches, we will use an ElasticSearch cluster to store the metadata.
Podcast metadata lives in Postgres, not a NoSQL store, and that's deliberate:
channel_id (consistent hashing) so all episodes of a channel co-locate. The hot "show page" query is a single-shard lookup — no cross-shard joins on the hot path.Sample backup & recovery:
pg_basebackup snapshots to S3, retained 30 days.In this design, we have a write path and a read path. For both paths, all requests first go through load balancers, and then an API gateway. Load balancers distribute traffic using consistent hashing, while API gateway enforces authorization, authentication and rate limiting.
On the write path, here's a sequence of events to upload/share a broadcast:
On the read path:
For the CDN/redis reads, if popular cache entries expire, we could end up with a thundering herd.
To mitigate this, we can adopt following approaches: