Case Study: Design a Unique ID Generator in Distributed Systems

“Mỗi công dân có một số CMND/CCCD duy nhất. Mỗi sự kiện trong hệ thống phân tán cũng cần một ID duy nhất --- không trùng lặp, không xung đột, dù hàng triệu máy chủ cùng tạo ra cùng lúc. Đó chính là bài toán Unique ID Generator.”

Tags: system-design case-study unique-id snowflake distributed-systems alex-xu interview Student: Hieu Prerequisite: Tuan-01-Scale-From-Zero-To-Millions · Tuan-02-Back-of-the-envelope · Tuan-07-Database-Sharding-Replication Lien quan: Tuan-10-Consistent-Hashing · Tuan-16-Design-URL-Shortener · Tuan-20-Design-Key-Value-Store


1. Context & Why --- Tại sao cần Unique ID Generator?

1.1 Analogy: Số CMND/CCCD cho hệ thống

Hiếu, bạn hãy nghĩ thế này: mỗi công dân Việt Nam có một số CMND/CCCD duy nhất. Số này giúp nhà nước phân biệt 100 triệu người --- không ai trùng nhau. Trong hệ thống phân tán, mỗi event, mỗi record, mỗi transaction cũng cần một “số CMND” --- một globally unique identifier.

Thế giới thựcHệ thống phân tán
Số CMND/CCCDUnique ID
Cơ quan công an cấpID Generator service
Không trùng giữa các tỉnhKhông trùng giữa các datacenter
Có thể tra cứu theo thời gianSortable by time
Có định dạng (12 số)Có định dạng (64-bit integer)

1.2 Tại sao không dùng AUTO_INCREMENT trong DB?

Khi hệ thống chỉ có một database duy nhất, AUTO_INCREMENT là đủ. Nhưng khi bạn scale ra nhiều DB server, nhiều datacenter:

Vấn đềGiải thích
Single point of failureMột DB đi xuống = không tạo được ID
BottleneckMỗi request phải gọi DB để lấy ID --- latency cao
Không scale horizontally10K+ IDs/sec sẽ làm DB quá tải
Không work across datacenter2 DB ở 2 DC có thể tạo trùng ID
Không sortable by timeAUTO_INCREMENT chỉ tăng dần, không chứa thông tin thời gian

Aha Moment: AUTO_INCREMENT là anti-pattern trong distributed systems. Bạn cần một cơ chế tạo ID không cần coordination giữa các node.


2. Deep Dive --- Alex Xu 4-Step Framework

Overview --- Bốn bước của Alex Xu

BướcTên gọiThời gian (45 phút)
1Understand the Problem & Establish Design Scope~5 phút
2Propose High-Level Design~15 phút
3Design Deep Dive~20 phút
4Wrap Up~5 phút

Step 1 --- Understand the Problem & Establish Design Scope

2.1 Functional Requirements (Yêu cầu chức năng)

Câu hỏi cho InterviewerTrả lời / Giả định
ID phải unique globally?Có, không bao giờ trùng lặp
ID chỉ chứa số hay có thể chứa chữ cái?Chỉ chứa số (numerical values only)
ID có cần sortable theo thời gian?Có, ID tạo sau phải lớn hơn ID tạo trước (roughly)
Độ dài ID?64-bit integer (fit vào BIGINT của SQL)
Hệ thống cần tạo bao nhiêu ID/giây?10,000+ IDs/sec
Hệ thống có nhiều datacenter?Có, nhiều datacenter, nhiều machine

Tóm tắt Functional Requirements:

  1. Globally unique --- Không bao giờ trùng lặp, kể cả across datacenter
  2. 64-bit integer --- Fit vào BIGINT, dùng làm primary key
  3. Sortable by time --- ID tạo sau > ID tạo trước (roughly ordered)
  4. High throughput --- 10,000+ IDs/sec per system

2.2 Non-Functional Requirements (Yêu cầu phi chức năng)

Yêu cầuMục tiêuGiải thích
High Availability99.99% uptimeID generation không bao giờ được down --- mọi service đều phụ thuộc
Low Latency< 1ms per IDID generation nằm trên critical path của mọi write operation
ScalabilityLinear scale với số machineThêm machine = thêm throughput, không cần coordination
OrderingTime-sortableIDs tạo trong cùng millisecond trên cùng machine phải có thứ tự
No coordinationKhông cần central authorityMỗi node tự tạo ID độc lập

Step 2 --- Propose High-Level Design: 4 Approaches

Hiếu, có 4 cách tiếp cận chính để tạo Unique ID trong distributed systems. Mỗi cách có trade-off riêng.

Approach 1: Multi-Master Replication (DB Auto-Increment với bước nhảy)

Ý tưởng: Dùng AUTO_INCREMENT của DB, nhưng mỗi server tăng với bước nhảy khác nhau.

Server 1: 1, 3, 5, 7, 9, ...    (start=1, increment=2)
Server 2: 2, 4, 6, 8, 10, ...   (start=2, increment=2)

