3 min readRishi

Designing Snowflake-Style IDs: Ordering, Workers, and Clock Rollback

Designing Snowflake-Style IDs: Ordering, Workers, and Clock Rollback

A random UUID is easy and it scatters across a B-tree. An auto-increment integer is ordered and it requires a single writer. A Snowflake-style id is the compromise Twitter published: a 64-bit value a machine can mint locally, roughly sorted by time, unique as long as you obey two rules.

The layout

One bit is left at zero so the value stays positive. The rest, in the original layout:

BitsFieldWhat it buys
41Milliseconds since a custom epochAbout 69 years of range, and time order
10Worker id1024 machines issuing at once
12Sequence4096 ids per millisecond per worker

Some deployments split the 10 bits into a datacenter and a worker. The math is the same. The sequence resets when the millisecond changes. If a worker needs a 4097th id in the same millisecond, it waits until the clock ticks. It does not overflow into the next worker's numbers.

Uniqueness is local. Two workers never share a worker id, and one worker never reuses a sequence inside a millisecond. No database round trip on the hot path.

The clock is part of the id

The timestamp is not decoration. If the clock steps backwards, the generator will hand out a timestamp it has already used, and the sequence may collide with an id that already left the process. NTP stepping a VM backwards is a real way to do this. The safe behavior is to refuse, or to wait until wall time catches the last timestamp you issued. Issuing into the past is a collision.

Worker ids have the same uniqueness problem across process restarts. A random 10-bit number will collide. Lease the id: a row with a TTL that the process renews, or the ordinal of a StatefulSet whose size stays under 1024. When the process dies, the lease expires and the id can be reused, but only after any id it minted is already in the past relative to the timestamp width you care about. Reusing a worker id in the same millisecond is the collision. A lease measured in seconds, and a generator that will not issue with a timestamp older than its start, is enough.

What the id is not

It is roughly ordered, not totally ordered. Two ids from different workers in the same millisecond compare by worker id, not by which request arrived first. Do not use the sort as a happens-before relationship.

It does not fit in a JavaScript number. Numbers are safe up to 2^53, and this id is larger. JSON APIs send it as a string. Clients that parse it into a number will silently round it, and the next GET will 404.

It encodes the creation time to anyone who knows the epoch. That is fine for an order id. It is the wrong primitive for a secret, a password-reset token, or an unguessable link. It is also not an idempotency key. A retry that mints a new id is a second write. The key has to come from the client and stay stable across the retry, which is the design in idempotency keys for write APIs.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

  • No comments yet. Be the first.