Distributed Unique ID Generator (Snowflake)
Problem statement
Design a service that generates unique 64-bit ids across many servers, roughly sortable by creation time, at millions of ids per second, with no single point of failure.
Requirements
Functional
Generate unique ids; 64-bit numeric.
Ids increase with time (k-sortable).
At least 10k ids/s per node; millions/s cluster-wide.
Non-functional
No coordination on the hot path; each node generates independently.
Highly available; no central DB round-trip.
Tolerate clock skew safely.
Back-of-the-envelope estimates
Snowflake layout: 1 sign bit | 41 bits ms timestamp (~69 years) | 10 bits machine id (1,024 nodes) | 12 bits sequence (4,096/ms). Per node: 4,096 ids/ms = 4M ids/s; 1,024 nodes → 4B ids/s theoretical. Way beyond any realistic need.
API
GET /v1/ids?count=1..1000 → { ids: [ ... ] }
Usually embedded as a library in each service rather than a network call.
Data model
No persistent data on the hot path. Machine ids are assigned from a coordination service (ZooKeeper/etcd) at startup; the last-used timestamp may be checkpointed to detect clock rollback.
High-level design
- Options considered: UUIDv4 (128-bit, random, not sortable, index-unfriendly); DB auto-increment (single point, hard to shard); ticket server (Flickr's MySQL with
REPLACE INTO, simple but a bottleneck); Snowflake (chosen); UUIDv7/ULID (128-bit but time-sorted — good when 64-bit isn't required). - Snowflake node: on each call, read current ms; if same ms as last, increment sequence; if sequence overflows, spin until next ms; if the clock went backwards, refuse or wait.
- Machine id assignment: from ZooKeeper ephemeral sequential nodes, or from the pod/instance metadata; must be unique across the fleet.
- Library, not service: embed in each application process to avoid an RPC per id.
Architecture diagram
Deep dives
Clock skew: NTP keeps skew small but leap seconds and VM pauses can move time backwards. Strategies: refuse to generate until the clock catches up; keep a 'logical' clock that never decreases; or use a few bits of the machine-id space to bump a 'epoch/incarnation' after a rollback.
Custom epoch: start the 41-bit timestamp at your launch date to extend lifetime.
Sortability caveat: ids are sortable only to millisecond precision and only across machines with synced clocks — good enough for DB index locality, not for ordering guarantees.
Bottlenecks & mitigations
Sequence overflow within a ms on a very hot node → spin to the next ms (rare) or add more nodes.
ZooKeeper down at startup → fall back to a pre-assigned id from config; never generate without a unique machine id.
Failure modes
- Two nodes with the same machine id (misconfiguration): duplicate ids — enforce with ephemeral znodes and fail startup on conflict.
- Clock jumps backwards: detected by comparing with the last timestamp; block or bump incarnation.
- Node restart within the same ms: sequence resets to 0 → possible duplicate; persist last timestamp and wait 1 ms on start.
Follow-up questions interviewers ask
When would you prefer UUIDv7/ULID over Snowflake?
How would you generate ids that reveal nothing about volume (non-sequential)?
How does MongoDB's ObjectId compare?