Designing Data-Intensive Applications
Case 5

Back-of-the-Envelope Estimation

Ballot write spikes, results read QPS, and why tallies must be pre-aggregated.

Order-of-magnitude checks catch designs that cannot work. Adjust assumptions for your interview scope — exact numbers matter less than which component becomes the bottleneck.

Assume national election: 100M eligible voters, 70% turnout = 70M ballots, 80% cast in final 4 hours, ballot row 500 B, results page polled every 5s by 10M viewers.

Write traffic

  • Ballots in 4 hours: 56M / 14,400s ≈ 3,900 ballots/s sustained in peak window.
  • Spike at poll close if everyone waits: 70M in 10 min ≈ 116,000/s — must queue or stagger; edge rate limiting essential.
  • Append-only INSERT — no UPDATE contention; partition by election_id.

Read traffic

  • Results page: 10M users / 5s refresh ≈ 2M reads/s — served from Redis/materialized counts, never COUNT(*) on ballot table.
  • Eligibility check: 70M once per voter ≈ 19,400/s if spread over 1 hour before peak.

Storage

  • Ballots: 70M × 500 B ≈ 35 GB — trivial for Postgres; audit log retention is the policy driver.
  • Aggregator state: 500 choices × 8 B count ≈ 4 KB — fits in memory; Redis replica for HA.

Network & caching

  • Ballot POST ~1 KB request × 116k/s spike ≈ 116 MB/s ingress — API gateway + WAF at edge.
  • Results JSON ~2 KB × 2M/s ≈ 4 GB/s egress at peak — CDN cache results page or aggressive edge caching.
  • Cache warming: precompute totals on each ballot event; read path is O(1) per choice_id.
typescript — Voting peak ballot writes
// National election peak ballots
const ballots = 70_000_000;
const sustainedWindowSec = 4 * 3600;
const sustainedQps = ballots * 0.8 / sustainedWindowSec;
const spikeQps = ballots / (10 * 60); // everyone in 10 min
console.log({ sustainedQps: Math.round(sustainedQps), spikeQps: Math.round(spikeQps) });
Order-of-magnitude summary

Peak ~4k ballots/s sustained (116k/s worst-case spike), ~2M results reads/s from cache. Never aggregate live from raw ballot table on read path.

Key Takeaways
  • ~4k sustained ballots/s in peak window; plan for 116k/s worst-case spikes.
  • ~2M results reads/s served from Redis — never COUNT(*) on ballot table live.
  • Ballot storage is modest (~35 GB); audit policy drives retention cost.
  • Aggregator state fits in memory; cache warming on each ballot event.
QPSappend-onlyRedisaggregatorpeak traffic