Tổng quát với N servers:
Server k: k, k+N, k+2N, k+3N, ...
Ưu điểmNhược điểm
Đơn giản, dùng sẵn feature của DBKhó scale: thêm/bớt server phải thay đổi increment của TẤT CẢ server
Không cần thêm service nàoID không sortable by time (Server 1 có thể tạo ID 7 SAU khi Server 2 tạo ID 8)
Tương đối reliableKhông work across datacenter (latency cao)
Throughput bị giới hạn bởi DB write speed

Khi nào dùng: Hệ thống nhỏ, ít server, không cần time-sorted IDs.

Approach 2: UUID (Universally Unique Identifier)

Ý tưởng: Mỗi machine tự tạo UUID 128-bit độc lập, không cần coordination.

UUID v4 Example: 550e8400-e29b-41d4-a716-446655440000
                 |--------|    |  |    |  |-----------|
                  random    version  variant   random
Ưu điểmNhược điểm
Đơn giản nhất, mọi ngôn ngữ đều hỗ trợ128-bit (không fit vào yêu cầu 64-bit)
Không cần coordination giữa các nodeKhông sortable by time
Không có single point of failureHiệu suất DB kém: random UUID làm B+ tree index fragmentation
Throughput vô hạn (mỗi node tự tạo)Dài (36 chars string) --- tốn storage

Collision probability của UUID v4:

Với UUIDs (1 quintillion):

Thực tế collision probability cực thấp, nhưng không phải zero và không đảm bảo unique bằng Snowflake.

Khi nào dùng: Khi không cần sortable, không giới hạn 64-bit, cần triển khai nhanh (vd: session ID, correlation ID, idempotency key).

Approach 3: Ticket Server (Flickr Approach)

Ý tưởng: Dùng một (hoặc vài) server trung tâm với AUTO_INCREMENT làm “máy bán vé”. Mỗi service gọi đến Ticket Server để lấy ID tiếp theo.

          +------------------+
          |  Ticket Server   |
          |  AUTO_INCREMENT  |
          |  current_id: 1000|
          +--------+---------+
                   |
      +-----+-----+-----+
      |     |     |     |
   Svc A  Svc B  Svc C  Svc D
   id=1001 id=1002 id=1003 id=1004
Ưu điểmNhược điểm
Đơn giản, dễ implementSingle Point of Failure (SPOF)
ID là số, 64-bit, tăng dầnBottleneck: mỗi request phải gọi Ticket Server
Sortable by time (roughly)Latency tăng khi cross-datacenter
Flickr dùng thành công nhiều nămCần replicate Ticket Server để HA --- thêm complexity

Flickr’s solution: Dùng 2 Ticket Servers với increment = 2, giống Multi-Master nhưng chỉ dành riêng cho ID generation.

Khi nào dùng: Hệ thống vừa, chấp nhận single point of coordination, cần ID tăng dần đơn giản.

Ý tưởng: Mỗi ID là một 64-bit integer được compose từ nhiều thành phần: timestamp, datacenter ID, machine ID, và sequence number. Mỗi machine tự tạo ID độc lập.

+---+-------------------------------------------+---------+---------+----------------+
| 0 |          41-bit Timestamp                  | 5-bit   | 5-bit   | 12-bit         |
|   |          (milliseconds since epoch)        |Datacenter| Machine | Sequence       |
+---+-------------------------------------------+---------+---------+----------------+
 1 bit                41 bits                     5 bits    5 bits     12 bits = 64 bits
Ưu điểmNhược điểm
64-bit integer --- fit yêu cầuPhụ thuộc vào đồng hồ máy (clock)
Sortable by time (timestamp trong ID)Clock skew giữa các machine gây ra out-of-order IDs
Không cần coordinationCần quản lý datacenter ID và machine ID
Throughput cực cao: 4,096 IDs/ms/machineTime-sorted chỉ trong phạm vi “roughly” --- không tuyệt đối
Dễ extract timestamp từ IDEpoch overflow sau 69 năm

Aha Moment: Snowflake là cách tiếp cận được sử dụng nhiều nhất trong thực tế. Twitter, Discord, Instagram, Baidu đều dùng biến thể của Snowflake.


Bảng so sánh toàn bộ Approaches

Tiêu chíMulti-MasterUUIDTicket ServerSnowflakeULIDKSUIDMongoDB ObjectId
Bit size64128646412816096
Globally uniqueCó (với cấu hình đúng)Có (cực cao)
Sortable by timeKhôngKhông (v4)
No coordinationKhôngKhông
ThroughputThấp (DB bound)Vô hạnThấp (bottleneck)4,096/ms/machineVô hạnVô hạn16M/sec/process
ComplexityThấpCực thấpTrung bìnhTrung bìnhThấpThấpThấp
DB index friendlyKhông (random)
Dùng trong thực tếMySQL replicationSession ID, correlation IDFlickrTwitter, Discord, InstagramShopify, CockroachDBSegmentMongoDB
Fit 64-bit?KhôngKhôngKhôngKhông

Các biến thể khác (Ngoài Alex Xu)

ULID (Universally Unique Lexicographically Sortable Identifier):

  • 128-bit: 48-bit timestamp (millisecond) + 80-bit randomness
  • Encoded thành 26-char Crockford Base32
  • Sortable by time, không cần coordination
  • Sử dụng: Shopify, CockroachDB

