design·lab

Data & storage

Distributed IDs

Mint globally unique, roughly time-sortable ids from independent nodes — without ever asking a central sequence for "the next number."

✗ The problem

Auto-increment doesn't scale across nodes

A single incrementing primary key needs one place that knows "the last value" — that's a coordination bottleneck and a single point of failure. Split it across shards with no coordinator and each node's own counter will mint the SAME id.

-- one sequence, one source of truth
CREATE TABLE orders (
  id SERIAL PRIMARY KEY  -- next id? ask the DB, every insert waits
);

// shard A and shard B, no coordinator:
shardA.nextId()  // -> 5
shardB.nextId()  // -> 5   ✗ COLLISION
Central sequence = coordination bottleneck + single point of failure — and the raw counter also leaks how many rows you have.
✓ How it works

Coordination-free id schemes

Each node mints its own ids from local state — no round-trip to a shared counter.

// UUIDv4 — 122 random bits
"e2c1-4a9b-...-91fd"  // unique, unordered

// Snowflake — 64 bits, no coordinator
id = (time << 22)
   | (node << 12)
   | (seq);        // seq resets each tick

// ULID — timestamp + randomness
"01ARZ3-NDEKTSV4RRFFQ69G5FAV"
41-bit time
ms since epoch
+
10-bit node
machine id
+
12-bit seq
per-tick counter

Result: Snowflake ids are globally unique, roughly time-sortable, and compact — UUIDv4 is simplest but random and index-unfriendly — ULID mixes both.

✓ See it live

Two nodes, one shared clock, zero collisions

Both nodes only share a logical clock. Tick it, mint from either node — every id stays unique with no coordinator involved.

Logical clock 5
Node 1
mint to see an id
Node 2
mint to see an id
Mint from both nodes — ids stay unique, no coordinator involved.
Contrast — two auto-increment nodes, no coordinator:
Node A
id = 0
Node B
id = 0
click to start…
✓ Takeaway

Unique ids, no central sequence

  • Coordination-free: every node mints ids from its own state — no shared counter to bottleneck on or fail over.
  • Snowflake = sortable + compact — the time prefix gives good index locality (B-tree friendly), unlike random ids.
  • UUIDv4 = simplest to adopt but fully random — no ordering, worse index locality.
  • Watch clock skew: if a node's clock jumps backward, its "time-sortable" ids can briefly go out of order — sync clocks (NTP) and monitor drift.
  • Once ids scale across nodes, the next question is which node owns which data — see sharding.