Feed / Timeline — Designed in Stages
You don’t need to design for scale on day one.
Define what you need—a feed of posts from people or sources the user follows, create post, maybe ranking—then build the simplest thing that works and evolve as followers and posts grow.
Here we use a social feed or timeline as the running example: users, posts, a follow graph, and “list my feed.” The same staged thinking applies to activity streams, news feeds, or any read-heavy, fan-out system where hot keys (e.g. celebrity feeds) can become bottlenecks.
Requirements and Constraints (no architecture yet)
Section titled “Requirements and Constraints (no architecture yet)”Functional Requirements
- List my feed — show the user a ordered list of posts (from followed users or sources), paginated (cursor or page).
- Create post — user can publish a new post; post appears in followers’ feeds.
- Optional: ranking (chronological vs algorithmic), reactions, comments, notifications.
Quality Requirements
- Read-heavy — feed reads are typically orders of magnitude more frequent than writes (create post).
- Latency target for feed load (e.g. p95 < 300–500 ms).
- Freshness: how quickly a new post appears in followers’ feeds (seconds to minutes is often acceptable for MVP).
- Expected scale: DAU/MAU, posts per second, followers per user (fan-out); hot keys when one user has very many followers (celebrity feeds).
Key Entities
- User — identity, auth.
- Post — content, author, timestamp, optional metadata (e.g. visibility).
- Feed — the ordered list of post identifiers (or post references) that a user sees; can be computed on read (pull) or precomputed on write (push).
- Follow graph — who follows whom (or what); used to determine which posts belong in a user’s feed.
Primary Use Cases and Access Patterns
- List my feed — read path; by far the dominant load; must be fast and cacheable where possible.
- Create post — write path; triggers fan-out (notify followers’ feeds) in push model, or is just a write in pull model.
- Ranking — order of posts in the feed (chronological vs score-based); affects both pull query and push precomputation.
- Fan-out and hot keys — when a user with many followers posts, push model must write to many feed entries; celebrity feeds can be a hot key (one user’s feed read very often) in pull model.
Given this, start with the simplest MVP: one API, one DB, and a clear choice between pull (query at read time) or push (precomputed feeds) in a minimal form, then evolve as read load and fan-out grow.
Stage 1 — MVP (simple, correct, not over-engineered)
Section titled “Stage 1 — MVP (simple, correct, not over-engineered)”Goal
Ship a working feed: users can create posts and view a feed of posts from people they follow. One API, one DB, minimal moving parts.
Components
- API — REST or GraphQL; auth, create post, list feed (and follow/unfollow if part of scope).
- Primary DB — stores users, posts, and follow graph; indexes to support “list posts from followed users” (pull) or “list precomputed feed entries for user” (push). For pull: index posts by author + time; for push: store feed rows (user_id, post_id, timestamp) when a post is created.
- Feed = query (pull) or precomputed (push) in simple form — either “SELECT posts FROM … WHERE author IN (followed) ORDER BY time” (pull) or “SELECT feed_entries WHERE user_id = ? ORDER BY time” (push). No ranking pipeline yet; chronological is fine.
Minimal Diagram
Client | v+-----------------+| API |+-----------------+ | vPrimary DB - users, posts, follow graph - feed: pull (query) or push (precomputed table)Patterns and Concerns (don’t overbuild)
- Auth and authorization: user can only create posts as themselves and list their own feed (or public feeds as defined).
- Indexing: support the main query—e.g. (follower_id, time) for push feed table, or (author_id, time) for posts in pull model.
- Basic logging and metrics (writes, read latency, feed size).
Why This Is a Correct MVP
- One API, one DB → simple to reason about and ship; pull is easier to implement, push avoids heavy read queries but adds write-time fan-out; either is valid for MVP.
- Vertical scaling buys you time; you can optimize pull with better indexes or switch to push (or hybrid) when read load or fan-out becomes a bottleneck.
Stage 2 — Growth Phase (more users, more posts, bottlenecks appear)
Section titled “Stage 2 — Growth Phase (more users, more posts, bottlenecks appear)”What Triggers the Growth Phase?
- Feed read latency grows (pull: complex query or large follow list; push: very long feed table per user).
- Write path slows down (push: fan-out on post create to many followers).
- Single DB becomes the bottleneck (connection pool, disk, or CPU for feed queries).
- Hot keys: one user’s feed (e.g. celebrity) read very often, or one user with many followers causing expensive fan-out on post.
Components to Add (incrementally)
- Feed cache or precomputation — cache the first page (or N items) of a user’s feed; invalidate or refresh on TTL or on new posts to that feed. For push, precomputation may already be in DB; add a cache layer in front.
- Async fan-out workers — for push model: when a post is created, enqueue a job to write feed rows for each follower (or batch); workers consume the queue and write to feed store. Keeps API response fast and spreads write load.
- Read replicas — feed reads go to replicas; writes (create post, follow) go to primary. Reduces load on primary and improves read latency.
- Push vs pull trade-off — revisit the choice: pull simplifies writes but can be heavy on read (big joins or many lookups); push spreads read load but adds write amplification and storage. Hybrid (e.g. push for most, pull for celebrities) is common at this stage.
Growth Diagram
+------------------+Clients ----------> | Load Balancer | +------------------+ | v +------------------+ | API | +------------------+ | | write | | read (feed list) v v Primary DB Read Replicas | | v v Fan-out Queue Feed Cache (hot feeds) | v Workers (write feed rows for followers)Patterns and Concerns to Introduce (practical scaling)
- Cache-aside or TTL cache for feed: reduce DB/replica load for hot feeds; invalidation strategy when new posts arrive (e.g. per-user cache key, invalidate on fan-out write).
- Async fan-out: don’t block “create post” on writing to all followers’ feeds; use a queue and workers so API stays fast and DB writes are spread over time.
- Monitoring: feed read latency (p50, p95), fan-out queue depth and lag, cache hit rate, replica lag.
Still Avoid (common over-engineering here)
- Complex ranking pipeline (ML, real-time scoring) before product needs it.
- Multi-region feed replication before you have latency or availability requirements.
- Splitting into many microservices (e.g. separate “feed service” with its own store) until the boundary is clear and the bottleneck is proven.
Stage 3 — Advanced Scale (very high read load, global, ranking)
Section titled “Stage 3 — Advanced Scale (very high read load, global, ranking)”What Triggers Advanced Scale?
- Feed store or cache becomes the bottleneck even with replicas and caching.
- You need ranking (algorithmic feed) or multiple feed types (e.g. “top” vs “latest”).
- You need low latency in multiple regions (users in EU and US).
- Hot keys (celebrity feeds, trending) require dedicated handling (e.g. separate storage or pull path for those users).
Components (common advanced additions)
- Dedicated feed store or graph store — store optimized for feed reads (e.g. key-value by user_id, ordered list of post ids or entries); or a graph store for follow graph + timeline queries. Offload from primary OLTP DB.
- Ranking pipeline — score posts (relevance, recency, engagement); run as a batch job or near-real-time; write ranked feed to feed store or cache. Separate pipeline from the write path.
- Sharding by user or timeline — partition feed data by user_id (or range) so no single partition holds all celebrity feeds; or partition by time for timeline-style access.
- Multi-region — feed reads from regional replicas or regional feed stores; write path may still be single region with async replication, or multi-region with clear consistency trade-offs.
Advanced Diagram (conceptual)
Global DNS / Geo routing | v +------------------+ | Load Balancer | +------------------+ | +----------------+----------------+ v v v Region A Region B Region C | | | API + Feed API + Feed API + Feed read replicas read replicas read replicas | | | +----------------+----------------+ | Primary (writes) + Replication | +---------------+---------------+ v v v Feed Store Ranking Pipeline Cache (hot feeds) (sharded) (batch / stream) (per user, TTL)Patterns and Concerns at This Stage
- Consistency: feed may be eventually consistent (new post appears in followers’ feeds after fan-out or ranking pipeline); define SLO for freshness.
- Hot keys: celebrity or high-fan-out users may use a different path (e.g. pull for them, push for everyone else) or dedicated partitions to avoid one key dominating.
- Ranking: separate pipeline from critical path; avoid blocking feed read on real-time scoring until you need it.
- SLO-driven ops: feed read latency, feed freshness, queue lag; error budgets and on-call.
Summarizing the Evolution
Section titled “Summarizing the Evolution”MVP delivers a feed with one API and one DB, using either pull (query at read time) or push (precomputed feed rows) in the simplest form. That’s enough to ship and learn.
As you grow, the first bottlenecks are usually feed read load and write-time fan-out—so you add read replicas, feed cache, and async fan-out workers. You revisit push vs pull and introduce a hybrid if needed (e.g. push for normal users, pull for celebrities).
At very high scale, you add a dedicated feed store or graph store, a ranking pipeline, and sharding by user or timeline. You keep the write path simple and add complexity only where read load, ranking, or multi-region require it.
This approach gives you:
- Start Simple — one API, one DB, pull or push in simple form, ship and learn.
- Scale Intentionally — add cache, replicas, and async fan-out when read load or fan-out justify it; add feed store and ranking when product and SLOs demand it.
- Add Complexity Only When Required — avoid dedicated feed service and ranking pipeline until boundaries and bottlenecks are clear.