KSUID (K-Sortable Unique Identifier):

  • 160-bit: 32-bit timestamp (second) + 128-bit random payload
  • Encoded thành 27-char Base62
  • Epoch: 2014-05-13 --- overflow sau ~136 năm
  • Sử dụng: Segment

MongoDB ObjectId:

  • 96-bit (12 bytes): 32-bit timestamp (second) + 40-bit random + 24-bit counter
  • Sortable by time (second resolution)
  • Counter cho phép 16,777,216 unique IDs per second per process

Step 3 --- Design Deep Dive: Snowflake ID Generator

3.1 Bit Layout Chi Tiết

 0 | 00000000 00000000 00000000 00000000 00000000 0 | 00000 | 00000 | 000000000000
 ^ |                    41 bits                      | 5 bits| 5 bits|   12 bits
 |                       |                              |       |         |
Sign bit             Timestamp                     Datacenter Machine  Sequence
(luôn = 0)     (ms since custom epoch)               ID       ID      Number
Thành phầnSố bitRangeGiải thích
Sign bit1Luôn = 0Đảm bảo ID luôn dương (positive)
Timestamp410 --- msMillisecond kể từ custom epoch
Datacenter ID50 --- 31Tối đa 32 datacenter
Machine ID50 --- 31Tối đa 32 machine per datacenter
Sequence120 --- 4095Counter reset mỗi millisecond

3.2 Cách tạo ID (Thuật toán)

Mỗi khi một service request ID:

1. Lấy current_timestamp (ms since epoch)
2. Nếu current_timestamp == last_timestamp:
     sequence = (sequence + 1) AND 0xFFF   // 12-bit mask
     Nếu sequence == 0:  // overflow! đã tạo 4096 IDs trong 1ms
       Wait cho next millisecond
3. Nếu current_timestamp > last_timestamp:
     sequence = 0   // reset counter
4. Nếu current_timestamp < last_timestamp:
     CLOCK SKEW DETECTED! -> Handle error
5. last_timestamp = current_timestamp
6. Compose ID:
     id = (timestamp << 22) | (datacenter_id << 17) | (machine_id << 12) | sequence
7. Return id

3.3 Epoch Selection (Chọn epoch)

Snowflake KHÔNG dùng Unix epoch (1970-01-01). Tại sao?

Nếu dùng Unix epoch (1970-01-01):

Đã quá gần! Nên chọn custom epoch = ngày hệ thống go-live:

Custom EpochOverflow DateCòn lại (từ 2026)
2010-01-01 (Twitter)~2079~53 năm
2015-01-01 (Discord)~2084~58 năm
2020-01-01~2089~63 năm
2025-01-01 (recommended cho hệ thống mới)~2094~68 năm
Unix epoch 1970-01-01~2039~13 năm

Aha Moment: Chọn custom epoch gần với ngày deploy để tối ưu hóa thời gian sống của hệ thống. Discord chọn 2015-01-01 --- ngày đầu tiên của Discord.

3.4 Clock Sync và NTP (Network Time Protocol)

Vấn đề cốt lõi: Snowflake phụ thuộc vào timestamp --- nhưng đồng hồ của các machine KHÔNG BAO GIỜ đồng bộ hoàn hảo.

NTP (Network Time Protocol) đồng bộ đồng hồ giữa các máy:

  • Stratum 0: Atomic clock, GPS receiver (độ chính xác ~nanosecond)
  • Stratum 1: Server kết nối trực tiếp với Stratum 0 (~microsecond)
  • Stratum 2: Server sync với Stratum 1 (~millisecond)
  • Stratum 3+: Mỗi tầng thêm ~1-10ms drift
  GPS Satellite / Atomic Clock
         |
    [Stratum 0]
         |
    [Stratum 1]  <-- Google, Amazon NTP servers
         |
    [Stratum 2]  <-- Datacenter NTP servers
         |
    [Stratum 3]  <-- Application servers (các machine của em)

Clock drift thực tế:

  • Server thường: drift ~100ms/ngày nếu không có NTP
  • Với NTP sync mỗi 30 giây: drift < 1ms
  • Google TrueTime (Spanner): uncertainty < 7ms (dùng GPS + atomic clock)

3.5 Clock Skew Handling (Xử lý lệch đồng hồ)

Scenario: Machine A có đồng hồ chậm hơn Machine B 5ms.

  • Machine A tạo ID với timestamp = 1000
  • Machine B tạo ID với timestamp = 1005
  • Thực tế hai ID được tạo cùng lúc --- nhưng ID của B “có vẻ” tạo sau

Các cách xử lý:

StrategyMô tảTrade-off
WaitNếu current_time < last_time, đợi cho đến khi current_time >= last_timeĐơn giản nhưng có thể block nếu clock nhảy lùi nhiều
RejectNếu clock skew > threshold (vd: 5ms), reject request và raise alarmAn toàn nhưng có thể mất ID trong thời gian ngắn
Logical clockDùng Lamport timestamp hoặc Hybrid Logical Clock (HLC) để đảm bảo causalityPhức tạp hơn nhưng đảm bảo ordering
Tolerance windowChấp nhận ID out-of-order trong một window nhỏ (vd: 10ms)Thực tế nhất, hầu hết các hệ thống dùng cách này

