A Broken Snowflake ID Generator Caused Duplicate Order Numbers in Production
Recently, a serious incident occurred in our online system: order numbers/serial numbers were duplicated, affecting core business processes. The root cause was eventually located: a self-developed in-house package Snowflake ID generator had a problem.
Let's review the standard structure of the Snowflake algorithm, analyze where the problem occurred, and summarize some general design suggestions.
1. Standard Snowflake Algorithm
A standard Snowflake ID consists of a 64-bit long integer:
+----------------------------------------------------------------------------------------------------+
| 1 Bit | 41 Bits Timestamp | 5 Bits Data Center ID | 5 Bits Worker ID | 12 Bits Sequence Number |
+----------------------------------------------------------------------------------------------------+
- 1-bit sign bit: Always 0, ensuring a positive number is generated.
- 41-bit timestamp: Records the millisecond difference from a fixed start time, supporting approximately 69 years.
- 10-bit machine ID: Used to identify different nodes.
- 12-bit sequence number: Used when generating multiple IDs within the same millisecond, supporting up to 4096 IDs per millisecond.
Advantages:
- High-performance generation of unique IDs, ordered by time, suitable for distributed environments.
2. Our "Customized" Snowflake Algorithm: Where is the Problem?
The structure of the in-house package Snowflake algorithm we used is as follows (inferred from investigation):
+----------------------------------------------------------------------------------------------------+
| 31 Bits Timestamp Delta | 13 Bits Data Center ID | 4 Bits Worker ID | 8 Bits Business ID | 8 Bits Sequence Number |
+----------------------------------------------------------------------------------------------------+
It looks rich in fields, but there are serious problems:
1. The timestamp only retains 31 bits, supporting a maximum of 24.85 days!
- After shifting left by 33 bits, only 31 bits of the timestamp are used.
- It starts cycling after exceeding 2^31 milliseconds.
- Our custom start time was 2018, and by 2025 it had already cycled countless times.
2. BusinessId uses the last segment of the IP address
The IP used the last segment separated by dots, i.e., the '1' in 192.168.0.1, which is extremely prone to duplication.
3. WorkId and DataCenterId were not configured, both defaulting to 0
This is equivalent to all instances sharing the same node identifier, rendering uniqueness virtually non-existent.
Final result: Time cycling + IP conflict + sequence repetition, IDs collided completely.
3. Lessons Learned
It is not recommended to self-develop common components
Snowflake algorithm involves critical details like clock rollback, bit operations, and distributed coordination; mature components are more reliable.
Do not blindly trust in-house packages
No matter who wrote the code, you must examine the implementation logic and understand its uniqueness guarantee mechanism.
Set machine IDs reasonably
Relying on IP suffixes is too fragile. It is recommended to plan centrally and allocate Worker ID and Data Center ID uniformly.
Cover boundary scenarios in advance
Simulate extreme situations such as long-term operation, sequence number overflow, and clock rollback to ensure system robustness.
4. Recommended Practices
Use mature open-source implementations, such as Hutool, Baomidou, etc.:
// Hutool example
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
long id = snowflake.nextId();
// Baomidou example (supports auto-derivation from IP/MAC, or manual specification)
DefaultIdentifierGenerator generator = new DefaultIdentifierGenerator(1, 1); // workerId=1, dataCenterId=1
long id = generator.nextId("user");
For medium to large systems, DataCenterId is generally used to identify different computer rooms or AZs (Availability Zones).
The configuration strategy for WorkerId can evolve gradually based on system scale:
- Simple method: Manually specify via configuration files. This method is simple and suitable for development environments or single-machine deployments.
- Standard method: Concatenate the IP and port number (or process ID), hash them, and then modulo by the total number of WorkerIds. It has a certain degree of automation, does not depend on external systems, and is suitable for small to medium-scale deployments.
- Intermediate solution: Rely on a registry center (such as Eureka, Nacos) to assign a number during service registration, ensuring uniqueness in combination with the service ID.
- Advanced solution: Use Redis, Zookeeper, etc., for centralized coordination of WorkerId allocation and release, supporting dynamic scaling and avoiding conflicts.
As the system scale expands, it is recommended to gradually introduce more complex but more reliable mechanisms, avoiding over-engineering from the start.
5. Other Suggestions: Do Not Concatenate Business Identifiers into IDs
Sometimes, to ensure uniqueness, we try to concatenate business information (such as type prefixes, module numbers) into the ID. But this practice brings a series of problems:
- It causes the ID to be non-numeric, losing the original time-increment sorting characteristic, affecting database indexing efficiency;
- The ID length becomes irregular or too long, potentially increasing storage costs and affecting log display and user experience;
- If the meaning of the business field changes, it may also cause data compatibility issues.
A more reliable approach is to store business fields separately, using the ID only for unique identification and sorting.
6. Conclusion
Don't reinvent the wheel for the sake of it, especially for common components. Do not rely on luck.
Top 1 from juejin.cn, machine-translated. The original thread is authoritative.
Single responsibility principle: what the Snowflake algorithm generates is a meaningless primary key; business information encoding should go in a separate field.