Concrete scenario
Several workers generate sortable IDs concurrently, then one virtual machine resumes with an older clock and a reused worker ID.
Mechanics
Pack a custom-epoch timestamp, unique worker ID, and per-tick sequence into an integer. Concurrent callers need an atomic counter, lock, or single-threaded owner; worker IDs need leases or durable assignment. On rollback, wait with a bound, change an epoch/worker component, or fail safely according to the contract.
Failure mode
Clock rollback plus worker-ID reuse can duplicate IDs. Sequence exhaustion blocks until a later tick, and restarting without preserving assignment can overlap another generator.
Trade-off
Snowflake-style IDs are compact and roughly time ordered, but depend on clock and worker management. UUIDv4 avoids those dependencies; UUIDv7 offers time locality with a larger representation.
Worked example: the numbers behind the 64 bits
| Field | Bits | Capacity | Design note |
|---|---|---|---|
| Sign | 1 | Always 0 | Keeps IDs positive in signed BIGINT and Java long |
| Timestamp (ms since custom epoch) | 41 | 2^41 ms, about 69.7 years | Twitter epoch 1288834974657 (Nov 2010) |
| Worker ID (datacenter 5 + machine 5) | 10 | 1,024 generators | Must be unique among live generators |
| Sequence | 12 | 4,096 per ms per worker | Resets each millisecond |
Throughput: 4,096 per ms is 4.096M IDs/s per worker, and 1,024 workers give about 4.2B IDs/s cluster-wide. If a worker exhausts 4,096 in one millisecond, it spins until the next millisecond.
Tuning the split: if you only ever need 64 workers but bursts of 50k IDs/ms, use 6 worker bits and 16 sequence bits. Sonyflake uses 39 bits of 10 ms units (about 174 years), 8-bit sequence and 16-bit machine ID, trading per-ms throughput for lifespan and more machines. Instagram uses 41 bits of time, 13 bits of logical shard ID and 10 bits of per-shard sequence, generated inside Postgres.
Generator logic
Failure modes and defences
- Clock rollback: keep
last_tsin memory, wait out small regressions (a few ms), and refuse on large ones. Persisting a high-water mark periodically (for example, every second) defends against restarts with a bad clock. Configure NTP (chrony) to slew rather than step. - Worker ID reuse: two processes with the same worker ID in the same ms will collide. Use leases with fencing: a process that loses its lease must stop issuing IDs before the lease can be reassigned.
- Multi-threading: the (last_ts, seq) pair must update atomically, using a mutex or a CAS on a packed 64-bit state.
- Ordering is only k-sorted: IDs from different workers interleave within clock skew, so do not use them for strict global ordering.