Cách Twitter Snowflake xử lý: Nếu current_timestamp < last_timestamp, throw exception và log warning. Operations team sẽ điều tra NTP issue.

Aha Moment: Clock skew là unavoidable trong distributed systems. Snowflake không đảm bảo strict global ordering --- chỉ “roughly sorted”. Nếu cần strict ordering, phải dùng single-machine approach (Ticket Server) hoặc Hybrid Logical Clock.

3.6 Sequence Overflow Scenarios

Khi một machine tạo > 4,096 IDs trong 1 millisecond:

Millisecond T=1000:
  ID #0:    ...1000...0000 00000000
  ID #1:    ...1000...0000 00000001
  ...
  ID #4095: ...1000...1111 11111111  <- MAX sequence
  ID #4096: OVERFLOW! sequence quay về 0
            -> Phải WAIT cho T=1001

Handling strategies:

StrategyMô tảKhi nào dùng
Spin-waitBusy-loop cho đến next millisecondDefault trong Snowflake, OK cho burst ngắn
SleepThread.sleep(1)Giảm CPU nhưng có thể mất chính xác
Pre-generate poolTạo trước một pool IDs, trả về từ poolHigh-throughput services, giảm latency spike
Borrow from futureTạo ID với timestamp = T+1 và sequence = 0Nguy hiểm --- có thể gây duplicate nếu clock correction xảy ra

3.7 Ordering Guarantees

Phạm viĐảm bảo?Giải thích
Cùng machine, cùng millisecondCó, strict orderingSequence number tăng dần
Cùng machine, khác millisecondCó, strict orderingTimestamp lớn hơn = ID lớn hơn
Khác machine, cùng datacenterKhông đảm bảoClock skew có thể làm out-of-order
Khác datacenterKhông đảm bảoClock skew + network latency

Chú ý: Điều này có nghĩa là 2 ID từ 2 machine khác nhau có thể có timestamp giống nhau nhưng thứ tự so sánh phụ thuộc vào datacenter_id và machine_id, không phải thời gian thực.


3. Back-of-the-Envelope Estimation

3.1 IDs per Millisecond per Machine

3.2 IDs per Second per Machine

3.3 Total Capacity với N Machines

Với max configuration (32 datacenter x 32 machine = 1,024 machines):

3.4 Yêu cầu 10K IDs/sec cần bao nhiêu machine?

Chỉ cần 1 machine là thừa sức cho 10K IDs/sec. Thực tế, ta deploy nhiều machine để High Availability, không phải vì throughput.

3.5 Timestamp Overflow

Với custom epoch = 2025-01-01:

3.6 Tổng số ID có thể tạo trong 69 năm

3.7 Storage Estimation

Mỗi ID là 8 bytes (64-bit):

So sánh với UUID (16 bytes) hoặc UUID string (36 bytes):

FormatSize per ID1 Billion IDsDB Index Impact
Snowflake (64-bit)8 bytes8 GBSequential --- B+ tree insert O(1) amortized
UUID binary (128-bit)16 bytes16 GBRandom --- B+ tree split O(log n)
UUID string (36 char)36 bytes36 GBRandom + larger index pages

4. Security First --- Bảo mật cho ID Generator

4.1 ID Predictability Attacks (Tấn công đoán trước ID)

Threat: Nếu attacker biết ID structure, họ có thể:

AttackMô tảImpact
Enumeration attackĐoán ID của record khác bằng cách tăng/giảm IDTruy cập dữ liệu trái phép
Timing attackExtract timestamp từ ID để biết thời gian tạo recordInformation leakage
Volume estimationSo sánh 2 IDs để ước lượng số record giữa chúngBusiness intelligence leak
Infrastructure mappingExtract datacenter_id và machine_idBiết số lượng server, datacenter topology

Ví dụ Enumeration:

Attacker thấy order_id = 7100424390656 00001 00001 000000000001
                                        ^DC    ^Machine
Họ biết: datacenter 1, machine 1
Họ tăng sequence: 000000000002, 000000000003, ...
-> Có thể duyệt qua toàn bộ orders của machine đó

4.2 Information Leakage từ Snowflake ID

Thành phầnThông tin bị lộMức độ nghiêm trọng
Timestamp (41 bit)Thời điểm chính xác (ms) tạo recordTrung bình --- attacker biết khi nào user đăng ký, đặt hàng
Datacenter ID (5 bit)Topology của hệ thốngThấp --- nhưng hữu ích cho targeted attack
Machine ID (5 bit)Số lượng và vị trí serverThấp --- có thể dùng cho DDoS targeting
Sequence (12 bit)Traffic volume tại thời điểm đóTrung bình --- business intelligence

4.3 Mitigation Strategies (Chiến lược giảm thiểu)

