UUID giúp nhiều node tạo ID mà không cần xin một bộ đếm trung tâm, nhưng UUIDv4 hoàn toàn ngẫu nhiên có thể làm các lần chèn phân tán trên B-tree. UUIDv7 giữ kích thước 128 bit và khả năng sinh phân tán, đồng thời đưa timestamp Unix mili-giây lên đầu để các ID mới thường nằm gần nhau khi sắp xếp.
Đây không phải “auto-increment có thêm randomness”. Để dùng đúng, đội phát triển cần hiểu bố cục bit, tính đơn điệu trong cùng một mili-giây, hành vi khi đồng hồ lùi, kiểu lưu trữ và giới hạn bảo mật.
1. Vì sao cần thêm một phiên bản UUID?
ID tự tăng có index locality tốt và dễ đọc, nhưng việc cấp số qua nhiều service, thiết bị hoặc vùng dữ liệu thường cần phối hợp. UUIDv4 giải quyết phần lớn bài toán phối hợp bằng 122 bit ngẫu nhiên khả dụng, đổi lại giá trị sinh liên tiếp không gần nhau.
Với B-tree, insert ngẫu nhiên có thể chạm nhiều page, tăng page split, cache miss và write amplification. RFC 9562 chuẩn hóa UUIDv7 để đặt thời gian theo thứ tự byte ở phần đầu. RFC khuyến nghị dùng UUIDv7 thay UUIDv1 hoặc UUIDv6 khi có thể.
2. Bố cục 128 bit của UUIDv7
| Trường | Số bit | Vai trò |
|---|---|---|
unix_ts_ms | 48 | Timestamp Unix mili-giây, big-endian |
ver | 4 | Giá trị nhị phân của version 7 |
rand_a | 12 | Randomness hoặc phần cơ chế monotonic tùy implementation |
var | 2 | UUID variant chuẩn |
rand_b | 62 | Randomness hoặc counter tùy implementation |
Nếu không dành bit cho fraction/counter, tổng phần ngẫu nhiên là 74 bit. Chuỗi canonical vẫn có dạng 8-4-4-4-12 như UUID quen thuộc; ký tự đầu của nhóm thứ ba biểu thị version 7.
Đừng tự ghép timestamp và random bằng thao tác bit chưa được kiểm thử. Hãy dùng thư viện triển khai RFC 9562 và xác minh bằng test vector của RFC.
3. “Time-ordered” thực sự cam kết điều gì?
Timestamp nằm ở các bit quan trọng nhất nên UUIDv7 từ mili-giây muộn hơn sẽ thường sắp sau UUIDv7 từ mili-giây sớm hơn khi so sánh byte đúng chuẩn. UUIDv6 và UUIDv7 được thiết kế để có thể sort như 128 bit opaque, không cần bóc timestamp.
Tuy nhiên, điều đó không tự động tạo ra tổng thứ tự tuyệt đối:
- Nhiều ID sinh trong cùng một mili-giây cần chiến lược monotonic riêng nếu thứ tự nội bộ quan trọng.
- Đồng hồ giữa các node có thể lệch; ID từ node A “mới hơn” chưa chắc đại diện event xảy ra sau event của node B.
- Độ trễ mạng và retry có thể làm thứ tự ghi database khác thứ tự timestamp.
- UUIDv7 không thay thế sequence, commit timestamp, vector clock hay offset của message broker khi nghiệp vụ cần thứ tự nghiêm ngặt.
4. Lợi ích với B-tree và giới hạn của lợi ích đó
ID mới thường dồn về vùng cuối hoặc vùng lân cận trong index thay vì rải đều như UUIDv4. Điều này có thể cải thiện cache locality và giảm churn của page trong workload insert-heavy. Nhưng mức cải thiện thực tế phụ thuộc DBMS, page size, fill factor, concurrency, buffer pool, storage và pattern ghi.
Không nên công bố một tỷ lệ tăng tốc chung. Hãy benchmark trên schema và tải thật, đo ít nhất throughput, p95/p99 latency, page split, index size, WAL/redo volume và cache hit. Một primary key tốt không sửa được query thiếu index hoặc transaction quá lớn.
5. So sánh các lựa chọn ID
| Lựa chọn | Điểm mạnh | Đánh đổi |
|---|---|---|
| Auto-increment | Nhỏ, locality cao, thứ tự rõ trong một nguồn cấp | Cần phối hợp khi sinh ngoài database; dễ lộ quy mô |
| UUIDv4 | Phân tán, phổ biến, entropy lớn | Insert ngẫu nhiên trên B-tree |
| UUIDv7 | Phân tán, có tiền tố thời gian, locality tốt hơn | Lộ thời điểm gần đúng; monotonicity cần xử lý |
| ID nghiệp vụ | Có ý nghĩa với người dùng | Dễ thay đổi, có quy tắc phức tạp; không nên làm surrogate key mặc định |
Một hệ thống có thể dùng UUIDv7 làm khóa kỹ thuật và một mã hiển thị riêng cho con người. Đừng bắt một cột vừa làm identity nội bộ, số hóa đơn, mã bảo mật và thứ tự nghiệp vụ.
6. Nhiều ID trong cùng một mili-giây
RFC cho phép ba hướng tăng tính đơn điệu: dành các bit đầu của phần random cho fraction dưới mili-giây; dùng counter được seed cẩn thận; hoặc dùng giá trị random có thể tăng như counter. Phần còn lại vẫn nên giữ randomness phù hợp.
Nếu ứng dụng chỉ dùng randomness, các ID cùng mili-giây vẫn có xác suất trùng cực thấp nhưng thứ tự của chúng là ngẫu nhiên. Nếu cần monotonic trong một process, chọn thư viện nêu rõ phương pháp, giới hạn số ID mỗi tick và hành vi khi counter tràn. Không tự giả định mọi package mang tên UUIDv7 đều bảo đảm strictly increasing.
7. Đồng hồ lùi và hệ phân tán
NTP adjustment, snapshot VM, suspend/resume hoặc cấu hình sai có thể làm clock quay về trước. Implementation cần phát hiện UUID mới không lớn hơn giá trị trước và áp dụng policy rõ ràng: giữ timestamp trước rồi tăng counter, chờ đồng hồ bắt kịp, hoặc báo lỗi.
Trong nhiều process/node, state monotonic cục bộ không tạo monotonic toàn cluster. Nếu nghiệp vụ bắt buộc một thứ tự toàn cục, hãy dùng một sequencer hoặc cơ chế đồng thuận phù hợp. Nếu chỉ cần uniqueness và locality tương đối, CSPRNG độc lập trên mỗi node thường loại bỏ nhu cầu phối hợp.
8. Sinh ID ở application hay database?
- Application-generated: có ID trước khi ghi, thuận tiện cho offline work, event và quan hệ giữa nhiều bản ghi.
- Database-generated: một nguồn thời gian/state có thể cho monotonicity tốt hơn trong ứng dụng một database.
- Hai phía cùng sinh: dễ tạo sai khác implementation; chỉ nên dùng khi có contract và test vector chung.
Chọn một owner mặc định. Nếu nhiều service sinh ID, khóa version thư viện, kiểm tra variant/version ở boundary và ghi rõ semantics trong API schema.
9. Lưu binary/native thay vì text khi phù hợp
UUID là 128 bit. Chuỗi canonical gồm 36 ký tự, nên lưu text thường tốn nhiều không gian index hơn kiểu UUID native hoặc binary 16 byte. RFC khuyến nghị lưu dạng 128 bit nền tảng khi khả thi.
- Ưu tiên kiểu
uuidnative của DBMS nếu driver và migration hỗ trợ. - Nếu dùng
BINARY(16), thống nhất network byte order; tránh helper cũ tự hoán đổi byte cho UUIDv1. - Nếu buộc lưu text, chuẩn hóa chữ thường, định dạng và collation; kiểm thử sort thực tế.
- Đảm bảo foreign key dùng cùng kiểu và độ dài với primary key.
10. Uniqueness vẫn cần ràng buộc database
Entropy lớn làm collision cực khó, không làm collision bất khả thi. Lỗi RNG, clone state, bug thư viện hoặc import dữ liệu có thể tạo trùng. Primary key hay unique index là hàng phòng thủ cuối cùng.
Khi insert gặp duplicate, không retry vô hạn. Ghi metric an toàn, sinh lại theo giới hạn và điều tra nếu tần suất khác 0. Tuyệt đối không bỏ unique constraint chỉ vì “UUID không thể trùng”.
11. Bảo mật và quyền riêng tư
RFC cảnh báo không xem UUID như capability bí mật. UUIDv7 để lộ thời điểm tạo gần đúng và thứ tự tương đối, còn counter có thể làm giá trị dễ đoán hơn. ID không thay thế authorization và không được dùng như token reset mật khẩu, API key hay signed download URL.
- Kiểm tra quyền truy cập cho mọi object lookup, bất kể ID khó đoán đến đâu.
- Dùng CSPRNG cho phần random; không dùng PRNG tiện ích có seed dự đoán được.
- Nếu timestamp tạo bản ghi là dữ liệu nhạy cảm, cân nhắc UUIDv4 hoặc lớp ID công khai khác.
- Không log dữ liệu riêng tư chỉ vì ID bản thân không phải secret.
12. Kế hoạch migration từ UUIDv4 hoặc bigint
- Đo baseline index size, insert latency, page split và storage.
- Thêm cột UUIDv7 nullable cùng unique index theo cách ít khóa phù hợp DBMS.
- Backfill theo batch có checkpoint; không suy diễn thời gian lịch sử từ ID mới sinh lúc backfill.
- Dual-write ID mới, kiểm tra parity và cập nhật foreign key/API theo từng giai đoạn.
- Chuyển luồng đọc, theo dõi query plan và rollback path.
- Chỉ đổi primary key hoặc xóa cột cũ sau thời gian ổn định và kiểm toán đầy đủ.
Thay primary key có thể kéo theo toàn bộ foreign key, replication, cache key, URL và data warehouse. Lợi ích locality hiếm khi biện minh cho một migration “big bang”.
13. Checklist kiểm thử
- Parse/serialize round-trip và khớp test vector RFC 9562.
- Xác nhận đúng version 7, variant và timestamp mili-giây.
- Sinh tải lớn trong cùng một mili-giây để test collision/counter overflow.
- Mô phỏng clock rollback, freeze time và process restart.
- Chạy đồng thời nhiều thread/process/node.
- So sánh thứ tự byte trong application với thứ tự của database.
- Kiểm tra unique constraint và đường xử lý duplicate.
- Benchmark UUIDv4, UUIDv7 và khóa hiện tại trên workload đại diện.
Khi nào nên dùng UUIDv7?
- Dùng khi cần sinh ID phân tán và muốn locality tốt hơn UUIDv4.
- Dùng khi timestamp gần đúng trong ID được chấp nhận về quyền riêng tư.
- Giữ bigint/sequence nếu một database cấp ID và sự đơn giản quan trọng hơn sinh phân tán.
- Giữ UUIDv4 nếu thứ tự thời gian không cần thiết hoặc việc lộ thời điểm tạo là không mong muốn.
- Dùng công cụ thứ tự chuyên dụng nếu cần causality hoặc global sequence.
Kết luận
UUIDv7 là một cải tiến thực dụng: tiền tố thời gian giúp ID sinh gần nhau có locality tốt hơn, còn phần random cho phép nhiều node hoạt động không cần bộ đếm trung tâm. Nhưng nó không hứa một trật tự nhân quả toàn cầu, không thay thế unique constraint và không phải token bảo mật. Triển khai tốt bắt đầu từ thư viện theo RFC 9562, storage 128 bit phù hợp, policy clock rollback rõ ràng, benchmark trên workload thật và migration theo từng giai đoạn.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.