List the key functional requirements for the system (Ask the AI for hints if stuck)...
List the key non-functional requirements (performance, scalability, reliability, etc.)...
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
Before capacity estimation, we should explain how we can generate a 64-bit unique id.
The breakdown is:
| Sign Bit | 1 bit | Always 0 | Ensures the generated ID is always a positive number. |
| Timestamp | 41 bits | ~69.7 years | Stores the time elapsed in milliseconds from a custom chosen starting point (epoch). |
| Node / Machine ID | 10 bits | 1,024 nodes | Uniquely identifies the physical server or process generating the ID to avoid collisions across machines. |
| Sequence Number | 12 bits | 4,096 IDs | A counter that increments for IDs generated within the exact same millisecond on the same machine. Resets to 0 every millisecond. |
With this estimation, we know that we can have up to 1024 machines.
For each machine, we can generate 4096 IDs per millisecond, the per node throughput per second is:
4096 * 1000 = 4 million IDs per second
The overall max throughput of the system is 4 million per second * 1024 machines = 4.1 billion IDs per second.
On the request side, assuming 100M DAU, and 5 requetss per day:
This is below the maximum throughput.
When the 12-bit counter overflows inside a millisecond, the node spins until the next millisecond.
Also, we can use batching to reduce the RPC count.
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
To retrieve the unique id:
POST v1/issue_unique_ids
To generate the worker id at bootstrap:
POST /v1/workers/lease
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
For this design, we should first understand how we generate the random IDs:
The breakdown is:
| Sign Bit | 1 bit | Always 0 | Ensures the generated ID is always a positive number. |
| Timestamp | 41 bits | ~69.7 years | Stores the time elapsed in milliseconds from a custom chosen starting point (epoch). |
| Node / Machine ID | 10 bits | 1,024 nodes | Uniquely identifies the physical server or process generating the ID to avoid collisions across machines. |
| Sequence Number | 12 bits | 4,096 IDs | A counter that increments for IDs generated within the exact same millisecond on the same machine. Resets to 0 every millisecond. |
This ensures:
For a request to generate a random unique ID, it first goes to load balancers, which distribute traffic evenly. Then the request gets processed by API gateway, which does rate limiting and authentication.
The request then gets processed by one of the id generator workers, which generates the ID using current timestamp, current machine id, and latest sequence number, and return the generated ID.
When an ID generator worker starts, it contacts a coordination service backed by etcd, a strongly consistent distributed key-value store using Raft consensus.
Failure handling:
Once initialized, workers generate Snowflake IDs locally without consulting etcd for each request.
How do we handle clock skew?
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
In this system, the only storage we need is the machine id we assign to each machine node. For this, we should use etcd.
For this storage, we need below features:
For all writes to the system, they go through the leader node, and need to be acknowledged by a majority of follower nodes.
For all reads to the system, they are handled by the leader node, to avoid stale reads.
In case of leader node crash, one of the follower nodes is guaranteed to have latest values, and can be promoted to the leader node.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
In this system, the only storage we need is the machine id we assign to each machine node. For this, we should use etcd.
For this storage, we need below features:
For all writes to the system, they go through the leader node, and need to be acknowledged by a majority of follower nodes.
For all reads to the system, they are handled by the leader node, to avoid stale reads.
In case of leader node crash, one of the follower nodes is guaranteed to have latest values, and can be promoted to the leader node.
For this design, we should first understand how we generate the random IDs:
The breakdown is:
| Sign Bit | 1 bit | Always 0 | Ensures the generated ID is always a positive number. |
| Timestamp | 41 bits | ~69.7 years | Stores the time elapsed in milliseconds from a custom chosen starting point (epoch). |
| Node / Machine ID | 10 bits | 1,024 nodes | Uniquely identifies the physical server or process generating the ID to avoid collisions across machines. |
| Sequence Number | 12 bits | 4,096 IDs | A counter that increments for IDs generated within the exact same millisecond on the same machine. Resets to 0 every millisecond. |
This ensures:
For a request to generate a random unique ID, it first goes to load balancers, which distribute traffic evenly. Then the request gets processed by API gateway, which does rate limiting and authentication.
The request then gets processed by one of the id generator workers, which generates the ID using current timestamp, current machine id, and latest sequence number, and return the generated ID.
When an ID generator worker starts, it contacts a coordination service backed by etcd, a strongly consistent distributed key-value store using Raft consensus.
Failure handling:
Once initialized, workers generate Snowflake IDs locally without consulting etcd for each request.
How do we handle clock skew?