StrategyMô tảTrade-off
Không expose internal IDDùng public-facing ID khác (UUID hoặc encrypted ID) cho API, giữ Snowflake ID internalThêm một lớp mapping, tăng storage
Encrypt ID trước khi exposeDùng Format-Preserving Encryption (FPE) để encrypt 64-bit ID thành 64-bit ciphertextVẫn giữ được size, nhưng mất sortability
Shuffle bitsXáo trộn thứ tự các bit để timestamp không còn ở vị trí cố địnhMất sortability, cần lookup table
Add random bitsThay datacenter+machine (10 bit) bằng random bitsMất khả năng trace về source, nhưng an toàn hơn
Hash-based public IDpublic_id = HMAC-SHA256(secret_key, snowflake_id)[:8]Một chiều, không reverse được, nhưng cần DB lookup
Rate limit APIGiới hạn số request per user/IP để chặn enumerationKhông giải quyết root cause nhưng giảm impact

Recommended approach cho hầu hết hệ thống:

Internal: Snowflake ID (dùng cho DB, inter-service communication)
External: Encrypted Snowflake ID hoặc UUID mapping (dùng cho public API)

Database:
  +------------------+------------------+
  | snowflake_id (PK)| public_id (UNIQUE)|
  | 71004243906560001 | a8f3k2m9x1       |
  +------------------+------------------+

4.4 OWASP Considerations

OWASP RiskÁp dụng cho ID GeneratorGiải pháp
A01 Broken Access ControlAttacker dùng predicted ID để truy cập resource của người khácAuthorization check TRÊN MỌI REQUEST, không chỉ dựa vào ID
A04 Insecure DesignExpose Snowflake ID trên public APIDùng public ID mapping hoặc FPE encryption
A09 Security LoggingKhông log ai request ID nàoLog mọi ID generation request với caller identity

Aha Moment: ID Generator bản thân không có lỗi bảo mật --- lỗi là ở cách bạn expose ID ra ngoài. Luôn có một lớp abstraction giữa internal ID và public-facing ID.


5. DevOps --- Vận hành ID Generator

5.1 Monitoring Metrics

MetricMô tảAlert Threshold
id_generation_rateSố ID tạo per second per machineSpike bất thường > 2x baseline
id_generation_latency_p99Latency để tạo 1 ID> 1ms (thường < 0.01ms)
sequence_overflow_countSố lần sequence overflow (4096/ms)> 0 per minute = cần thêm machine hoặc review traffic
clock_skew_detectedSố lần current_time < last_time> 0 = NTP issue, cần điều tra ngay
ntp_offset_msĐộ lệch giữa local clock và NTP server> 10ms = critical alert
ntp_sync_failuresSố lần NTP sync thất bại> 3 consecutive failures = critical
machine_id_conflicts2 machine có cùng datacenter_id + machine_id> 0 = IMMEDIATE action (duplicate IDs!)

5.2 Clock Drift Detection

# prometheus-alerts.yml
groups:
  - name: id_generator_alerts
    rules:
      # Clock skew detected
      - alert: ClockSkewDetected
        expr: increase(clock_skew_detected_total[5m]) > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Clock skew detected on {{ $labels.instance }}"
          description: "Machine {{ $labels.machine_id }} detected backward clock movement. IDs may be out of order."
 
      # NTP offset too high
      - alert: NTPOffsetHigh
        expr: abs(ntp_offset_milliseconds) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "NTP offset > 10ms on {{ $labels.instance }}: {{ $value }}ms"
 
      # NTP sync failure
      - alert: NTPSyncFailure
        expr: increase(ntp_sync_failures_total[15m]) > 3
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "NTP sync failing on {{ $labels.instance }}"
          description: "NTP has failed to sync 3+ times in 15 minutes. Clock drift will increase."
 
      # Sequence overflow
      - alert: SequenceOverflow
        expr: rate(sequence_overflow_total[1m]) > 0
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Sequence overflow on machine {{ $labels.machine_id }}"
          description: "Machine is generating > 4096 IDs/ms. Consider adding more machines."
 
      # ID generation latency spike
      - alert: IDGenerationLatencyHigh
        expr: histogram_quantile(0.99, rate(id_generation_duration_seconds_bucket[5m])) > 0.001
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P99 ID generation latency > 1ms on {{ $labels.instance }}"

5.3 NTP Monitoring Best Practices

PracticeMô tả
Dùng nhiều NTP sourceCấu hình ít nhất 3 NTP server (vd: time.google.com, time.aws.com, pool.ntp.org)
Monitor NTP offset liên tụcExport ntp_offset_ms metric qua node_exporter hoặc custom exporter
Set NTP sync intervalDefault 64-1024 giây, có thể giảm xuống 16-32 giây cho ID generator machine
Dùng chrony thay ntpdchrony handle drift tốt hơn, khởi động nhanh hơn, chính xác hơn
Avoid NTP step (nhảy đồng hồ)Cấu hình NTP chỉ slew (điều chỉnh từ từ) thay vì step (nhảy đột ngột)
Hardware timestampingDùng NIC có hỗ trợ hardware timestamping để giảm jitter

5.4 Machine ID Management

StrategyMô tảKhi nào dùng
Static configCấu hình datacenter_idmachine_id trong config file hoặc env varHệ thống nhỏ, ít thay đổi
ZooKeeper/etcdMỗi machine đăng ký với ZooKeeper để nhận machine_id uniqueHệ thống lớn, cần auto-assignment
Cloud metadataDùng cloud instance metadata (AWS instance ID, GCP zone) để tính machine_idCloud-native systems
MAC address hashHash MAC address thành 10-bit (datacenter + machine)Đơn giản nhưng có thể conflict

