SkillPilotAI

Case Study 4: Instagram / Twitter News Feed

Show the posts of the people a user follows in a fast, personalised home timeline, in milliseconds, for hundreds of millions of users.

C.1 Requirements

Functional requirements:

  • A user can create a post (with text, photo, video).
  • A user can follow / unfollow other users.
  • Home feed: the latest posts of followed users, ranked or in reverse-chronological order.
  • Users can like, comment and share; every post shows its like count.
  • The feed is an infinite scroll: cursor-based pagination.
  • User profile timeline: all posts of one user.
  • Notification of a new post to followers (optional, async).

Non-functional requirements:

  • High availability 99.99%: a slightly stale feed is fine, but the feed must not go down (AP over CP).
  • Feed load latency p99 < 200 ms, post publish < 500 ms.
  • Eventual consistency is OK: it is fine if a new post shows up for followers within 5-10 seconds.
  • Scale: 50 crore (500 million) DAU, a read-heavy system (read:write about 100:1).
  • The like counter can be approximately correct, but never negative or badly wrong.
  • Durable media storage (11 nines): an S3-like object store + CDN.

C.2 Estimation (back-of-the-envelope)

ItemNumberHow it is derived
Daily active users50 croreAssumption: a global app at Instagram/Twitter scale.
Posts per day10 croreOne in every 5 users posts once a day; 10 crore / 86400 = ~1,200 writes/sec avg, peak ~5,000/sec.
Feed read QPS~1.2 lakh/sec avg50 crore users x 20 feed refreshes/day = 1000 crore / 86400 = ~1.15 lakh/sec; peak 3x = ~3.5 lakh/sec.
Fan-out cache writes~10 lakh/sec peakAvg 200 followers x 1,200 posts/sec = 2.4 lakh/sec avg; peak 5,000 x 200 = 10 lakh/sec; pushing celebrity posts would make this explode, hence hybrid.
Post metadata storage~1 TB/day10 crore posts x ~1 KB metadata = 100 GB/day; with replication x3 + indexes ~0.5-1 TB/day, ~300 TB a year.
Media storage~20 PB/yearHalf of the posts carry a photo, 1 MB avg (multiple resolutions) = 50 TB/day x 365 = ~18 PB/year.
Timeline cache size~4 TB RAM20 crore active users x 500 post IDs x 8 bytes = ~800 GB; with overhead and replicas, a ~3-4 TB Redis cluster.
Like events~10 lakh/sec peakThere are some likes on every feed view; the counters must be handled with Redis + batch flush.

C.3 API design

EndpointPurpose
POST /v1/postsCreate a new post (caption, media_ids); with an Idempotency-Key header so no double post is created
POST /v1/media/upload-urlGet an S3 pre-signed URL; the client uploads the media directly
GET /v1/feed?cursor=<id>&limit=20Next page of the home timeline; cursor-based pagination
GET /v1/users/{id}/posts?cursor=A user's profile timeline
POST /v1/users/{id}/followFollow; DELETE on the same path to unfollow
POST /v1/posts/{id}/likeLike (idempotent: liking again does not raise the count); DELETE to unlike

C.4 Data model

Table / StoreFieldsWhy
users (MySQL/Postgres, sharded by user_id)user_id PK, username, name, follower_count, is_celebrity, created_atThe user profile is relational and rarely changes; the is_celebrity flag decides fan-out.
posts (Cassandra, partition key author_id, clustering post_id DESC)post_id (Snowflake ID), author_id, caption, media_urls, created_atWrite-heavy, time-ordered data; one author's posts sit in one partition, latest first.
follows (Cassandra, do tables)followers(user_id, follower_id) aur following(user_id, followee_id)Fast lookup is needed in both directions: followers for fan-out, following for pull.
timeline cache (Redis sorted set / list)key feed:{user_id} -> latest 500 post_ids with score = timestampA precomputed home feed with O(log n) reads in memory; only IDs are stored and the data is hydrated separately.
post_counters (Redis + Cassandra counter table)post_id, like_count, comment_countHot counters are INCR'd in Redis and flushed to the durable store periodically.
likes (Cassandra, partition key post_id)post_id, user_id, created_atRecord of who liked; because (post_id, user_id) is unique the like stays idempotent.

C.5 High-level architecture

