system design · system-design
Design a Distributed Counter (Sharded + Eventually Consistent)
Sharded counters, eventual consistency, write-heavy. View counts, like counters, page views.
Theory
Explanation
Intuition first, formal definition second. Skim the bullets if you already know this; read the prose if you don't.
Counter on one row = hotspot for popular content (1M writes/sec on a single key). Shard counter across N keys; sum at read. Reads are slightly more expensive (N reads); writes scale linearly.
Write picks random shard from {key:0, key:1, ..., key:N-1}, INCR. Read SUM all shards. Periodic rollup compacts shards into snapshot row. CRDT (G-counter) is the formal abstraction. Approximate counts via HyperLogLog for very-large unique cardinality.
When to use
Likes, views, votes, click counters at scale.
When not to
Tiny counters (single key fine). Strict-monotonic global counters (use Spanner).
flowchart LR Client([Client]) --> LB[Load Balancer] LB -->|random shard| S0[(Shard 0)] LB -->|random shard| S1[(Shard 1)] LB -->|random shard| SN[(Shard N)] Reader[Read API] --> S0 Reader --> S1 Reader --> SN Reader --> Sum[SUM] Rollup[Daily Rollup] -.compact.-> Snapshot[(Snapshot)]
Key insights
- Shard count determines write capacity ceiling. 10 shards = 10x single-key write rate.
- Read amplification = N. Pick N to balance read vs write cost.
- CRDT semantics: add-only counters monotonically grow; convergent on merge.
- For unique-count (e.g., unique viewers), use HyperLogLog (HLL), fixed memory, approximate.
- Rollup reduces read amplification long-term: snapshot + shards delta since snapshot.