Pitfall: Khi một machine restart hoặc bị thay thế, machine_id PHẢI được bảo toàn hoặc de-register trước khi assign cho machine mới. Nếu 2 machine có cùng ID chạy đồng thời --- duplicate IDs sẽ xảy ra.

5.5 Grafana Dashboard Panels

PanelQueryVisualization
ID Generation Ratesum(rate(ids_generated_total[1m])) by (machine_id)Time series, stacked
Latency P50/P95/P99histogram_quantile(0.99, rate(id_gen_duration_bucket[5m]))Time series, multi-line
Sequence Overflow Eventsincrease(sequence_overflow_total[1h])Stat panel (should be 0)
NTP Offset per Machinentp_offset_millisecondsTime series, threshold line at 10ms
Active Machinescount(up{job="id-generator"} == 1)Gauge
Clock Skew Events (24h)increase(clock_skew_detected_total[24h])Stat panel (should be 0)

6. System Design Diagrams

6.1 Snowflake Bit Layout

block-beta
    columns 4
    block:header:4
        A["Snowflake ID: 64-bit Layout"]
    end
    block:bits:4
        B["0"]
        C["Timestamp (41 bits)"]
        D["DC(5) + Machine(5)"]
        E["Sequence (12 bits)"]
    end
    block:detail:4
        F["Sign<br/>(always 0)"]
        G["Milliseconds since<br/>custom epoch<br/>(~69.7 years)"]
        H["Datacenter: 0-31<br/>Machine: 0-31<br/>(1,024 nodes max)"]
        I["0-4095 per ms<br/>(4.096M IDs/sec<br/>per machine)"]
    end

    style B fill:#6c757d,color:#fff
    style C fill:#0d6efd,color:#fff
    style D fill:#198754,color:#fff
    style E fill:#dc3545,color:#fff
    style F fill:#6c757d,color:#fff
    style G fill:#0d6efd,color:#fff
    style H fill:#198754,color:#fff
    style I fill:#dc3545,color:#fff

6.2 Multi-Datacenter ID Generation Architecture

graph TB
    subgraph "Client Services"
        SvcA["Order Service"]
        SvcB["User Service"]
        SvcC["Payment Service"]
        SvcD["Inventory Service"]
    end

    subgraph "Load Balancer Layer"
        LB["Load Balancer<br/>(Route to nearest DC)"]
    end

    subgraph "Datacenter 1 (US-East) — dc_id=00001"
        direction TB
        subgraph "ID Generator Cluster DC1"
            M1_1["Machine 1<br/>machine_id=00001<br/>Snowflake Generator"]
            M1_2["Machine 2<br/>machine_id=00010<br/>Snowflake Generator"]
            M1_3["Machine 3<br/>machine_id=00011<br/>Snowflake Generator"]
        end
        NTP1["NTP Server DC1<br/>(Stratum 2)<br/>Sync: time.google.com"]
        ZK1["ZooKeeper Node 1<br/>(Machine ID Registry)"]
    end

    subgraph "Datacenter 2 (EU-West) — dc_id=00010"
        direction TB
        subgraph "ID Generator Cluster DC2"
            M2_1["Machine 1<br/>machine_id=00001<br/>Snowflake Generator"]
            M2_2["Machine 2<br/>machine_id=00010<br/>Snowflake Generator"]
        end
        NTP2["NTP Server DC2<br/>(Stratum 2)<br/>Sync: time.aws.com"]
        ZK2["ZooKeeper Node 2<br/>(Machine ID Registry)"]
    end

    subgraph "Datacenter 3 (AP-Southeast) — dc_id=00011"
        direction TB
        subgraph "ID Generator Cluster DC3"
            M3_1["Machine 1<br/>machine_id=00001<br/>Snowflake Generator"]
            M3_2["Machine 2<br/>machine_id=00010<br/>Snowflake Generator"]
        end
        NTP3["NTP Server DC3<br/>(Stratum 2)<br/>Sync: pool.ntp.org"]
        ZK3["ZooKeeper Node 3<br/>(Machine ID Registry)"]
    end

    subgraph "Monitoring"
        Prometheus["Prometheus<br/>(Metrics Collection)"]
        Grafana["Grafana<br/>(Dashboard)"]
        AlertManager["AlertManager<br/>→ Slack / PagerDuty"]
    end

    SvcA & SvcB & SvcC & SvcD --> LB
    LB --> M1_1 & M1_2 & M1_3
    LB --> M2_1 & M2_2
    LB --> M3_1 & M3_2

    NTP1 -.->|sync| M1_1 & M1_2 & M1_3
    NTP2 -.->|sync| M2_1 & M2_2
    NTP3 -.->|sync| M3_1 & M3_2

    ZK1 -.->|assign machine_id| M1_1 & M1_2 & M1_3
    ZK2 -.->|assign machine_id| M2_1 & M2_2
    ZK3 -.->|assign machine_id| M3_1 & M3_2

    ZK1 <-->|replicate| ZK2 <-->|replicate| ZK3

    M1_1 & M1_2 & M1_3 & M2_1 & M2_2 & M3_1 & M3_2 --> Prometheus
    Prometheus --> Grafana
    Prometheus --> AlertManager

    style M1_1 fill:#0d6efd,color:#fff
    style M1_2 fill:#0d6efd,color:#fff
    style M1_3 fill:#0d6efd,color:#fff
    style M2_1 fill:#198754,color:#fff
    style M2_2 fill:#198754,color:#fff
    style M3_1 fill:#dc3545,color:#fff
    style M3_2 fill:#dc3545,color:#fff
    style NTP1 fill:#f9a825,stroke:#333
    style NTP2 fill:#f9a825,stroke:#333
    style NTP3 fill:#f9a825,stroke:#333
    style ZK1 fill:#6f42c1,color:#fff
    style ZK2 fill:#6f42c1,color:#fff
    style ZK3 fill:#6f42c1,color:#fff
    style Prometheus fill:#e65100,color:#fff
    style Grafana fill:#388e3c,color:#fff