Instagram / Twitter: architecture and request flow
  1. 1.Clients: Mobile App, Web Browser; media loads from CDN (origin S3 media: photos, resized)
  2. 2.Load Balancer -> API Gateway (auth, rate limit)
  3. 3.Services: Post Service (Snowflake post_id), Feed Service (merge + hydrate), Follow Service (celebrity flag), Like Service (idempotent like), Media Service (upload-url, resize), Ranking (ML score)
  4. 4.Kafka: post-created and like events
  5. 5.Workers: Fan-out Workers (paginate followers, skip celebrities), Counter Aggregator (batch flush every 5-10 sec), Ranking Service
  6. 6.Data: Redis Timeline feed:{user_id} (500 IDs, ~4 TB), Cassandra (posts by author_id + post_id DESC, follows, likes, counter table), MySQL (users, is_celebrity), Redis Counters (INCR likes:{id}), S3 (media)

Post publish: S3 upload -> Gateway -> Post Service -> Cassandra -> Kafka post-created -> ZADD into followers' timelines. Feed read: Redis feed IDs + celebs pull from Cassandra -> Ranking -> hydrate post cache -> CDN URLs. Numbers: feed 1.2 lakh/sec avg, 3.5 lakh peak; 1,200 posts/sec; fan-out peak 10 lakh writes/sec. Hybrid fan-out: normal user push on write, celebrity (>10 lakh followers) pull on read.

  • The photo is first uploaded straight to S3 through a pre-signed URL from the Media Service; images load from the CDN.
  • Post publish: LB -> API Gateway -> Post Service writes to Cassandra with a Snowflake ID, and an event goes on Kafka post-created.
  • Fan-out Workers paginate the followers and ZADD the post_id into every active follower's Redis timeline feed:{id}; celebrities are skipped.
  • Feed read: Feed Service merges the Redis IDs + celebrities' posts from Cassandra and gets them scored by the Ranking Service.
  • Like Service INCRs the Redis Counters; the like event goes through Kafka and the Counter Aggregator flushes to Cassandra in batches.
ComponentWhat it does
Post ServiceValidates the post, saves it in Cassandra, assigns a Snowflake ID, then publishes a post-created event to Kafka.
Media ServiceGives a pre-signed S3 URL; after upload async workers create thumbnails/resizes, served through the CDN.
Fan-out WorkersRead the event from Kafka, fetch the author's followers and push the post_id into every follower's Redis timeline (for normal users).
Feed ServiceMerges precomputed IDs from Redis + the latest posts of followed celebrities, applies ranking, hydrates the post data and returns it.
Follow ServiceMaintains the follow graph; updates follower_count and sets the flag when the celebrity threshold is crossed.
Like Service + Counter AggregatorThe like record goes to Cassandra, the count via Redis INCR; the aggregator flushes to the durable store in batches every few seconds.
Ranking ServiceScores the feed with an ML model (engagement, recency, closeness); Instagram publicly uses a ranked feed.

C.6 Main flows

Post publish and fan-out on write:

  1. The client first gets a pre-signed URL from /media/upload-url and uploads the photo directly to S3.
  2. The client sends POST /v1/posts with an Idempotency-Key.
  3. The API Gateway checks auth and rate limit; the request reaches Post Service.
  4. Post Service creates a Snowflake post_id, writes to the Cassandra posts table and returns 201 to the client.
  5. Post Service puts an event on the Kafka topic post-created (author_id, post_id, ts).
  6. The Fan-out Worker reads the event; if the author is a celebrity it skips (pull model), otherwise it paginates the followers list.
  7. The post_id is ZADDed into every active follower's Redis feed:{id} sorted set, trimmed at size 500.
  8. Inactive followers (no login for 30 days) are skipped; their feed is rebuilt at login.

Home feed read (hybrid merge):

  1. The client sends GET /v1/feed?cursor=.
  2. Feed Service reads the top N post_ids of feed:{user_id} from Redis (after the cursor).
  3. It picks the celebrities from the following list and pulls their latest posts from Cassandra/cache.
  4. The two lists are merged and the Ranking Service scores them.
  5. The post IDs are hydrated with a multi-get from the post cache (Redis); Cassandra on a miss.
  6. Like counts from the Redis counters and the CDN media URLs are attached.
  7. The response is returned with next_cursor; the client loads images from the CDN.

