Web fundamentals, an interactive architecture walkthrough

Distributed unique ID generator: how the architecture works

How a distributed ID generator packs a timestamp, worker id and sequence into 64 bits so ids are unique and roughly time-ordered.

This is a learning explainer based on public engineering writing and general design practice. It is simplified, it is not the actual architecture of any company, and it may be inaccurate.

25 components, 18 connections, 12 steps. Updated 4 November 2026.

Interactive diagram

Overview

A distributed unique ID generator gives every new record an id that no other machine will ever issue, without a central counter on the hot path. The common design packs a millisecond timestamp, a worker number and a per-millisecond sequence into a 64-bit integer. The only coordination is handing each generator a unique worker number at startup, after which every node issues ids independently.

Because the timestamp is in the high bits, ids sort roughly by creation time, which keeps database index inserts sequential and lets an id stand in for a created-at value. The generator can run as a small network service or as a library inside each caller. Older and newer alternatives are shown alongside: a ticket database that hands out ranges, and UUIDv7 or ObjectId-style ids that need no coordination. This is a simplified learning design, not any company's production system.

Trade-offs and what changes at scale

Ordering depends on clocks, so ids from different nodes are only roughly sorted and a clock that moves backwards must stop the node rather than risk duplicates. 64 bits fit native integer columns and are compact, but the bit split fixes the limits: about 69 years from the epoch, 1,024 workers and 4,096 ids per worker per millisecond. Ids reveal creation time and volume, so they should not be the only protection on a public URL. Random 128-bit ids avoid coordination entirely but are larger and, when fully random, scatter index writes.

How it works, step by step

The walkthrough above, as text. Each step lights up the components named under it.

  1. When one of the ID Generator Nodes starts, it leases a worker id from ZooKeeper as an ephemeral node that disappears if the process dies. A unique worker id is the only coordination the scheme needs; after that each node issues ids on its own.

    Components ZooKeeper, ID Generator NodesVia lease worker id

  2. Each node keeps its clock in step with the NTP Servers. Ids are only as ordered as the clocks that produce them, so clocks across nodes need to agree within a few milliseconds.

    Components NTP Servers, ID Generator NodesVia time sync

  3. The Order Service takes ids from its local ID Buffer, which refills a batch through the Internal Load Balancer, and one of the ID Generator Nodes packs the current millisecond, its worker id and a sequence number into each id. Batching spreads one network round trip over many ids.

    Components ID Generator Nodes, Order Service, ID Buffer, Internal Load Balancer, 64-bit layoutVia gRPC

  4. Within one millisecond each of the ID Generator Nodes can issue 4,096 ids (the 64-bit layout's 12-bit sequence); after that it waits for the next millisecond. With 1,024 workers that is about 4 million ids per millisecond across the fleet.

    Components ID Generator Nodes, 64-bit layoutVia gRPC

  5. If the system clock jumps backwards, the Clock Guard makes the node refuse to issue ids until time catches up. Issuing ids with an old timestamp could repeat an id already handed out.

    Components Clock Guard, ObservabilityVia monitor clock

  6. Because ids rise with time, new rows in the Orders DB land at the right edge of the primary key B-tree instead of at random pages. Random UUIDv4 keys scatter inserts across the index and cause more page splits.

    Components Orders DBVia insert row

  7. The Message Service uses ids as the clustering key in the Messages DB, so "messages before id X" is a range scan and no separate timestamp column is needed for ordering. Ids from different nodes are only roughly ordered, within the clock skew between them.

    Components Messages DB, Message ServiceVia insert row

  8. The Upload Service instead runs the Embedded Generator in its own process, with a worker id leased the same way. This removes the network hop, at the cost of every caller needing a unique worker id.

    Components Embedded Generator, ZooKeeperVia in-process call

  9. An older alternative is the Range Allocator: it bumps a counter in the Ticket DB by 1,000 and the caller hands out that block locally. Ids are dense and short, but they reveal volume and are only ordered per block.

    Components Range Allocator, Ticket DBVia alt: get block

  10. Ids also key events on the Event Stream and rows in the Analytics Store, where the creation time can be read from the id itself. UUIDv7 and ObjectId-style ids give similar ordering without a worker id, using more bits.

    Components Event Stream, Analytics Store, No-coordination idsVia order created

  11. The ID Inspector decodes any id back into its timestamp and worker, which helps when tracing a record to the node that created it. Anyone who sees the ids can do the same, so they leak creation time and rough volume.

    Components ID InspectorVia decode ids

  12. Observability tracks ids issued per second, sequence exhaustion and clock offset per node. A node that keeps waiting for the next millisecond is a sign it needs more workers beside it.

    Components Observability, ID Generator NodesVia metrics

Sequence diagram

The same walkthrough as a UML sequence diagram, one lane per layer or group (18 components in all), left to right in the order a request reaches them, numbered by step. A loop on a lane is a step that stays inside that group. Solid arrows are synchronous calls, dashed are asynchronous.

Distributed unique ID generator: sequence diagram of the walkthroughID ServiceCoordinationInternal LoadBalancerCallersObservabilityWhere ids end upAlternativesID Inspectorlease worker id1time sync2gRPC3take id3refill batch3gRPC4monitor clock5skew alerts5insert row6insert row7get ids7in-process call8lease worker id8alt: get block9increment by 1,0009order created10load10decode ids11metrics12

Key components

Coordinationdomain

  • NTP Servers: keep node clocks in sync
  • ZooKeeper: leases unique worker ids (zookeeper)

ID Servicedomain

  • Clock Guard: refuses ids if the clock goes back
  • Embedded Generator: same layout, runs in the caller
  • ID Generator Nodes: timestamp + worker id + sequence (grpc)

Callerslayer

  • ID Buffer: prefetched batch of ids per caller
  • Message Service: needs sortable message ids
  • Order Service: needs an id per order
  • Upload Service: embeds the generator as a library

Internal Load Balancer

  • Internal Load Balancer: spreads calls across ID nodes

Observability

  • Observability: ids/sec, clock skew, sequence exhaustion

Where ids end uplayer

  • Analytics Store: time derived from the id (clickhouse)
  • Event Stream: events keyed by id (kafka)
  • Messages DB: clustering key on id (cassandra)
  • Orders DB: B-tree primary key on id (postgresql)

Alternativesdomain

  • Range Allocator: hands out blocks of 1,000 ids
  • Ticket DB: auto-increment counter row (mysql)

ID Inspector

  • ID Inspector: decodes time and worker from an id

Sources

Public writing this explainer draws on. Read these for what the real systems actually do.

Make it yours

Copy this diagram and its walkthrough into your own workspace, then change anything: swap Postgres for DynamoDB, add a cache, rewrite the narration. Free, and it uses no AI credits.

This is a learning explainer based on public engineering writing and general design practice. It is simplified, it is not the actual architecture of any company, and it may be inaccurate. Real systems differ in the details. Browse the library.