6.3 Clock Sync Flow

sequenceDiagram
    participant AC as Atomic Clock<br/>(Stratum 0)
    participant S1 as NTP Stratum 1<br/>(time.google.com)
    participant S2 as NTP Stratum 2<br/>(DC NTP Server)
    participant IDGen as ID Generator<br/>(Machine)
    participant Mon as Monitoring<br/>(Prometheus)

    Note over AC,S1: Stratum 0 → 1: ~microsecond accuracy
    AC->>S1: Time reference signal

    Note over S1,S2: Stratum 1 → 2: ~millisecond accuracy
    S2->>S1: NTP Request (T1)
    S1-->>S2: NTP Response (T2, T3)
    S2->>S2: Calculate offset<br/>offset = ((T2-T1) + (T3-T4)) / 2

    Note over S2,IDGen: Stratum 2 → 3: ~1-10ms accuracy
    loop Every 16-32 seconds
        IDGen->>S2: NTP Request
        S2-->>IDGen: NTP Response
        IDGen->>IDGen: Adjust clock (slew, not step)
    end

    IDGen->>Mon: Export ntp_offset_ms metric

    alt NTP offset > 10ms
        Mon->>Mon: Fire NTPOffsetHigh alert
        Mon-->>IDGen: Alert → Ops team investigates
    end

    alt Clock goes backward
        IDGen->>IDGen: DETECT: current_time < last_time
        IDGen->>Mon: Increment clock_skew_detected counter
        IDGen->>IDGen: WAIT until current_time >= last_time
        Mon->>Mon: Fire ClockSkewDetected alert
    end

6.4 ID Generation Request Flow