Liking and counter update:

  1. The client sends POST /v1/posts/{id}/like.
  2. Like Service inserts (post_id, user_id) into the Cassandra likes table; if it already exists it is a no-op (idempotent).
  3. For a new like, INCR likes:{post_id} in Redis.
  4. The like event goes to Kafka for notifications and analytics.
  5. Every 5-10 seconds the Counter Aggregator persists the Redis counts in batches to the Cassandra counter table.
  6. The client sees the updated count instantly through an optimistic UI.

C.7 Deep dives

Fan-out on write (push) vs on read (pull) = hybrid
  1. 1.Normal user (avg 200 followers): PUSH. Post -> Kafka post-created -> Fan-out Worker (followers list)
  2. 2.ZADD post_id into feed:{f1}, feed:{f2}, feed:{f3}, trim at size 500; skip inactive f4 (30 days inactive = skip, rebuilt at login)
  3. 3.Celebrity (10 crore followers): PULL. Pushing would be 1 post = 10 crore Redis writes = minutes; so skip fan-out, the post stays only in the Cassandra author partition
  4. 4.The follower opens the feed (GET /v1/feed): at read time the celeb's latest posts are pulled from Cassandra + merged; few celebs are followed

Push: fast reads (precomputed) but expensive writes, wasted on inactive users. Hybrid = push for most, pull for celebrities; the Follow Service sets the is_celebrity flag when the threshold is crossed.

Home feed read: hybrid merge
  1. 1.Redis feed:{uid}: top N IDs after the cursor; celebs' latest posts: following list -> Cassandra / cache
  2. 2.Merge both lists -> Ranking Service score
  3. 3.Hydrate: Redis post cache multi-get; miss -> Cassandra; drop deleted / unfollowed posts
  4. 4.Attach like counts (Redis) + media CDN URLs
  5. 5.Response: 20 posts + next_cursor (Snowflake ID = time order)
Like + hot counter
  1. 1.POST /posts/{id}/like -> Cassandra likes (post_id, user_id unique; repeat = no-op)
  2. 2.Redis INCR likes:{post_id} on a new like
  3. 3.Hot post? Shard into N sub-keys, sum on read
  4. 4.Kafka like event -> notification, analytics
  5. 5.Aggregator every 5-10 sec -> Cassandra counter

Problem: the celebrity problem (an account with 10 crore followers). With fan-out on write one post = 10 crore Redis writes, taking minutes. So hybrid: push for normal users, pull at read time and merge for celebrities (for example >10 lakh followers).

Problem: fan-out on write vs fan-out on read. The push model has fast reads (precomputed) but expensive writes, wasted on inactive users. The pull model has cheap writes but queries N followees on every read. Hybrid is best: push for most, pull for celebrities.

Problem: the like counter of a hot post (lakhs of likes per minute). To avoid contention on one DB row use Redis INCR; for very hot keys shard the counter into N sub-keys and sum on read. Flush in batches to the durable store.

Problem: the timeline cache grows very large. Store only post IDs (8 bytes), not the whole post. Trim every feed at 500 entries, keep only active users' feeds in the cache; rebuild inactive ones from Cassandra at login.

Problem: globally unique, time-sortable post IDs. Snowflake-style 64-bit ID: timestamp + machine id + sequence. Unique without a central DB, and the ID itself gives time ordering, which is useful for cursor pagination.

Problem: a post still shows in the feed after an unfollow or post delete. Filter at read time: while hydrating, drop deleted posts and unfollowed authors. A background job cleans up the timelines; eventual consistency.

C.8 Trade-offs

DecisionWhat was chosen and why
Push vs Pull vs Hybrid fan-outChose hybrid: fast precomputed reads for 99% of users, and avoids the write explosion for celebrities.
Cassandra vs MySQL for postsCassandra: write-heavy, a time-series-like access pattern, easy horizontal scaling; no joins needed.
Exact vs approximate like countApproximate real-time (Redis) + eventually exact (batch flush); a user does not care about 1,23,456 vs 1,23,460.
Ranked vs chronological feedA ranked feed raises engagement but needs ML infra and adds complexity; an MVP can start with chronological.
Strong vs eventual consistencyEventual: availability and latency matter more; it is fine if a post shows up 5 seconds late.

C.9 Interview tips

  • State the read:write ratio first: it is read-heavy, which makes the case for precomputing (fan-out on write).
  • Raise the celebrity problem yourself and explain the hybrid model; this is what the interviewer wants to hear.
  • Show why the timeline cache holds only IDs, and the memory math.
  • Mention hot-key sharding and batch flush for the like counter.
  • Do not forget to handle media on a separate path (pre-signed S3 + CDN).