Future Improvements:
total users = 1B
Active Users = 10% = 100M
On an average a person tweets 5 times a day = 500M tweets
Tweets per sec = 500 * 10^6/10^5 = 5000 tweets/sec
During peak time = 15000 tweets/sec [3x]
Read:Write Ratio = 100:1
Reads per sec = 500K tweets/sec
During peak time = 1500K tweets/sec
Per tweet gets an average 10 likes:
So total likes activity = 5B likes/day = 50K likes/sec, which can be around 200K likes/sec during peak times
Storage:
Tweet + metadata = 500 bytes / tweet
Total storage in a day = 500 * 10^6 * 500 = 250GB/day
total storage in a month = 7.5 TB/month
Total storage in a year = 90TB/year
Likes data will be 100 bytes including metadata
Total storage in a day = 5B * 100 bytes = 0.5TB/day
Total storage in a month = 15 TB/month
Total storage in a year = 180 TB/year
Follow: Each users follow 600 people on an average.
Each data will be 100 bytes
600 * 100 * 1B = 60 PB
User ID:
POST /v1/user/create-account
Request:
Idempotency-Key: uuid
Authentication: Bearer
{name, username, email, phone_no, etc....}
Response:
{success: True, user_id: uuid}
Follow/Unfollow
POST /v1/follow/{user_id}
Response:
{
success: True
}
POST /v1/unfollow/{user_id}
Response:
{
success: True
}
Tweet
POST /v1/tweet
Request:
Idempotency-Key: uuid,
Authentication: Bearer
{user_id: uuid, tweet: string}
Response:
{
success: True,
tweet_id: uuid,
curr_likes: 0
}
GET /v1/tweet/{tweet_id}
Response:
{
tweet_id: uuid,
tweet: string
curr_likes: 0
}
DELETE /v1/tweet/{tweet_id}
Response:
Status: 204
# Only the user who posted the tweet can delete it
Like:
POST /v1/tweet/like/{tweet_id}
Response:
{
success: True,
tweet_id: uuid,
curr_likes: 0
}
Unlike:
POST /v1/tweet/unlike/{tweet_id}
Response:
{
success: True,
tweet_id: uuid,
curr_likes: int
}
# This will only unlike a tweet, if the user had previously liked the tweet
GET /v1/tweet/likes/{tweet_id}
Response:
{
success: True,
tweet_id: uuid,
curr_likes: 0
}
Feed
GET /v1/feed/{user_id}
Response:
{
user_id: uuid,
feed: [
{tweet_id: uuid, user_id: uuid, content: string, curr_likes: int, created_at: timestamp}, {}, {}....
]
has_more: True
next_cursor: uuid
}
Profile
GET /v1/profile/{user_id}
Response:
{
user_id: uuid,
feed: [
{tweet_id: uuid, user_id: uuid, content: string, curr_likes: int, created_at: timestamp}, {}, {}....
]
has_more: True
next_cursor: uuid
}
This will be sorted in reverse chronological order of created at.
We can add APIs for pinning some comment, that will appear at top of the profile followed by the latest tweet.
All the requests are routed through Load Balancer and API Gateway (which also handles the rate limiting)
We have the following Services (Auth, User, Tweet, Like, Feed and Notification)
All the authentication related requests (login, logout etc) are handled by the auth service
User Service is responsible for creating new account, following a user, serving user profile.
When an user follows another profile, it is added to a Kafka, then workers update the Postgres DB.
This is used to create user feeds.
When a tweet is added, user Service also consumes the event, invalidates the existing cache.
Like Service is responsible for handling users liking the tweets. The like events are added to the Kafka (parititoned by tweet_id). An aggregator calculates the delta and flushed the data to DB.
Tweet Service handles all the tweets.
When an user creates a tweet, it is persisted in the DB and also emits a Kafka event which is consumed by the Feed and User Service.
Feed Service:
For users who have small number of followings (<5000), we fan out the tweets on write. Feed Service updates their feed tweet data.
With celebrity users, we fan out the tweet on read, in order to save millions of updates per new tweet.
When an user requests feed, we check the current feed of the user. We also check the celebrity list, the user follows and pulls the latest tweets from them. We combine both the lists and sort them in decreasing order of timestamp. The feed is then stored in Redis and returned to the user.
We use Redis to cache Feed data, user profile, likes etc for faster reading with low latency.
We use LRU to invalidate cache, plus a new tweet also invalidates the existing cache, along with setting TTLs, so that we do not show stale data to the user.
Notification service can send notification to users based on new likes / new follower / tweets by followers. We will check the user's setting before sending notifications, with the tuning of atleast once event
Users DB: Postgres
(
user_id: uuid [PK],
username, name, address, .... , created_at, updated_at
)
Tweets DB: Postgres
(
tweet_id: uuid [PK]
user_id: uuid [FK]
content: string
likes: count
created_at: timestamp
)
Index at (tweet_id, user_id), (tweet_id, content, likes)
Sharded by tweet_id
Follow DB: Postgres
(
id: uuid [PK]
follower: uuid [FK]
followee: uuid[FK]
)
Index at: (follower, followee), (followee, follower)
Likes DB: Postgres
(
id: uuid [PK]
tweet_id: uuid [FK]
user_id: uuid [FK]
created_at: timestamp
)
Index at: (tweet_id, user_id)
Feed: Cassandra
(
user_id: uuid,
feed: [
{tweet_id, user_id, content, like}, ....
]
)
Redis will be used for caching the feed, tweet / like counts / followers list with different TTLs
Handling Celebrity Tweets:
When a celebrity tweets, a lot of followers read the tweets, likes spikes during the initial short burst.
To handle such spikes, we partition the kafka topic with content_id + salt, and aggregator aggregates the likes (by processing the events parallelly) and updates the DB once instead of calling the DB on every call
Feed Generation Service treats the Celebrity tweets separately.
We use a hybrid fanout approach on reads and writes.
We use fanout approach on writes for users with less followers, where all the follower's feed data is updated after an user they follow tweets.
We maintain a separate list of tweets from celebrity users.
When an users requests for the feed, we check the followers data, check the celebrity tweets, merge with their existing tweets feed and rank them (latest, celebrity weight), defaulting to chronologically decreasing order and present the feed data to the user
Redis is used as a caching layer for serving fast tweets, user profiles (which changes less frequently).
Failure Scenarios:
For high availability and low latency, we will replicate the data across servers world wide, so that the users connect to the nearest edge server and receive data with low latency. We will also add redundancy in replicating the data across servers.
Latency Numbers :
the number keeps adding on, if the cache is not present. So a cold start could take somewhere around 200-300ms (including processing time by the services)