Deepanshu's Diary

From Chronological Timelines to Scalable Feeds: Fan-Out Strategies and Operational Trade-offs

--system-designfeedsbackend

From Chronological Timelines to Scalable Feeds: Fan-Out Strategies and Operational Trade-offs

I ran a small product experiment that started as a simple chronological timeline and hit a hard limit: a handful of popular accounts focused most traffic, and our write path strained under bursts when they posted. That concrete failure crystallized a trade-off I now use when designing feeds: timelines are easy to reason about, but real traffic patterns, personalization needs, and moderation requirements force precomputation, sharding, ranking, and operational controls. In this post I show a practical mental model, a compact architecture and data flow, what I’ve implemented as experiments, common failure modes with mitigations, and a pre-launch checklist.

In this post I explain a mental model for turning a timeline into a feed system, give an example architecture and data flow, show what I built around these ideas, list where this design breaks (with mitigations), and finish with a practical checklist you can use before you ship.

A mental model

Think of a timeline as the user's view over three abstractions:

- Events: immutable items produced by actors (posts, comments, likes). - Streams: how events are grouped for delivery (author stream, topic stream, user-follow stream). - Presentation: the transformation of those events for a user (filtering, ranking, enrichment).

A feed system coordinates producers, storage, and presentation pipelines. Two fundamental approaches appear early in design decisions:

- Fan-out-on-write (push): when an event is created, the system writes a copy into each follower’s timeline. This gives low read latency but high write amplification. - Fan-out-on-read (pull): the system stores events once and composes a personalized feed at read time. This reduces write amplification but increases read CPU and latency.

Most realistic systems blend both approaches: precompute for heavy hitters (high-fanout users), compose for long tails, and use caches for hot reads.

Architecture / data-flow example

Below is a compact example I use when designing small-to-medium feed systems.

1) Producers (mobile/web API) -> Event ingestion queue (Kafka/Rabbit) with event-id and causal metadata. 2) Worker pool consumes events; distinguishes heavy-hitter authors vs normal authors. - Heavy hitters: fan-out worker writes event references into per-user timeline store (Redis sorted sets), and to a durable event store (Postgres/Cassandra). - Others: event stored only in durable event store; pointers kept in author/topic streams. 3) Ranking service: when a user requests a feed, the API composes candidate events by merging precomputed per-user entries + recent author/topic candidates, then applies a ranking function and enrichment (e.g., attachments, citation links). 4) Response cache (CDN/edge or Redis) serves repeated requests; background jobs refresh hot caches.

A small table clarifies responsibilities:

| Component | Purpose | Example tech | |---|---:|---| | Event Store | Durable canonical events | Postgres/Cassandra [event_id, ts, payload] | | Timeline Store | Fast per-user ordered pointers | Redis sorted set, TTLed | | Queue | Decouples ingestion & processing | Kafka / RabbitMQ | | Ranker | Scores candidates per user | Microservice (Python/Go) | | Cache | Low-latency reads | Redis / CDN |

Key engineering details: idempotent event processing (unique event IDs), stable timestamps or logical clocks to avoid re-ordering, and honoring deletion/takedown via tombstones or incremental reconciliation.

What I built around this idea

I’ve been iterating on feed-adjacent infrastructure across several projects in my portfolio. Relevant pieces I implemented or am actively evolving:

- Deepanshu Blogs and growth analytics: a local-first blog generator that produces canonical HTML, RSS and sitemap, and the beginnings of growth analytics hooks (implemented locally). This is useful as a consumer of feed-like aggregation (RSS + discovery UI) and for experimenting with composition and caching strategies; the 31-post editorial batch is new content prepared locally without external deployment (implemented) [repo link]. - Google WhatsApp Scraper – Growth Engine UI: implemented lead classification, service matching, and a growth-engine UI that composes candidate leads for a reviewer to act on. This is an operationally similar pattern to feed rank-and-review: candidate generation, UI enrichment, and review gates (implemented). Sending messages remains gated and is review-only (deployment/operational gate). - ARIL monorepo: tenant-scoped knowledge bases, documents, chunks, embeddings, and retrieval components (implemented). These tools are useful when you want to personalize feeds based on semantic signals (e.g., embedding-similarity for topical feeds). Real production Postgres migration is still an environment gate.

I use these projects as testbeds for components such as ranking hooks, enrichment pipelines, and protected routes. Where I say "implemented" I mean working locally with tests and fixture runs; where I say "gate" I mean deployment or external integration required (e.g., migrating a Postgres cluster, enabling live sends, or connecting to real provider APIs).

Where this breaks (failure modes and mitigations)

1) Hotspot / fanout storm: a celebrity account posts and you get millions of writes. - Mitigation: detect high-fanout authors and switch them to fan-out-on-read with caching; rate-limit or shard fan-out worker pools.

2) Deletion / privacy compliance (GDPR): fan-out copies make deletions hard. - Mitigation: store tombstones in the event store and run background reconciliation to remove or mask fan-out entries; keep canonical source of truth that can invalidate cache entries.

3) Duplicate or inconsistent items after retries and replays. - Mitigation: make writes idempotent using event_id or dedup tables; use causal metadata and monotonic sequence numbers per author.

4) Out-of-order displays due to clock skew or async pipelines. - Mitigation: use logical timestamps (event sequence numbers) or reconciliation passes that re-sort timelines based on authoritative ordering.

5) Personalized ranking bias or stale models. - Mitigation: degrade to simple heuristics when model predictions are stale; monitor drift and roll out model updates with canaries.

6) Abuse / spam flooding the feed. - Mitigation: spam classification in ingestion, rate-limits per actor, review gates for suspicious content.

A practical checklist

- Use unique event IDs and require idempotent ingestion handlers. - Decide early: which actors are heavy hitters and how will you treat them (precompute vs compose). - Store a canonical event in durable storage; make fan-out stores pointers only. - Implement tombstones for deletions and test reconciliation jobs. - Add per-author and per-user rate limits and backpressure monitoring. - Instrument queue depth, per-worker lag, and cache hit ratio; alert on thresholds. - Provide a fallback simple ranking path in case the model service or enrichment fails. - Test with replay/backfill scenarios and chaos (reordering, duplicate deliveries).

Conclusion

Turning a timeline into a resilient feed system is incremental engineering: start with a simple chronological view, add selective precomputation for hot paths, keep a canonical event store, and ensure idempotency and privacy controls. Design the operational story (backfills, deletions, rate-limits) early — those are the failure modes that bite in production. Use caches and hybrid fan-out strategies to balance read latency with write amplification.

If you want to dig into code-level examples and the test harnesses I used, check the repositories and the projects I mentioned; they contain the concrete fixture tests and local-first implementations I run when iterating on feed behaviors.

References

[1] Anthropic — Model Context Protocol: https://www.anthropic.com/news/model-context-protocol

[2] Model Context Protocol introduction: https://modelcontextprotocol.io/introduction

[3] Deepanshu — portfolio and projects: https://github.com/deepanshuvermaa/my-portfolio

[4] Project evidence: ARIL, Listenly, Google WhatsApp Scraper and others (see repositories and README): https://github.com/deepanshuvermaa/museum-of-failure

[5] Go2 payroll and GST projects (example infra and test suites): https://github.com/deepanshuvermaa/go2-payroll, https://github.com/deepanshuvermaa/go2-gst

[6] Trading engine repo with event-driven patterns used for inspiration: https://github.com/deepanshuvermaa/trading-engine

Copied!
Back to all posts