Skip to content

Design a News Feed

Posting and reading a feed with fan out on write, fan out on read and the hybrid for celebrities, plus feed storage, simple ranking, cursor pagination, like counters and the media path.

Builds on Caching and Message Queues and Streams

Stage 5advanced24 min read
Interview questions

Open any social app and the first thing you see is a feed: a list of recent posts from the people you follow. It looks like the simplest screen in the app. It is also one of the most asked system design questions, because one small idea decides everything: do you build the feed when someone posts, or when someone reads?

Most of this note is about that one question. Everything else (storage, ranking, counters, photos) hangs off it.

1. Problem Statement

Design the feed for a social network like Instagram, X or Facebook. Users follow other users. They publish posts with text and maybe a photo. When a user opens the app, they see a page of posts from the accounts they follow, newest and most interesting first. They can scroll down to load more, like a post, and comment on it.

What makes it hard is the shape of the data. Most people have a few hundred followers. A few accounts have tens of millions. The same design has to work for both, and a design that is perfect for one is a disaster for the other.

2. Questions to Ask First

QuestionThe assumption I will make
How many users?500 million accounts, 300 million active in a week, 200 million active every day
How do people follow?One way, like Instagram or X. You can follow someone without them following you back.
How many followers does a person have?Typical user has 300. A few thousand accounts have more than 1 million. The top ones have 30 million.
What does the feed show?Posts only from accounts you follow. No ads and no suggestions from strangers.
What order?Recent first, then a simple score on top. We will build the simple one first.
How fresh?A post from a normal account should show up in followers' feeds within about 5 seconds. A celebrity post within 30 seconds is fine.
Can a feed be slightly out of date?Yes. Eventual consistency is fine. Missing a post for a few seconds hurts nobody.

3. Requirements

3.1 Functional Requirements

RequirementDetail
Publish a postText plus up to 4 photos
Follow and unfollowChanges whose posts appear in my feed
Read my feedA page of 20 posts, with more on scroll
See a profileOne person's posts, newest first
Like and unlikeOne like per user per post, and a visible like count
CommentAdd and read comments, with a visible comment count
Delete a postThe post disappears from every feed

3.2 Non Functional Requirements

  1. Reading the feed must be fast. Under 300 ms at p99, because it is the first screen of the app.
  2. Reads far outnumber writes. People look at feeds all day and post once in a while. The design should push work to the write side to keep reads cheap.
  3. Highly available. A broken feed means the app looks broken. Better an older feed than an error.
  4. Eventually consistent. Two people may see slightly different states for a few seconds.
  5. Handles the celebrity case. A post from someone with 30 million followers must not hurt anyone else.
  6. Scrolling must be stable. Posts must not repeat or jump while a user scrolls down.

4. Back of the Envelope Numbers

Reads first.

daily active users        = 200 million
feed opens per user       = 10 per day
feed opens per day        = 200M x 10 = 2 billion
per second on average     = 2,000,000,000 / 86,400 = about 23,000
at peak (3 times average) = about 70,000 feed reads per second

Writes.

people who post each day  = 10 percent of 200M = 20 million posts per day
per second on average     = 20,000,000 / 86,400 = about 230
at peak                   = about 700 posts per second

So reads outnumber posts by 100 to 1 (70,000 against 700). That ratio is what we should design around.

Now the two ways to build a feed, with real numbers. This is the part to do slowly.

Fan out on read. When you open the app, we look up the 300 accounts you follow, fetch each one's recent posts, and merge them.

lookups per feed read     = 300 accounts
at peak                   = 70,000 x 300 = 21 million lookups per second

Fan out on write. When someone posts, we push the post id into the feed of each follower.

posts per day             = 20 million
followers per post        = 300 on average
if all followers get it   = 20M x 300 = 6 billion inserts per day
only active followers     = 60 percent of them = 3.6 billion per day
per second on average     = 3,600,000,000 / 86,400 = about 42,000
at peak                   = about 125,000 inserts per second

21 million lookups per second against 125,000 inserts per second. Fan out on write is about 170 times cheaper at peak, because we pay once per post instead of once per read, and reads are 100 times more common.

Now the celebrity problem.

followers                 = 30 million
one post                  = 30 million inserts
at 100,000 inserts per sec = 30,000,000 / 100,000 = 300 seconds = 5 minutes

The last follower sees the post 5 minutes late, and for those 5 minutes that one post uses up the fan out workers. If 100 such accounts post 3 times a day:

100 accounts x 3 posts x 30M followers = 9 billion inserts per day

