Design a Distributed Unique ID Generator
1. Problem Statement & Scope Clarification
System Mission
Design a distributed, highly available, low-latency 64-bit unique ID generation service (similar to Twitter Snowflake / Instagram ID generator) capable of generating globally unique, roughly time-sortable 64-bit numeric IDs across multiple data centers and AWS regions with zero runtime inter-node network coordination.
Functional Requirements
- Global Uniqueness: Zero ID collisions across all AWS regions, availability zones, and worker instances over a 70-year operating horizon.
- 64-bit Integer Representation: Must fit inside a standard 64-bit signed integer (
int64/BIGINTin SQL databases) to minimize primary key index bloat and fit cache lines efficiently. - Roughly Time-Ordered (K-Ordered): Chronologically sortable by generation timestamp to prevent database B-Tree index fragmentation and random page splits on insertion.
- High Throughput & Low Latency: Support aggregate throughput per cluster with sub-millisecond () generation latency.
- Batch Generation: Support atomic batch requests (up to 1,000 IDs per call) for high-throughput transactional ingest pipelines.
Non-Functional Requirements (SLAs & SLOs)
- Availability: ("six nines") uptime SLA — ID generation is in the critical path of every write request in an enterprise architecture.
- Latency: , , .
- Clock Drift Safety: Absolute monotonicity within individual worker processes; graceful wait-or-reject semantics if system hardware clock steps backward.
- Client JSON Compatibility: Safe serialization as string for 64-bit unsigned integers to avoid IEEE-754 double precision truncation in JavaScript clients (
Number.MAX_SAFE_INTEGER).
2. Capacity & Scale Estimation (Back-of-the-Envelope Math)
Traffic & Generation Scale
- Global Average Write QPS: .
- Peak Write QPS ( multiplier): .
- Annual ID Volume:
- Downstream Primary-Key Footprint (the generator itself stores nothing; this is what consumers pay to index the IDs, and it is why 8-byte IDs beat 16-byte UUIDs): (vs. 151.5 TB with 128-bit UUIDs)
Bit-Space Longevity & Allocation Proof
A 64-bit integer is structured into 4 discrete bitfields:
- Sign Bit (1 bit): Fixed to
0to keep the integer strictly positive in signed 64-bit systems. - Timestamp (41 bits): Milliseconds elapsed since a custom epoch (e.g.,
2026-01-01T00:00:00Z= ). The service remains operational until year . - Data Center ID (5 bits): unique data center / AWS region partitions (e.g., 0 =
us-east-1, 1 =us-west-2, 2 =eu-west-1, etc.). - Worker Machine ID (5 bits): unique worker nodes per data center. Total possible concurrent generator instances globally .
- Sequence Number (12 bits): unique IDs per millisecond per individual worker node.
Peak Theoretical Generation Capacity
This capacity exceeds the peak requirement by over , providing massive headroom for multi-tenant enterprise growth.
3. High-Level Architecture & AWS Component Mapping
Synthesizing vector architecture diagram...
Start with the clients in the "Client Applications & Ingestion Pipelines" panel. API gateways and the order service call the generator fleet over gRPC through an internal NLB, while the high-volume event stream skips the network entirely and embeds the generator as a library (dotted arrow). In the "ID Generator Fleet" panel, each worker creates IDs locally; to stay unique, it must own a worker ID nobody else uses. That is the job of the "Worker ID Lease Coordination Tier" panel: each worker holds a lease in DynamoDB, renewed every 30 s, so a crashed worker's ID frees up only after its lease expires, and it syncs its clock to Amazon Time Sync. Uniqueness depends on two things only: no shared worker IDs (the lease) and a clock that never goes backward (time sync plus a monotonic check).
Data Flow Walkthrough
- Zero-Coordination ID Ingress: Upstream clients (or embedded libraries) issue
GenerateIdrequests via internal L4 NLB over persistent multiplexed gRPC connections to local ECS Fargate workers. - Atomic In-Memory Assembly: The worker evaluates the local monotonic clock (). If within the same millisecond as previous generation, it atomically increments its 12-bit sequence counter. It bit-shifts timestamp, datacenter ID, worker ID, and sequence into a single 64-bit integer in with zero network coordination.
- Lease Coordination & Fencing: Each worker dynamically maintains a 30-second lease in DynamoDB. A background thread heartbeats every 10 seconds. If a heartbeat is delayed within 5 seconds of lease expiration, the generator trips its fencing buffer and suspends generation before split-brain duplicate allocation can occur.
Decentralized Scale Invariant: Because each worker node possesses a globally unique (datacenter_id, worker_id) coordinate, zero inter-node consensus (e.g., Raft, Paxos, or ZooKeeper) is required on the hot request path. The generator achieves over throughput bounded solely by local CPU bit-shift performance.
Concrete Step-by-Step Request Walkthrough: Tracing an ID Generation & Lease Heartbeat
| Step # | Event / Action | Component State | Distributed Transition | Output / Response |
|---|---|---|---|---|
| 1 | Worker daemon container boots up in AWS us-east-1 (AZ-a) | Datacenter ID set to 1;scans DynamoDB WorkerNodeRegistryTable | Worker loops , executes conditional PutItem for slot WORKER#04 | Slot claimed with fencing_epoch = 104,lease_expiry_ts = Now + 30s |
| 2 | Client microservice issues gRPC GenerateIdRequest | NLB routes to healthy Worker 04 over HTTP/2 stream | Worker daemon validates lease validity buffer: current_time < lease_expiry - 5000ms | L4 connection active; worker enters atomic bit-shift loop |
| 3 | Worker reads monotonic clock; compares with | , sequence counter incremented: (seq + 1) & 4095 | Atomic in-memory assembly: bitwise OR of Timestamp, DC, Worker, and Sequence | Single 64-bit integer computed in (L1 cache) |
| 4 | Burst event: client requests 5,000 IDs in 1ms | Sequence counter hits limit: seq == 4095 | Sequence rollover detected; worker enters microsecond spin-wait loop tilNextMillis() | Sequence resets to 0 upon clock advance;zero duplicate IDs emitted |
| 5 | Background lease manager daemon runs at 10-second interval | Active worker health confirmed; NTP clock sync drift | Executes DynamoDB update: advances lease_expiry_ts by with optimistic condition | Worker lease refreshed; continuous generation unblocked |
4. API Interface Design & ID Structure
64-bit Bitfield Layout
Bits are numbered from 0 (the most significant bit) to 63 (the least significant bit).
| Field | Bits | Width | Range | Why this width |
|---|---|---|---|---|
| Sign | 0 | 1 bit | always 0 | Keeps the ID a positive signed 64-bit integer |
| Milliseconds since custom epoch | 1–41 | 41 bits | 0 to 2^41 − 1 | 2^41 ms ≈ 69 years of IDs from the custom epoch |
| DataCenter ID | 42–46 | 5 bits | 0–31 | 2^5 = 32 data centers |
| Worker ID | 47–51 | 5 bits | 0–31 | 2^5 = 32 workers per data center |
| Sequence Counter | 52–63 | 12 bits | 0–4095 | 2^12 = 4,096 IDs per worker per millisecond |
The five fields add up to 1 + 41 + 5 + 5 + 12 = 64 bits. Because the timestamp sits in the most significant bits after the sign bit, an ID made in a later millisecond is always larger than an ID made in an earlier millisecond, so the IDs sort by creation time. Within one millisecond, the data center, the worker and the sequence counter make each ID unique.
gRPC Service Protocol (id_generator.proto)
protobufsyntax = "proto3"; package hispeeddesign.idgen.v1; service IdGeneratorService { // Generates a single 64-bit unique ID rpc GenerateId (GenerateIdRequest) returns (GenerateIdResponse); // Generates a batch of unique IDs atomically rpc GenerateIdBatch (GenerateIdBatchRequest) returns (GenerateIdBatchResponse); } message GenerateIdRequest { string caller_service = 1; } message GenerateIdResponse { int64 id = 1; // Signed 64-bit representation string id_str = 2; // String representation for JS safe integer parsing int64 timestamp_ms = 3; // Milliseconds since Unix epoch extracted from ID int32 datacenter_id = 4; int32 worker_id = 5; } message GenerateIdBatchRequest { int32 count = 1; // Max batch size: 1,000 string caller_service = 2; } message GenerateIdBatchResponse { repeated int64 ids = 1; repeated string ids_str = 2; int64 epoch_offset_ms = 3; }
gRPC Status Codes & Error Contracts
| gRPC Status | Reason Code | When It Fires | Client Behavior |
|---|---|---|---|
OK | SUCCESS | ID or batch generated | Normal completion |
INVALID_ARGUMENT | BATCH_SIZE_EXCEEDED | count outside | Fix request; do not retry |
RESOURCE_EXHAUSTED | SEQUENCE_SATURATED | Worker spent spin-waiting on a saturated sequence and shed load | Retry on another worker (NLB re-balances) with jittered backoff |
FAILED_PRECONDITION | LEASE_FENCING_TRIP | Worker lease within of expiry or heartbeat failed | Retry immediately on a different worker; never retry on the same subchannel |
UNAVAILABLE | CLOCK_ROLLBACK_FATAL | Clock stepped backwards ; worker halting | Retry on another worker; page on-call if rate |
5. Data Model & Worker Node Coordination Schema
Worker nodes dynamically acquire a (datacenter_id, worker_id) pair on startup via DynamoDB leases to prevent static configuration drift or duplicate assignments during Kubernetes / ECS auto-scaling.
DynamoDB Schema: WorkerNodeRegistryTable
| Attribute Name | DynamoDB Type | Description |
|---|---|---|
PK (Partition Key) | STRING | DC#<datacenter_id> (e.g. DC#01) |
SK (Sort Key) | STRING | WORKER#<worker_id> (e.g. WORKER#04) |
instance_id | STRING | ECS Task ARN / EC2 Instance ID |
ip_address | STRING | Private IP of the worker daemon |
lease_expiry_ts | NUMBER | Unix epoch in milliseconds (TTL enabled) |
fencing_epoch | NUMBER | Monotonic 64-bit lease generation token for split-brain fencing |
version | NUMBER | Optimistic locking counter |
Worker Node Lease Acquisition Algorithm
- On container boot, the node discovers its AWS Region and sets
datacenter_id(e.g.,us-east-1= 1). - It loops through
worker_idfrom0to31, attempting a conditionalPutItemwith an incrementedfencing_epoch:json{ "TableName": "WorkerNodeRegistryTable", "Item": { "PK": {"S": "DC#01"}, "SK": {"S": "WORKER#04"}, "instance_id": {"S": "arn:aws:ecs:us-east-1:123456789:task/worker-04"}, "ip_address": {"S": "10.0.4.182"}, "lease_expiry_ts": {"N": "1767225630000"}, "fencing_epoch": {"N": "104"}, "version": {"N": "1"} }, "ConditionExpression": "attribute_not_exists(PK) OR lease_expiry_ts < :now_ms", "ExpressionAttributeValues": { ":now_ms": {"N": "1767225600000"} } } - Once claimed, a background daemon heartbeats every 10 seconds, extending
lease_expiry_tsby 30 seconds. - Lease Safety Margin: Worker nodes verify that
current_time < lease_expiry_ts - 5000ms. If network latency or a GC pause delays the heartbeat within 5 seconds of expiry, the worker proactively suspends ID generation before the lease expires, eliminating split-brain collision windows. - If the worker crashes, its lease expires, allowing a replacement container to reclaim the slot with an incremented
fencing_epoch.
Unlock Complete Architecture & Production Runbooks
You have explored the free architectural preview (~43%). Spend 1 Coin to unlock the remaining 6 production deep-dive sections for a full 24 hours.