Hermes Wiki
Architecture/Challenges/design-a-distributed-id-generation-service

Design a Distributed Unique ID Generation Service

Scenario prompt

Your platform has outgrown a single database's auto-increment primary key — you're sharding across many database instances, and every service still needs globally unique, roughly-chronologically-sortable IDs for orders, events, and rows (think Twitter's Snowflake or Instagram's ticket-server problem). Requirements:

  • IDs must be unique across many nodes generating them concurrently, with no central coordinator on the hot path (a single ID-issuing service would become both a bottleneck and a single point of failure at scale)
  • IDs should sort approximately by creation time, so range scans and "give me the last N rows" queries stay cheap without a separate timestamp column or extra index
  • Clock skew or drift between nodes must never produce a duplicate ID or make time-ordering guarantees silently wrong
  • Sustained tens of thousands of IDs/sec per node, with negligible per-ID latency — this sits directly in the write path of every request

Mihir's attempt

[!todo] Write your own attempt here before reading the model solution below — how you'd let nodes mint IDs independently without colliding, and what you'd do when a node's clock jumps backward.

Model solution

Encode identity into the ID itself instead of coordinating on every request — a composite bitfield ID. The Snowflake pattern packs a 64-bit integer into three fields: a millisecond timestamp (relative to a custom epoch, ~41 bits), a worker/node ID (~10 bits), and a per-millisecond sequence counter (~12 bits). Any node can mint IDs entirely locally once it knows its own worker ID — no network round-trip, no shared counter, no lock. Uniqueness falls out of the structure: two different worker IDs can never collide, and the sequence counter guarantees uniqueness within the same node in the same millisecond.

Pay the coordination cost once, at startup, not per ID. Worker ID assignment is the one place a central authority is unavoidable — something has to guarantee no two live nodes claim the same worker ID. Doing this via a coordination service (ZooKeeper, etcd, or even a database row with a unique constraint) at process startup, not per-request, means the hot path stays fully decentralized; the coordinator is consulted maybe once per deploy, not once per order.

Treat clock regression as a correctness event, not a rounding error. If a node's system clock jumps backward (NTP correction, VM migration, leap-second smoothing gone wrong), naively using wall-clock time can mint an ID that collides with — or sorts before — one already issued. The standard defenses: refuse to generate IDs and block/error until the clock catches back up to the last-seen timestamp, or fail the node's health check entirely if skew exceeds a threshold, rather than silently emitting an ID with a stale timestamp. This is the same instinct as Idempotency Keys — treat the failure mode as first-class instead of assuming the happy path.

Compare against the alternatives explicitly, because "just use a UUID" is usually the real interview answer to defend against. UUIDv4 is fully decentralized and needs zero coordination, but it's random — terrible for B-tree index locality (every insert hits a random leaf page, fragmenting the index) and not time-sortable. A database sequence with per-shard offsets (shard 1 issues ...001, ...101, ...201, shard 2 issues ...002, ...102, ...202) is simple but caps throughput at whatever a single sequence can do and couples ID minting to database availability. UUIDv7 (timestamp-prefixed, standardized in 2024) narrows the gap by giving UUID's decentralization plus rough sortability, at the cost of more entropy per ID than Snowflake's tighter bitfield. The right choice depends on which constraint bites hardest: raw throughput and compactness favor Snowflake-style; interop with systems that expect standard UUIDs favors UUIDv7.

Gaps to revisit

  • Worker ID exhaustion at large fleet sizes — 10 bits caps you at 1024 concurrent workers; widening the field trades away timestamp or sequence bits, and that tradeoff has to be sized against your actual fleet growth curve
  • Predictability as an information leak — a sequential, time-encoded ID handed out in a public API (an order ID, an invoice number) tells a competitor your request volume and growth rate; some systems deliberately obfuscate or re-encode public-facing IDs while keeping the sortable form internal
  • Multi-region minting — if nodes in different regions each mint IDs locally, does approximate global time-ordering across regions matter for your use case, and what does clock synchronization (NTP vs. something tighter like PTP) cost to guarantee it

Principal Engineer Lens

The generalizable lesson isn't "implement Snowflake" — it's recognizing when a problem that looks like it needs a central authority (uniqueness) can be restructured so the authority is only consulted rarely instead of on every operation. That reframing — move coordination out of the hot path, encode enough structure into the data itself that nodes can act independently — shows up again and again: leader election for a partitioned scheduler, lease-based locks, even CDN cache-key design. In a Fintech or Capital Markets context specifically, this exact problem reappears as "how do you assign trade IDs or order IDs across a sharded order-entry tier without a central sequencer becoming the latency floor for every order" — the same bitfield-plus-local-minting answer applies, and defending the clock-skew handling in an architecture review is often what separates a design that merely works from one that's provably correct under failure.

Hermes Wiki