That is more than the 3.6 billion from all normal users put together. A hundred accounts would more than double our write load. This is why one simple rule cannot cover the whole network.

Feed storage. Each feed holds the latest 300 post ids. Nobody scrolls further.

one entry in a Redis sorted set = about 48 bytes (an 8 byte id plus overhead)
one feed                        = 300 x 48 B = 14.4 KB
feeds for 300M weekly users     = 300M x 14.4 KB = 4.3 TB
with one replica                = 8.6 TB, about 215 nodes at 40 GB each

Post storage is small. 20M posts x 1 KB = 20 GB a day, 7.3 TB a year. One database can hold that for years.

Photos are the big one.

posts with a photo   = 30 percent of 20M = 6 million per day
size of all versions = 1.5 MB (original plus 3 smaller sizes)
storage              = 6M x 1.5 MB = 9 TB per day, 3.3 PB per year
delivery at peak     = 70,000 pages x 6 photos x 150 KB = 63 GB per second = about 500 Gbit per second

No set of our own servers should serve 500 Gbit per second. That number is the whole case for a CDN, a network of servers around the world that keeps copies of the photos close to the viewer.

Likes: 5 per user per day is 1 billion a day, about 11,600 per second, about 35,000 at peak. Fine for the system as a whole. One viral post is the trouble, and section 9.3 deals with it.

5. High Level Design

Loading diagram…

5.1 Walk Through of Each Box

API gateway. The front door. It checks the login token, limits how fast one user can call us, and sends the request to the right service.

Post service. Takes a new post, stamps it with an id, saves it, and drops a "post created" message onto a queue. It does not do the fan out itself. If it did, the user would wait while we copy the post to 300 feeds. Posting should return in about 100 ms, so the heavy part happens after.

Message queue. A line of work waiting to be done. The post service puts a message in, and workers take it out at their own speed. If a burst of posts comes in, the queue gets longer for a moment and nothing breaks. Think of the ticket line at a post office: the clerk is not slowed down because ten people arrived at once, the line just grows.

Fan out workers. They read a "post created" message, ask the follow graph who follows the author, and push the post id into each follower's feed. "Fan out" just means one thing spreading out to many places. Workers can be added or removed freely, since each message is independent.

Follow graph service. Answers two questions: who does this person follow, and who follows this person. Two different questions, so two different indexes (section 7).

Feed store. One list per user, holding the ids of the latest 300 posts that should appear in their feed. It is in memory, because it is read 70,000 times a second. It holds ids, not full posts. A post id is 8 bytes, a post is 1 KB, so the feed stays small and we never copy a post's text 300 times.

Celebrity recent posts list. A small list per celebrity of their last 20 or so posts. Used only for the pull half of the hybrid, see section 9.1.

Post store. The real posts. Sharded by post id so the load spreads evenly. Also cached in front, since a hot post is read by millions.

Feed service. The read side. It gets the id list, merges in celebrity posts, loads the full posts, ranks them, and returns a page.

Counter service. Likes and comment counts, kept apart from the posts because they change so fast (section 9.3).

Media service and CDN. Photos never pass through our app servers. The app uploads directly to object storage, and the CDN serves them (section 8.1).

6. API Design

All calls need a login token. {id} values are 64 bit numbers sent as strings, so JavaScript does not round them.

MethodPathPurpose
POST/v1/media/uploadsAsk for a place to upload one photo
POST/v1/postsPublish a post
GET/v1/feedMy feed, one page
GET/v1/users/{id}/postsA profile's posts
DELETE/v1/posts/{id}Delete my post
POST/v1/users/{id}/followFollow
DELETE/v1/users/{id}/followUnfollow
PUT/v1/posts/{id}/likeLike (safe to repeat)
DELETE/v1/posts/{id}/likeUnlike
POST/v1/posts/{id}/commentsAdd a comment
GET/v1/posts/{id}/commentsComments, one page

Like is PUT and not POST on purpose. Liking twice must give the same result as liking once, because phones retry requests on bad networks. PUT says that out loud.

6.1 Upload a Photo

Send { "contentType": "image/jpeg", "sizeBytes": 2480000 } and get back a mediaId, an uploadUrl and expiresInSeconds: 600. The uploadUrl is a signed link. It lets the phone send the file straight to object storage for 10 minutes, and nothing else. Our servers never touch the bytes.

6.2 Publish a Post

Send a header Idempotency-Key: 7c1e... so a retry does not create two posts, and a body like this:

