Design Shared Counter
Global view/like counters — sharding, aggregation, and read-your-writes at billions of events/day.
Interview tip Separate hot counter shards from periodic flush to DB. Mention local aggregation, Redis INCR, and handling counter rebuild after loss.
① Functional requirements
- increment(entity_id) — add 1 or N
- get_count(entity_id) — current total
- batch_get(entity_ids[]) — mget counts
- Optional decrement for unlike
- Admin reset / adjust count
② Non-functional requirements
- 1M increments/sec peak
- get p99 < 10ms
- Counts accurate within 1% or flush every few seconds
- Horizontally scalable
- Survive Redis node loss without permanent drift
③ Back-of-the-envelope scale
Assumptions
- 100B/day ≈ 1.2M incr/sec average; 3× peak → ~4M/sec
- 1B entities — most cold, top 1% hot
- Batch flush every 5s reduces DB writes 100×
- 100 counter shards × Redis cluster
④ High-level architecture
Counter Service
Clients
Counter API
Redis shards (INCR)
Flush workers → DB
Read cache / CDN edge
increment always hits Redis shard. Periodic flush merges shard delta into Cassandra/DB. Cold entities read from DB + cache.
⑤ Data flow & execution path
Increment path
① INCR shard→② Async flush→③ Merge DB total→④ Read cache
Hot path: Redis INCR only — no DB on write
Flush: shard delta + persisted_total
Read: Redis + DB persisted + delta
Rebuild: replay increment log if Redis lost
Clarify acceptable staleness on read vs write cost. Viral video = hot key — local aggregator on API nodes.
⑥ API & interfaces
| Endpoint / flow | Purpose | Notes |
|---|---|---|
| POST /v1/incr/{id} | Increment | returns new approximate count |
| GET /v1/count/{id} | Read count | cache-aside |
| POST /v1/mget | Batch read | parallel shard fetch |
⑦ Data model & storage
Redis: counter_key → integer delta. DB: entity_id → persisted_total, flushed_at. Optional Kafka log for replay.
| Store | What | Why |
|---|---|---|
| Redis cluster | Hot counters | INCR atomic |
| Cassandra | Persisted totals | Wide-column per entity |
| Kafka | Increment log | Replay and analytics |
⑧ Deep dive — core components
Hot key mitigation
Per-API-node local buffer aggregates 1000 increments before single INCR. Risk brief loss on crash — acceptable for views. Or replicate hot key across nodes with periodic merge.
Accuracy vs cost
Flush every 5s: reads may lag few seconds. For likes show exact — shorter flush or read from Redis primary only.
⑨ Trade-offs & alternatives
| Decision | Option A | Option B | Pick when |
|---|---|---|---|
| Store | Redis + DB | DB only | Redis for speed; DB for durability |
| Flush | Time-based | Threshold-based | Time simpler; threshold fewer writes for cold keys |
| Hot key | Local aggregate | Key replicate | Local aggregate lower Redis load |
| Accuracy | Strong | Eventual | Views tolerate eventual; payments need strong |
⑩ 45-minute interview script
- 0–5 min: incr/get requirements
- 5–10 min: Scale math
- 10–20 min: Redis shard + flush
- 20–30 min: Hot key and accuracy
- 30–38 min: Failure recovery
⑪ Likely follow-up questions
| Question | Short answer |
|---|---|
| Unique viewer vs raw views? | Separate dedupe set (HyperLogLog or bloom) for unique; raw incr for total views |
| Global vs per-region counts? | Regional shards + periodic merge to global aggregate for display |
| Counter reset attack? | Rate limit incr per IP/user; cap velocity; anomaly detection on spikes |
⑫ Revision checklist
- Redis INCR sharding
- Periodic DB flush
- Hot key local aggregation
- Kafka replay log
- batch_get parallel
- Read merge persisted + delta
- Monitoring shard balance
- Rate limit abusive incr