Generating Trillions of Unique Order IDs Without Collisions
The article analyzes why simple auto‑increment or timestamp‑based IDs fail at massive scales, compares common distributed ID schemes, and presents an improved Snowflake‑plus‑segment hybrid solution with clock‑rollback protection, automatic machine/room allocation, and production‑grade safeguards for trillion‑level order processing.
Core evaluation criteria for a distributed ID
Global uniqueness – no duplicate IDs across machines, rooms, shards, or services.
Monotonic order – IDs increase over time to support range queries and sharding.
High performance – generate >100 k IDs per second without DB or Redis bottlenecks.
Security – IDs must not expose total order volume.
High availability – continue generating IDs during clock rollback, DB/Redis failures.
Capacity – support trillion‑level orders without bit overflow.
Distributed compatibility – seamless expansion across data‑centers without manual reconfiguration.
Survey of existing solutions
UUID/GUID
Implementation: 36‑character string composed of MAC address, random number and timestamp.
Advantages: No external dependencies.
Critical flaws: Unordered, large storage footprint, unsuitable as sharding key, terrible query performance at trillion scale.
Database auto‑increment primary key
Implementation: SINGLE_TABLE AUTO_INCREMENT.
Advantages: Simple, naturally ordered and unique.
Critical flaws: Single‑node throughput limit, duplicate IDs after sharding, costly migration, impossible for trillion‑scale.
Database segment (range allocation)
Implementation: Service fetches a contiguous block of IDs from the DB and caches it in memory; when exhausted, it fetches the next block.
Advantages: Reduces DB hits, ordered, globally unique, fault‑tolerant persistence.
Drawbacks: Exhaustion blocks requests, single‑point DB failure, manual step‑size adjustment on scaling.
Redis INCR
Implementation: Atomic INCR combined with timestamp and machine tag.
Advantages: Extremely fast, no duplicates under normal operation.
Drawbacks: Redis crash or persistence loss leads to duplicates; memory consumption grows with massive ID volume; multi‑shard deployment needs extra partition tags; cross‑region sync is complex.
Native Snowflake (Twitter)
Structure: 1‑bit sign + 41‑bit timestamp + 10‑bit machine ID + 12‑bit sequence (64‑bit long).
Advantages: Pure‑memory calculation, no third‑party services, ordered, 4 k IDs/s per node.
Fatal issues: Clock rollback causes duplicates; only 1024 machines supported; 12‑bit sequence caps at 4096 IDs per millisecond, which overflows under flash‑sale traffic.
Improved hybrid: segment + Snowflake
Normal traffic: use improved Snowflake in‑memory generation – no I/O, handles millions of QPS.
Clock rollback or sequence overflow: switch to DB‑pre‑allocated segment to guarantee uniqueness.
Machine IDs are assigned centrally in the DB, allowing automatic expansion beyond the native 1024‑node limit.
3‑bit room code separates up to eight data‑centers, enabling seamless multi‑region deployment.
Revised Snowflake bit layout for trillion‑scale
New 64‑bit composition: sign(1) | timestamp(42) | room(3) | worker(13) | sequence(5) Sign bit fixed to 0 ensures positive numbers.
42‑bit timestamp provides ~140 years of range.
3‑bit room code supports up to 8 data‑centers.
13‑bit worker ID allows 8192 instances.
5‑bit sequence yields 32 IDs per millisecond; combined with an in‑memory buffer it satisfies peak load.
Code illustration
Worker‑node table (MySQL)
CREATE TABLE id_worker_node (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '机器唯一ID',
room_code TINYINT NOT NULL COMMENT '机房编码 0-7',
host_name VARCHAR(64) NOT NULL COMMENT '服务主机名',
ip VARCHAR(32) NOT NULL COMMENT '服务IP',
create_time DATETIME DEFAULT NOW(),
update_time DATETIME DEFAULT NOW(),
UNIQUE uk_host_ip(host_name,ip)
) COMMENT '分布式ID机器节点分配表';On startup the service queries the table by IP/hostname; if no record exists it inserts one, automatically obtaining a unique workerId.
Improved Snowflake generator (Java)
import java.sql.Timestamp;
import java.util.concurrent.atomic.AtomicLong;
public class ImprovedSnowflakeIdGenerator {
// Bit allocation
private static final long TIMESTAMP_BIT = 42L;
private static final long ROOM_BIT = 3L;
private static final long WORKER_BIT = 13L;
private static final long SEQ_BIT = 5L;
private static final long MAX_ROOM_ID = (1L << ROOM_BIT) - 1;
private static final long MAX_WORKER_ID = (1L << WORKER_BIT) - 1;
private static final long MAX_SEQ = (1L << SEQ_BIT) - 1;
private static final long WORKER_SHIFT = SEQ_BIT;
private static final long ROOM_SHIFT = WORKER_SHIFT + WORKER_BIT;
private static final long TIMESTAMP_SHIFT = ROOM_SHIFT + ROOM_BIT;
// Base time: 2025‑01‑01
private static final long BASE_TIME = new Timestamp(1735689600000L).getTime();
private final long roomId;
private final long workerId;
private long lastTimestamp;
private final AtomicLong sequence = new AtomicLong(0);
public ImprovedSnowflakeIdGenerator(long roomId, long workerId) {
if (roomId < 0 || roomId > MAX_ROOM_ID) {
throw new IllegalArgumentException("机房编码超出范围");
}
if (workerId < 0 || workerId > MAX_WORKER_ID) {
throw new IllegalArgumentException("机器ID超出范围");
}
this.roomId = roomId;
this.workerId = workerId;
this.lastTimestamp = System.currentTimeMillis();
}
public synchronized long nextId() {
long currentTime = System.currentTimeMillis();
// Clock‑rollback detection
if (currentTime < lastTimestamp) {
throw new ClockBackwardException("系统时钟回拨,时间差:" + (lastTimestamp - currentTime));
}
if (currentTime == lastTimestamp) {
long seq = sequence.incrementAndGet() & MAX_SEQ;
if (seq == 0) {
currentTime = waitNextMilli(lastTimestamp);
}
} else {
sequence.set(0);
}
lastTimestamp = currentTime;
long timePart = (currentTime - BASE_TIME) << TIMESTAMP_SHIFT;
long roomPart = roomId << ROOM_SHIFT;
long workerPart = workerId << WORKER_SHIFT;
long seqPart = sequence.get();
return timePart | roomPart | workerPart | seqPart;
}
private long waitNextMilli(long lastTime) {
long now = System.currentTimeMillis();
while (now <= lastTime) {
now = System.currentTimeMillis();
}
return now;
}
}
class ClockBackwardException extends RuntimeException {
public ClockBackwardException(String msg) { super(msg); }
}Segment‑based fallback generator
@Component
public class SegmentIdGenerator {
@Autowired
private IdSegmentMapper segmentMapper;
private long currentStart;
private long currentEnd;
private AtomicLong currentId;
@PostConstruct
public void initSegment() {
loadNewSegment();
}
private synchronized void loadNewSegment() {
// Atomically increase step size (e.g., 10 000)
int row = segmentMapper.updateSegmentStep(10000);
SegmentPO po = segmentMapper.selectOne();
this.currentStart = po.getStartId();
this.currentEnd = po.getEndId();
this.currentId = new AtomicLong(currentStart);
}
public long nextSegmentId() {
long id = currentId.incrementAndGet();
if (id > currentEnd) {
loadNewSegment();
return nextSegmentId();
}
return id;
}
}Unified ID service (automatic switch)
@Service
public class DistributeIdService {
@Autowired
private SegmentIdGenerator segmentIdGenerator;
private ImprovedSnowflakeIdGenerator snowflakeGenerator;
@PostConstruct
public void init() {
long roomId = 0; // fetched from config or DB
long workerId = getLocalWorkerId(); // IP‑based lookup
snowflakeGenerator = new ImprovedSnowflakeIdGenerator(roomId, workerId);
}
public long getNextId() {
try {
return snowflakeGenerator.nextId(); // fast path
} catch (ClockBackwardException e) {
return segmentIdGenerator.nextSegmentId(); // fallback
}
}
public String getOrderNo() {
long id = getNextId();
return "ORD" + id; // optional business prefix
}
private long getLocalWorkerId() {
// Query id_worker_node by IP/hostname, auto‑insert if missing
return 1;
}
}Controller usage example
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private DistributeIdService idService;
@PostMapping("/create")
public Result createOrder() {
String orderNo = idService.getOrderNo(); // globally unique
// ... create order logic ...
return Result.success(orderNo);
}
}Production‑grade safeguards for trillion‑scale
Clock‑rollback protection
Record service start timestamp; each ID generation compares current time and triggers immediate fallback on backward movement.
Configure OS NTP with minimal adjustment frequency to avoid large jumps.
Persist segment ranges in DB; even if the clock jumps back, pre‑allocated ranges guarantee uniqueness.
Monitoring captures ClockBackwardException and raises alerts for operations teams.
Automatic machine‑ID allocation
On startup the service reads its IP/hostname, queries the id_worker_node table, inserts a row if absent, and receives a unique workerId. Cluster expansion requires no configuration changes.
Segment pre‑loading buffer
An asynchronous thread monitors remaining capacity; when the current segment falls below 20 % it pre‑fetches the next block, ensuring no DB‑induced latency during flash‑sale spikes.
Multi‑data‑center isolation
Room codes are fixed per region (e.g., East China 0, North China 1, South China 2). Different rooms generate disjoint ID spaces, preventing cross‑region collisions.
Rate limiting and monitoring
Global QPS of ID generation is monitored; exceeding thresholds triggers throttling.
Count of Snowflake‑to‑segment fallbacks is recorded to gauge clock‑rollback frequency.
Segment table is periodically inspected; step size is dynamically increased under high load to reduce DB round‑trips.
Sharding‑friendly ID
The monotonically increasing 64‑bit ID can serve directly as a sharding key; modulo or time‑range partitioning yields efficient pagination and range queries, far superior to unordered UUIDs.
Common pitfalls and their remedies
Clock rollback leads to duplicate primary keys
Root cause: NTP sync moves the clock backward, causing identical millisecond timestamps.
Solution: Detect rollback, switch to segment fallback, and raise alerts.
Cluster expansion beyond 1024 nodes
Root cause: Native Snowflake limits machine ID to 10 bits.
Solution: Expand to 13 bits, supporting up to 8192 instances.
Flash‑sale peak exceeding 4096 IDs per millisecond
Root cause: 12‑bit sequence caps at 4096.
Solution: Use in‑memory segment buffer; when sequence exhausts, automatically fall back to DB segment without blocking.
Redis crash causing ID duplication
Root cause: No persistent segment record.
Solution: Discard pure Redis approach; rely on DB‑persisted segment as fallback.
Cross‑region ID collisions
Root cause: Identical worker IDs across data‑centers.
Solution: Encode a 3‑bit room identifier, guaranteeing isolation.
Conclusion
Supporting trillion‑level orders cannot rely on a single ID scheme; each has fatal drawbacks. The proposed hybrid architecture uses an improved Snowflake generator for high‑performance primary path and a database‑persisted segment generator as a safety net, together with automatic machine/room allocation, clock‑rollback detection, and comprehensive monitoring. This design satisfies global uniqueness, ordering, performance, security, scalability, and high availability, making it suitable for e‑commerce, logistics, payment and any massive‑order business.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