{ "text": "Sunrise from the train", "mediaIds": ["902110553"] }

Response is 201 Created with the new postId, authorId and createdAt.

6.3 Read the Feed

GET /v1/feed?limit=20&cursor=eyJzIjoiYWJjMTIzIiwibyI6MjB9

Response:

{
  "items": [
    {
      "postId": "1790234001122",
      "author": { "id": "4411", "name": "Asha" },
      "text": "Sunrise from the train",
      "media": [{ "url": "https://cdn.example.com/m/902110553_640.jpg" }],
      "likeCount": 128,
      "commentCount": 9,
      "likedByMe": true,
      "createdAt": "2026-10-10T06:12:44Z"
    }
  ],
  "nextCursor": "eyJzIjoiYWJjMTIzIiwibyI6NDB9"
}

The cursor is a blob that only the server understands. The app hands it back untouched. When nextCursor is null, there is nothing more.

7. Data Model

Several stores, each picked for how it is read.

7.1 Post Ids

Posts get ids in the style of Snowflake: a 64 bit number made of 41 bits of time in milliseconds, 10 bits for the machine, and 12 bits for a counter within that millisecond.

Why not a plain auto number? One database would have to hand them out, and that is a bottleneck. Why not random? Then sorting by id tells us nothing. With a time-first id, sorting by id is sorting by time. That means "newest first" needs no separate time column in the index, and a cursor can be just "an id".

7.2 Tables

users

ColumnTypeWhy
user_idbigintPrimary key
handletextUnique name. A unique index, because people look it up.
follower_countbigintKept here so we can decide in one read whether the account is a celebrity
is_celebritybooleanSet when followers pass 100,000 (see section 9.1)

following and followers

TablePartition keyClustering keyWhy
followinguser_id (me)followee_idAll of my follows sit together. "Do I follow X" is a direct lookup.
followersuser_id (the account followed)follower_idFan out workers read my followers in pages of 1,000

Both tables hold the same fact, "A follows B", in two directions, and the write is done twice. It looks wasteful, but each question ("who do I follow", "who follows me") is then a single partition read. With one table, one of the two would have to scan.

posts

ColumnTypeWhy
post_idbigintPrimary key and shard key. Random-looking spread across shards (hash of id), so no shard is hotter.
author_idbigintWho wrote it
texttextUp to 500 characters
media_idslist of bigintUp to 4
created_attimestamp
is_deletedbooleanSoft delete. The row is kept for 30 days, so a delete can be undone.

