Command Palette

Search for a command to run...

PHASE 13BAdvanced ~8 min· topic 2 of 8

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.

0/8 · 0%

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

  1. 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.

  2. 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.

  3. 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 a long, sorted by time. Requires assigning unique machine IDs (config, ZooKeeper/etcd, or pod ordinal) and handling clock going backwards (refuse or wait).

  4. 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 as uuid-creator (UuidCreator.getTimeOrderedEpoch()) or JDK support where available.

Code & diagrams

Snowflake.javajava

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

01

Why do random UUIDv4 primary keys hurt database write performance?

Practice

01

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

Back to phase