Requirements (CRUD)
Functional:Create Post
Upload Photo/Video,
Edit Post,
Delete Post,
Read Feed,
Infinite Scroll,
Like,
Comment,
Share,
Follow/Unfollow,
Notifications
Non Functional:
High Availability
Eventual Consistency
Low Latency (less than 200 ms feed generation target)
Back of Envelope
World Population | InternetUsers(60%) | FB Users(80-85% of Internet users) | Active FB Users(20%)
----------------------|-----------------------|------------------------------------|--------------------
7 Billion //Year 2020 | 7 x 0.6 = 4.2 Billion | 4.2 x 0.8 = 3.5 Billion | 3.5 x .2 = 700 Million
Traffic Estimates (Incoming Requests/sec)
700 Million pulling their new feed 5 times a day. Total New feed requests = 700 x 5 = 3500 Million requests/day.
3500/24x60x60 = Around 39,000 Requests/sec.
Storage Estimates
Cache/CDN
Suppose We want to keep 500 posts/user on CDN for quick fetch.
Total posts to be stored. 500 x 700 Million = 350 Billion.
Let's assume on average each posts is 1 KB in size. Total bytes = 350 TB
Assuming 1 server can cache is 100GB. Total servers needed = 350 TB / 100 GB = 3500 Machines needed in total
API Design
// POST MESSAGE(Image+Text+Video)
POST https://url/v1/post
header { Authorization: {Bearer "API_KEY_TOKEN"},}
body {text, picture_url, video_url}
// READ FEED
GET HTTP/2 https://url/v1/feed
header { Authorization: {Bearer "API_KEY_TOKEN"},}
body {to be filled}
DB Schema
SQL DB
User information, password, comments can be stored in seperate tables.
Likes to Comments can be stored in seperate table and comments table can store like_id.
| username | user_id(pk) | created_at | enabled | //User Table
| user_id(pk) | password_hash | created_at | expiry_at | //Password Table
Comments Table: Comment can have text,video,image,@,#,likes,shares
| comment_id (pk) | text_id(fk) | user_id (fk) | media-id(fk) | created_at | like_id(fk) | share_id(fk) |
| key=like_id (pk) | value=file url storing all likes | //likes Table
| key=text_id (pk) | value=text url | //Text Table
Relationship Friends & Friends of Friends stored in TAO(Graph DB)
HLD
Block Diagram
%%{init: {'themeVariables': {'fontSize': '13px'}, 'sequence': {'actorMargin': 45, 'messageMargin': 30, 'boxMargin': 8, 'useMaxWidth': true}}}%%
flowchart LR
%%=========================
%% USER
%%=========================
U([User])
%%=========================
%% EDGE
%%=========================
DNS[Authoritative DNS]
CDN[CloudFront CDN]
WAF[WAF + Shield]
APIGW[API Gateway]
%%=========================
%% SERVICES
%%=========================
subgraph Kubernets Pod
POSTLB[Post Service LB]
POST[Post Service]
POST1[Post Service]
end
subgraph Kubernets Pod
FEEDLB[Feed Service LB]
FEED[Feed Service]
FEED1[Feed Service]
end
subgraph Kubernets Pod
USERLB[User Service LB]
USERSVC[User Service]
USERSVC1[User Service]
end
RANK[Ranking Service]
FANOUT[Fanout Service]
MEDIA[Media Service]
%%=========================
%% CACHE
%%=========================
POSTCACHE[(Redis Post Cache)]
FEEDCACHE[(Redis Feed Cache)]
TAOCACHE[(TAO Cache)]
%%=========================
%% DATABASES
%%=========================
POSTDB[(Post DB)]
POSTREPLICA[(Post Replica)]
TAO[(TAO/MySQL)]
TAOREPLICA[(TAO Replica)]
OBJECT[(Object Storage)]
%%=========================
%% KAFKA
%%=========================
KAFKA[(Kafka)]
%%=========================
%% FLOW
%%=========================
U --> DNS
DNS --> CDN
CDN --> WAF
WAF --> APIGW
APIGW -->|POST /api/v1/posts| POSTLB
APIGW -->|GET /api/v1/feed| FEEDLB
APIGW -->|GET /api/v1/user| USERLB
POSTLB -->|Load balance| POST
FEEDLB -->|Load balance| FEED
USERLB -->|Load balance| USERSVC
POST -->|store image| OBJECT
POST --> POSTCACHE
POST --> POSTDB
POSTDB --> POSTREPLICA
POST --> KAFKA
KAFKA --> FANOUT
FANOUT --> FEEDCACHE
FANOUT --> TAOCACHE
TAOCACHE --> TAO
TAO --> TAOREPLICA
FEED --> FEEDCACHE
FEED --> RANK
RANK --> TAOCACHE
%%=========================
%% COLORS
%%=========================
classDef edge fill:#dbeafe,stroke:#1d4ed8,color:#000;
classDef service fill:#d1fae5,stroke:#059669,color:#000;
classDef cache fill:#fef3c7,stroke:#d97706,color:#000;
classDef db fill:#fce7f3,stroke:#be185d,color:#000;
classDef kafka fill:#fee2e2,stroke:#dc2626,color:#000;
class DNS,CDN,WAF,APIGW edge
class POSTLB,FEEDLB,USERLB,POST,FEED,USERSVC,RANK,FANOUT,MEDIA service
class POSTCACHE,FEEDCACHE,TAOCACHE cache
class POSTDB,POSTREPLICA,TAO,TAOREPLICA,OBJECT db
class KAFKA kafka
Post Upload
TAO Database used for storing relationships
sequenceDiagram
actor User
participant CDN
participant APIGW as API Gateway
box rgb(230,230,255) Kubernets Pod
participant PSLB as Post Service LB
participant POST as Post Service
end
participant OBJ as Object Storage
participant DB as Post DB
participant KAFKA as Kafka
participant FAN as Fanout Service
participant TAO
participant FC as Feed Cache
rect rgb(220,240,255)
User->>CDN: HTTPS POST /api/v1/posts\nJWT + multipart/form-data
CDN->>APIGW: Forward (dynamic request)
APIGW->>PSLB: Route POST /posts
PSLB->>POST: Forward to healthy instance
end
rect rgb(220,255,220)
POST->>OBJ: PUT media object
OBJ-->>POST: mediaURL
POST->>DB: INSERT post(authorId, caption, mediaURL)
DB-->>POST: postId
POST->>KAFKA: Publish PostCreated(postId)
POST-->>User: HTTP 201 Created\n{postId}
end
rect rgb(255,235,220)
KAFKA->>FAN: Consume PostCreated
FAN->>TAO: GetFollowerIds(authorId)
TAO-->>FAN: followerIds
FAN->>FC: Update followers' feed cache
end
Normal Feed Read
TAO Database used for storing relationships
sequenceDiagram actor User participant CDN participant APIGW as API Gateway box rgb(230,230,255) Kubernets Pod participant FSLB as Feed Service LB participant FEED as Feed Service end participant CACHE as Feed Cache participant DB as Feed DB participant RANK as Ranking Service participant TAO rect rgb(220,240,255) User->>CDN: HTTPS GET /api/v1/feed?page=1 CDN->>APIGW: Forward request APIGW->>FSLB: Route /feed FSLB->>FEED: Forward request end rect rgb(220,255,220) FEED->>CACHE: GET feed:user123 alt Cache Hit CACHE-->>FEED: Candidate Post IDs else Cache Miss FEED->>DB: SELECT feed entries DB-->>FEED: Candidate Post IDs FEED->>CACHE: Store feed end end rect rgb(255,250,220) FEED->>RANK: Rank(candidatePosts) RANK->>TAO: Fetch friend affinity TAO-->>RANK: Social features RANK-->>FEED: Ranked posts FEED-->>User: HTTP 200\nJSON Feed Response end
Celebrity Sequence
sequenceDiagram actor Celebrity participant PostService participant PostDB participant Kafka participant FeedService Celebrity->>PostService: POST /posts PostService->>PostDB: Save Post PostService->>Kafka: Publish PostCreated PostService-->>Celebrity: 201 Created Note over Kafka: No fanout to 400M followers User->>FeedService: GET /feed FeedService->>PostDB: Fetch celebrity posts FeedService->>FeedService: Merge with cached feed FeedService-->>User: Ranked feed
GET Newsfeed
| Push Model | Pull Model | |
|---|---|---|
| What | Whenever someone posts a item(text, video, audio), Generate newsfeed for all friends/followers and send to friend/follower. Client side browser caches it and delivers when client comes online | Only Generate feed whenever request to retrieve comes from client |
| Advantage, Disadvatange |
Adv: Newfeed is calculated in advance, which leads to very less delay on client to read news feed Disadv: For inactive users/friends also newfeed is calculated. More resources are used upfront |
Adv: Newfeed is calculated for active users only, which leads to low resource usage Disadv: Client can get significant latency in receiving newsfeed as its calculated when client asks |
| Usecase | we use a push model for the majority of users | Celebrity Case: Whenever a celebrity posts a message. Feed for its followers is calculated and delivered. |
What we will do?
-
We will use both of approaches based on the situation. See usecase in above table
Newfeed Architecture
@startuml
!pragma teoz true
actor user as u
control "App\nService" as as #LightBlue
control "Fanout\nService" as fo #LightBlue
queue "Message\nQueue" as mq
control "Workers" as w #LightBlue
box userdb #Azure
entity UserCache as cudb #LightPink
database UserDB as udb #LightGreen
end box
box postdb #Azure
entity "Cache\nPostDB" as cpdb #LightPink
database "PostDB\nGraphDB" as pdb #LightGreen
end box
box newsfeed #Azure
entity "Cache\nNewsfeed" as cnf #LightPink
control "Newsfeed\nService" as nfs #LightBlue
end box
u -> as: POST "Hello"
as -> cpdb: "Hello"
cpdb -> pdb: "Hello"
as -> fo: userId=1\nCreated POST
group GET Friends of userId=1
fo -> cudb: Get friends\nof userid=1
cudb -> udb:Get friends\nof userid=1
udb -> fo: friend list
end
group GET User Filters
fo -> cudb: Get userid\nfilters
cudb -> udb:Get userid\nfilters
udb -> fo: filters(if\nuser has muted someone)
end
fo -> mq: friendId + PostId
mq -> w: data
w -> cnf: data
cnf -> nfs: data
nfs -> pdb: Get post
pdb -> nfs: post
hnote over nfs
Create post
username, profile
picture,post content
post image
end note
nfs -> u: news feed
@enduml