sequenceDiagram
    participant Svc as Client Service
    participant LB as Load Balancer
    participant IDGen as ID Generator<br/>(Machine #5, DC #2)
    participant Clock as System Clock

    Svc->>LB: Request new ID
    LB->>IDGen: Forward to nearest generator

    IDGen->>Clock: Get current_timestamp_ms
    Clock-->>IDGen: timestamp = 1742300000123

    alt timestamp == last_timestamp
        IDGen->>IDGen: sequence++
        alt sequence > 4095 (overflow)
            IDGen->>IDGen: WAIT for next millisecond
            IDGen->>Clock: Get current_timestamp_ms
            Clock-->>IDGen: timestamp = 1742300000124
            IDGen->>IDGen: sequence = 0
        end
    else timestamp > last_timestamp
        IDGen->>IDGen: sequence = 0
    else timestamp < last_timestamp (CLOCK SKEW!)
        IDGen->>IDGen: Log warning + wait
    end

    IDGen->>IDGen: Compose ID:<br/>= (timestamp << 22)<br/>| (dc_id << 17)<br/>| (machine_id << 12)<br/>| sequence

    IDGen-->>LB: Return ID = 7300811265438109697
    LB-->>Svc: ID = 7300811265438109697

    Note over Svc: Extract info from ID:<br/>timestamp: 2025-03-18T12:00:00.123Z<br/>datacenter: 2<br/>machine: 5<br/>sequence: 1

7. Aha Moments & Common Pitfalls

Aha Moments --- Đúc kết

#1 --- Clock skew là điều không thể tránh khỏi trong distributed systems: Không có cách nào đồng bộ hoàn hảo đồng hồ giữa các machine. Snowflake chấp nhận “roughly sorted” thay vì “strictly sorted”. Nếu cần strict global ordering --- dùng Lamport Clock, Hybrid Logical Clock, hoặc Google TrueTime.

#2 --- UUID vs Snowflake không phải chọn 1 bỏ 1: UUID phù hợp cho idempotency key, session ID, correlation ID (không cần sort, không cần compact). Snowflake phù hợp cho primary key, event ID, message ID (cần sort, cần fit 64-bit). Nhiều hệ thống dùng cả hai cho các mục đích khác nhau.

#3 --- DB index performance với random vs sequential IDs: Đây là lý do lớn nhất để không dùng UUID làm primary key trong relational DB. B+ tree index yêu sequential insert --- mỗi record mới append vào cuối leaf node. Random UUID gây page split liên tục, làm giảm write throughput 2-5x và tăng disk I/O.

#4 --- 1 machine Snowflake tạo đủ 4.1M IDs/sec: Throughput không phải lý do deploy nhiều machine. Lý do chính là High Availability --- nếu 1 machine chết, các machine khác vẫn tạo ID bình thường.

#5 --- Custom epoch là quyết định một lần, không thể thay đổi: Chọn epoch sai (quá xa trong quá khứ) = lãng phí bit timestamp. Chọn epoch trong tương lai = ID âm. Chọn epoch = ngày go-live là tốt nhất.

#6 --- Snowflake ID có thể bị reverse-engineer: Bất kỳ ai có ID cũng có thể extract timestamp, datacenter, machine, và sequence. Đừng bao giờ expose Snowflake ID trực tiếp trên public API nếu bạn quan tâm security.

Common Pitfalls --- Sai lầm thường gặp

Pitfall 1: Không xử lý clock skew

Sai: “Đồng hồ máy luôn chính xác, không cần lo.” Đúng: Clock skew LUÔN xảy ra. NTP step, leap second, VM migration, hardware issue --- tất cả đều có thể làm đồng hồ nhảy lùi. Phải có logic detect và handle.

Pitfall 2: Dùng UUID làm primary key rồi thắc mắc sao DB chậm

Sai: “UUID unique, dùng làm PK luôn cho tiện.” Đúng: Random UUID gây B+ tree fragmentation. Nếu phải dùng UUID, dùng UUID v7 (time-ordered) hoặc ULID. Hoặc dùng Snowflake ID làm PK và UUID làm public-facing ID.

Pitfall 3: Hard-code machine_id

Sai: machine_id = 1 trong config file, deploy lên 5 server. Đúng: 5 server cùng machine_id = 1 = duplicate IDs. Phải có cơ chế assign unique machine_id --- ZooKeeper, etcd, hoặc cloud metadata.

Pitfall 4: Không monitor sequence overflow

Sai: “4096 IDs/ms là đủ rồi, không cần lo.” Đúng: Traffic spike, retry storm, hoặc bug có thể gây burst > 4096/ms. Sequence overflow = ID generator phải wait = latency spike. Phải monitor và alert.

Pitfall 5: Quên tính epoch overflow date

Sai: Dùng Unix epoch 1970 cho hệ thống deploy 2025. Đúng: . Chỉ còn 13 năm! Chọn custom epoch = 2025 để có 69 năm runway.

Pitfall 6: Nghĩ Snowflake đảm bảo global ordering

Sai: “ID lớn hơn = tạo sau, luôn luôn.” Đúng: Chỉ đúng trên cùng 1 machine. Giữa 2 machine khác nhau, clock skew có thể làm ID nhỏ hơn được tạo sau ID lớn hơn. Snowflake chỉ đảm bảo roughly time-sorted, không phải strictly time-sorted.

Pitfall 7: Không có kế hoạch cho sau khi epoch overflow

Sai: “69 năm nữa mới hết, không phải việc của mình.” Đúng: Document rõ overflow date. Đặt calendar reminder. Lên kế hoạch migration: tăng timestamp lên 42+ bit (giảm machine/sequence bits), hoặc chọn epoch mới và migrate.


8. Wrap Up --- Tóm tắt cho Interviewer

BướcNội dung chính
Step 1: Scope64-bit, globally unique, time-sortable, 10K+ IDs/sec, multi-datacenter
Step 2: High-Level4 approaches: Multi-Master, UUID, Ticket Server, Snowflake (chọn)
Step 3: Deep DiveBit layout (1+41+5+5+12), clock sync (NTP), clock skew handling, sequence overflow
Step 4: Wrap UpMonitoring (clock drift, overflow), Security (ID enumeration), Alternatives (ULID, KSUID)

Interview tip: Khi interviewer hỏi “Design a Unique ID Generator”, họ muốn nghe:

  1. Bạn hỏi câu hỏi để làm rõ requirements (không nhảy vào Snowflake ngay)
  2. Bạn trình bày nhiều options rồi chọn có lý do
  3. Bạn hiểu trade-offs (clock skew, ordering guarantees, security)
  4. Bạn biết vận hành hệ thống (monitoring, alerting, NTP)

TopicLinkLiên quan
ScalingTuan-01-Scale-From-Zero-To-MillionsTại sao cần distributed ID khi scale
Capacity EstimationTuan-02-Back-of-the-envelopeCông thức IDs/sec, overflow calculation
Database ShardingTuan-07-Database-Sharding-ReplicationID làm shard key, sequential vs random impact
Consistent HashingTuan-10-Consistent-HashingPhân phối ID generation across nodes
MonitoringTuan-13-Monitoring-ObservabilityClock drift monitoring, alerting
SecurityTuan-14-AuthN-AuthZ-Security · Tuan-15-Data-Security-EncryptionID enumeration, information leakage
URL ShortenerTuan-16-Design-URL-ShortenerSnowflake ID dùng làm base cho short code
Key-Value StoreTuan-20-Design-Key-Value-StoreID làm key trong distributed KV store

Tham khảo


Case Study liên quan: Tuan-16-Design-URL-Shortener (Snowflake ID dùng để tạo short code) · Tuan-20-Design-Key-Value-Store (ID làm key trong distributed store)