author_posts (a profile's timeline)

ColumnTypeWhy
author_idbigintPartition key. A profile page asks only about one author.
post_idbigintClustering key, sorted newest first. Reading 20 posts is one short scan.

This table is also what the pull half of the hybrid reads. A celebrity's last posts are the top of their partition.

post_likes is partitioned by post_id with user_id as the clustering key. The pair is unique, so a double like is just an overwrite.

post_counters has post_id as primary key and two numbers, like_count and comment_count. It is a saved copy, refreshed in batches (section 9.3). comments is partitioned by post_id with a time-ordered comment_id as the clustering key, so reading the first page of comments is one short scan.

media

ColumnTypeWhy
media_idbigintPrimary key
owner_idbigintWho uploaded it
object_keytextWhere the original sits in object storage
statusenumUPLOADING, PROCESSING, READY, FAILED. A post only accepts READY media.
variantsjsonSizes made, such as 320, 640, 1080

7.3 The Feed Itself

Not a table. It lives in the in-memory feed store, as one sorted list per user.

key     feed:{user_id}
member  post_id
score   post_id   (time-first, so the id is already a time)
size    newest 300 only

Adding to a feed is one command: add the id, then cut the list back to 300. Reading a page is one command: give me the 20 ids just below this cursor. Both are fast because the list is already sorted.

Why score by post id and not a separate timestamp? One less number to store, and two posts in the same millisecond still have a clear order.

Why 300? On an ordinary day nobody goes more than 100 posts deep. 300 is three times that. If someone does reach the end, we fall back to the pull path, which can go as deep as we want, just slower.

8. Key Flows

8.1 Publish a Post with a Photo

Loading diagram…

The user gets their 201 before any fan out happens. Their own feed is handled by adding the post to it right away in the post service, so they always see what they just posted, even if the workers are a few seconds behind.

The photo takes a different road from the text. It goes phone to storage directly, never through our servers. A photo is a few MB and a post is 1 KB, so if photos came through the app servers, 99 percent of their bandwidth would be spent carrying photos. The resizing step makes 3 smaller copies, so a phone on a slow network can ask for the 320 pixel one.

8.2 Read the Feed (Hybrid)

Loading diagram…

Count the lookups: one for the feed list, one for the follow check, one for the celebrity lists (batched), one for the post bodies (batched), a few counters. About 5 round trips, in parallel where they can be. Fan out on read alone needed 300.

8.3 Like a Post

Loading diagram…

The insert is the guard. Only a real first like changes the counter, so a retried request cannot add 2.

9. Deep Dives

9.1 Fan Out on Write, Fan Out on Read, and the Hybrid

The problem. Every follower must be able to see a new post, and the feed must open in a split second. Where does the work go, at post time or at read time?

Option one: fan out on write (push). When a post is made, copy its id into the feed of every follower.

  • Reading is trivial: one list, already sorted. About 5 ms.
  • Writes cost one insert per follower. For 300 followers that is 300 inserts (about 180 once we skip inactive people), around 40 ms of worker time at 5,000 inserts per second per worker. Perfectly fine.
  • Wasted work for people who never open the app. We skip those who have not been seen in 2 weeks, which saves about 40 percent.
  • For 30 million followers, one post is 30 million inserts and 5 minutes of delay, as worked out in section 4.

Option two: fan out on read (pull). Store nothing extra. When someone opens the app, fetch the recent posts of each account they follow and merge them.

  • Posting is trivial: one row.
  • Reading is heavy: 300 lookups per open, 21 million lookups per second at peak.
  • No wasted work. Nobody pays for feeds that are never opened.
  • A celebrity's post is read by everyone, but it is one hot, cached list, which is exactly what caches are good at.

Neither wins alone. Push is great for the 300 follower majority and terrible for the 30 million follower few. Pull is terrible for the many reads of ordinary accounts and fine for the few celebrities, because celebrity posts are a small set that can be cached.

The choice: do both, split by follower count. This is the hybrid.

  • An account with fewer than 100,000 followers is pushed. Fan out workers copy its posts into follower feeds.
  • An account with more than 100,000 followers is pulled. Its post is saved once and added to its own small recent posts list. Nothing is pushed.
  • When someone reads their feed, the feed service takes the pushed list, finds which celebrities this user follows, reads their recent lists, and merges the two.

Why 100,000? It is where push starts to cost more than it saves. At 100,000 followers one post is 100,000 inserts, about 1 second at 100,000 per second. That is still acceptable. At 1 million it is 10 seconds, and at 30 million it is 5 minutes. Anything between 100,000 and 1 million works. I would start at 100,000 and move it by watching how long the slowest fan out takes.

Check that the read side stays cheap. A user follows 300 accounts and, say, 4 of those are above the line. The read is 1 feed list plus 4 celebrity lists, merged. The celebrity lists are the hottest data in the system, read tens of thousands of times per second at peak for the biggest ones, so they live in memory and get copied locally in the feed servers for a second or two. Reading them is nearly free.

Only a few thousand accounts out of 500 million are over the line, roughly 1 in 100,000. We pay a heavier read for those few and save all of their fan out cost.

Two details people get wrong.

First, an account that crosses the line. Say an account reaches 100,000 followers. From then on its posts are pulled. The old ones already pushed stay in feeds, which is harmless. To stop it flipping back and forth when the follower count hovers around 100,000, make it a celebrity at 100,000 and only turn it back at 80,000. That gap is called hysteresis, and it stops the account flipping between modes every few minutes.

Second, a new follow. If you follow someone new, your feed list has none of their old posts. On follow, copy that author's last 10 posts into the follower's feed. If the author is a celebrity, there is nothing to copy, the pull path covers it. On unfollow, do not hunt through the feed. Filter at read time: when merging, drop any post whose author you no longer follow. That check is cheap since we already loaded the follow list.

Deletes work the same way. We do not remove a deleted post from 300 feeds. We mark it deleted, and the feed service drops it when it loads post bodies, because a deleted post comes back as "gone". The feed list gets cleaned up by itself as new posts push the old id off the end of the 300.

9.2 Ranking and Pagination

The problem. A raw time ordered feed is fine, but people follow a lot of accounts and miss the posts they care about. And once we have an order, we must page through it without repeating or skipping posts while new ones keep arriving.

Ranking first.

Option one: pure recency. Sort by post id, newest first. It needs no extra data, it is easy to explain to users ("this is the order things happened"), and it is predictable. Its weakness is that a chatty account crowds out a quiet one you care more about.

Option two: recency plus a score. Take the candidate posts and give each a number, then sort by it.

score = affinity x (1 + log10(1 + likes + 2 x comments)) / (1 + age_in_hours / 3)

Read it in three parts.

  • Affinity is how much you care about this author, from 1 to 5. We work it out once a night from how often you like, comment on or open that person's posts in the last 30 days. It is one number per pair, stored in a small table.
  • The log part rewards posts that others like, but a post with 50,000 likes is not 1,000 times better than one with 50. The log squeezes the big numbers down.
  • The last part is the age penalty. A post loses about half its score after 3 hours, and a bit more every hour after.

Try it with three posts.

A: close friend (affinity 3), 2 hours old, 10 likes, 2 comments
   engagement = 10 + 4 = 14, log10(15) = 1.18, so 2.18
   age part   = 1 + 2/3 = 1.67
   score      = 3 x 2.18 / 1.67 = 3.9

B: a stranger you follow (affinity 1), 30 minutes old, no likes
   score      = 1 x 1 / 1.17 = 0.86

C: celebrity (affinity 1), 10 hours old, 50,000 likes, 2,000 comments
   engagement = 54,000, log10 = 4.73, so 5.73
   age part   = 1 + 10/3 = 4.33
   score      = 1 x 5.73 / 4.33 = 1.32

Order is A, then C, then B. The close friend's post from two hours ago beats a brand new post from a stranger, and the famous post still shows above the newest post from a stranger. That feels about right, which is how you tune these things: try real examples and see if the order makes sense.

The choice: ship pure recency first, then add the score. I would not start with the score. You need data on likes and affinity before the numbers mean anything, and recency is a good feed on its own. When you add the score, only rank the top 200 candidates, the 300 pushed ids plus the pulled celebrity posts, cut down to the newest 200. The score is a few multiplications, so 200 of them take well under a millisecond, and we do not need to score the entire network.

Pagination.

Option one: offset. GET /feed?page=3, which means skip 40 and give 20. It is the first thing everyone thinks of, and it breaks. Say you load page 1 (posts 1 to 20). While you read, 5 new posts arrive. You ask for page 2, which is "skip 20", but everything has moved down by 5. Now page 2 starts with 5 posts you already saw.

Option two: a cursor. The server says "the last thing I gave you was this one", and the next request means "give me what comes after that". New posts at the top do not matter, because the position is tied to a post and not to a number.

For a time ordered feed, the cursor is just the id of the last post you saw. The query is "20 ids with a score below this id", which is a direct jump in a sorted list.

For a ranked feed, there is a catch. Scores change as likes arrive. If page 2 re-ranks from scratch, a post you saw on page 1 might slide down to page 2, or one you skipped might jump up. The fix is a snapshot:

  1. On the first request, rank the top 200 candidates and save the ordered ids as a list for 15 minutes, under a session id.
  2. The cursor holds the session id and the position, for example "session abc123, offset 20".
  3. Page 2 reads positions 20 to 39 of the saved list. No re-ranking, so no repeats and no jumps.
  4. Pull to refresh starts a new session.

What does it cost? A session is 200 ids x 8 B = 1.6 KB. With 23,000 feed opens per second and a 15 minute life, that is 23,000 x 900 = 20 million live sessions, about 33 GB. Small compared with the 8.6 TB of feeds. Worth it for a stable scroll.

9.3 Likes and Comment Counters

The problem. Every post shows a like count. Likes are the highest volume write in the system after the feed fan out, at 35,000 per second at peak. And they are not spread evenly. One viral post may get 50,000 likes in a single second.

Option one: update a number in the database. UPDATE post_counters SET like_count = like_count + 1. Every like locks that row for a moment. A single database row handles about 1,000 updates per second at best. A hot post at 50,000 per second would queue up 50 times what the row can take, and every other query waits behind it.

Option two: count in memory and save in batches. Add to a counter in the in-memory store (one command, 100,000 a second per node is normal), and let a background job write the totals to the table every 5 seconds.

Choose option two. 50,000 likes per second become 1 database write per post every 5 seconds. A viral post costs the database almost nothing.

Details to get right.

  • The truth is the post_likes table, where each (post, user) pair is a row. The counter is a convenience copy. If the counter is lost, we can recount from the table. A user liking twice cannot double count because of the unique pair.
  • A hot counter on one memory node can itself be too hot. Split it into 16 pieces: likes:{post}:0 to :15. A like adds to a random piece, and reading means adding up the 16. This is the same trick as splitting a hot cache key. Only do it for posts that cross, say, 1,000 likes per second.
  • The count a user sees can be a few seconds behind. Nobody can tell a difference between 1,204,331 and 1,204,380 likes.
  • Show the user their own like at once. The response to the like includes likedByMe: true, and the app adds 1 on the screen on its own, so it feels instant.
  • For huge numbers, show "1.2M" instead of the exact digits. Exact numbers that change every second look wrong anyway.

Comment counts are the same idea, with less volume (about 1,150 comments a second), so a plain counter in memory with a batch flush is plenty.

10. Failure Handling

What breaksWhat the user seesWhat we do
Fan out workers fall behindNew posts show up late in feeds, maybe by minutesThe queue grows but nothing is lost. Alert on queue age, add workers. The author still sees their own post immediately.
A worker crashes mid fan outSome followers miss the post for a whileThe message is not removed from the queue until the work is done, so another worker retries. Adding the same id twice is harmless in a sorted list.
A feed store node diesThose users get an empty or old feedReplica takes over in seconds. If a feed is gone, rebuild it with the pull path (read recent posts of everyone followed) and refill the list. Slower, but it works.
Feed store is down entirelyFeed is slow, older than normalFall back to pull for everyone, with a cap on how many follows we read (say the newest 100). Better a partial feed than an error.
Counter cache lostLike counts reset to older numbersRecount from post_likes and the last saved post_counters. Show the saved number meanwhile.
Photo upload fails halfwayPost button stays disabledThe signed link allows retries. Status stays UPLOADING, and a cleanup job removes files older than a day with no post.
CDN has a problemPhotos load slowlyFall back to a second CDN or go straight to object storage for a short time. Text still shows.
Celebrity list cache is missingCelebrity posts absent from feedsRebuild from author_posts, one short read per celebrity. Cheap because the table is already sorted by newest.
Deleted post appearsA deleted post is seen for a momentMark deleted first, then filter at read time. The cache for that post is cleared at the same moment.

What goes on under all of this: the feed is allowed to be old, but never allowed to be an error. If anything is slow, serve less (a shorter list, a cached page) instead of failing.

11. Tradeoffs

DecisionChosenAlternativeReason
Feed buildingHybrid, push for most, pull for celebritiesPush for allA 30 million follower post would take 5 minutes and use up the fan out capacity
Feed buildingHybridPull for all21 million lookups per second at peak is far too heavy
Inactive usersNo feed pushed after 14 daysPush to allSaves about 40 percent of writes. Their feed is rebuilt on return.
OrderingRecency first, then a score on the top 200Full machine learning ranking at the startNeeds likes and affinity data first, and recency alone is a good feed
PaginationCursor, plus a saved ranked sessionOffsetOffset repeats posts when new ones arrive
Like countsIn memory, flushed every 5 secondsUpdate the rowA viral post would overload one row

12. Interview Talking Points

Say these in about this order.

  • Start by saying the whole problem is one choice, build the feed at write time or read time, and that you will come back to it with numbers. Then fix the scope in two minutes: follow one way, posts only, 200 million daily users, 100 reads for every post.
  • Do the arithmetic for both extremes. Pull is 70,000 reads x 300 follows = 21 million lookups per second. Push is about 125,000 inserts per second at peak. Push wins about 170 to 1 for normal accounts.
  • Then show where push breaks: 30 million followers is 30 million inserts and 5 minutes of delay, and 100 such accounts double the daily write load. Only then say "so I will use a hybrid", and give the rule: push below 100,000 followers, pull above.
  • Say what the read looks like in the hybrid: one list from the feed store plus about 4 celebrity lists, merged, so about 5 lookups instead of 300. Mention that the feed holds ids, 8 bytes each, and not whole posts.
  • Mention the small ugly cases without being asked: new follow copies 10 old posts, unfollow and delete are filtered at read time, the celebrity line has a gap to stop flipping. This is what shows you have thought past the diagram.
  • Ranking: say you would ship recency first, and describe the score in one line (affinity, likes on a log scale, age penalty). Say you only rank the top 200.
  • Pagination: explain why offset repeats posts, use a cursor, and for the ranked feed save a session for 15 minutes so the order cannot change while someone scrolls.
  • Finish with the sidebars in one sentence each: likes counted in memory and flushed every 5 seconds because a viral post would melt one row, photos go straight to object storage and out through a CDN because 500 Gbit per second cannot come from our servers, and the feed may be old but must never be an error.

In this topic

Done with this topic?

How well do you know it?

PreviousDesign a Distributed Cache