Topic 13B.2
Unique ID Generation at Scale
In one line
Auto-increment IDs need one central counter; random UUIDs scatter indexes. Distributed systems use time-ordered IDs (Snowflake, UUIDv7, ULID) that are unique without coordination and sort by creation time.
Think of it like this
Numbering tickets at a festival with many entry gates. One shared ticket roll (auto-increment) makes every gate queue for the roll. Instead, each gate prints tickets as 'time + gate number + counter': unique without talking to each other, and you can still tell roughly when each was issued.
Key ideas
- 01
AUTO-INCREMENT: simple, compact, ordered, but the single database is a bottleneck and a single point of failure, IDs leak business volume ('order 1,000,432'), and merging data from shards collides.
- 02
UUIDv4 (random 128-bit): no coordination at all, but random values insert all over a B-tree index (page splits, poor cache locality) and aren't sortable. UUIDv7 and ULID fix that: they start with a millisecond TIMESTAMP followed by randomness, so they're unique AND roughly time-ordered and index-friendly. UUIDv7 is the default choice today for most databases.
- 03
SNOWFLAKE (Twitter's design, 64-bit):
41 bits timestamp (ms) | 10 bits machine ID | 12 bits sequence. Up to 4,096 IDs per millisecond per machine, fits in along, sorted by time. Requires assigning unique machine IDs (config, ZooKeeper/etcd, or pod ordinal) and handling clock going backwards (refuse or wait). - 04
Other options: TICKET SERVERS / segment allocation (a service hands out blocks of 1,000 IDs from a database counter, used by Flickr and Meituan's Leaf); and short IDs for URLs by base62-encoding a number (Phase 12, URL shortener).
Java / Spring map
- →
UUID.randomUUID()is v4; for UUIDv7 use a library such asuuid-creator(UuidCreator.getTimeOrderedEpoch()) or JDK support where available.
Code & diagrams
41-bit time | 10-bit worker | 12-bit sequence. Synchronized for simplicity; real ones are lock-free.
final class Snowflake {
private static final long EPOCH = 1_700_000_000_000L; // custom epoch extends the 69-year range
private final long workerId; // 0..1023, unique per instance
private long lastMs = -1, seq = 0;
Snowflake(long workerId) { this.workerId = workerId; }
synchronized long next() {
long now = System.currentTimeMillis();
if (now < lastMs) throw new IllegalStateException("clock moved backwards");
if (now == lastMs) {
seq = (seq + 1) & 0xFFF; // 4096 per ms
if (seq == 0) while ((now = System.currentTimeMillis()) <= lastMs) { } // wait for next ms
} else seq = 0;
lastMs = now;
return ((now - EPOCH) << 22) | (workerId << 12) | seq;
}
}Explain without notes
Why do random UUIDv4 primary keys hurt database write performance?
Practice
Pick an ID scheme for (a) orders in a sharded database, (b) short links, (c) internal events.
Trade-offs
- ↔
Central counters are simple but a bottleneck; random UUIDs need no coordination but fragment indexes; time-ordered IDs balance both but expose creation time and depend on sane clocks.
Completion checklist
I can explain Snowflake's bit layout
I default to time-ordered IDs (UUIDv7/Snowflake) in distributed systems