3 điểm bởi GN⁺ 2024-08-27 | 1 bình luận | Chia sẻ qua WhatsApp
  • UUID có 8 phiên bản từ v1 đến v8; số cao hơn không có nghĩa là mới hơn hay tốt hơn, mà là các cách tạo khác nhau được định nghĩa trong RFC 9562
  • Lựa chọn phổ biến thường thu hẹp vào v4 và v7: v4 là mặc định cho ID ngẫu nhiên, còn v7 phù hợp khi cần sắp xếp theo thời gian tạo
  • v1 và v6 dùng cùng vật liệu đầu vào, nhưng v6 thay đổi thứ tự các trường để khi sắp xếp sẽ theo thời gian tạo
  • v3 và v5 tạo UUID bằng cách băm dữ liệu đầu vào; v3 dùng MD5, v5 dùng SHA-1, và DNS, URL có thể là các giá trị đầu vào ứng viên
  • Trên thực tế, nếu có thể thì dùng v7 thay cho v1/v6; nếu cần UUID dựa trên dữ liệu đầu vào thì dùng v5; nếu cần UUID hoàn toàn tùy chỉnh thì cân nhắc v8

Cách tạo theo từng phiên bản UUID

  • Các phiên bản UUID từ v1 đến v8 đều được định nghĩa trong RFC 9562
  • UUID dựa trên thời gian

    • UUID Version 1 được tạo từ timestamp, bộ đếm đơn điệu và địa chỉ MAC
    • UUID Version 6 dùng cùng dữ liệu như v1, nhưng đổi thứ tự để khi sắp xếp sẽ theo thời gian tạo
    • UUID Version 7 được tạo từ timestamp và dữ liệu ngẫu nhiên
  • UUID ngẫu nhiên và do người dùng định nghĩa

    • UUID Version 4 được tạo hoàn toàn từ dữ liệu ngẫu nhiên, gần với hình dung của nhiều người khi nhắc đến UUID
    • UUID Version 8 là cơ chế hoàn toàn do người dùng định nghĩa, ngoại trừ các trường version/variant cần có ở mọi phiên bản
  • UUID dựa trên băm dữ liệu đầu vào

    • UUID Version 3 được tạo từ băm MD5 của dữ liệu do người dùng cung cấp
      • Các giá trị đầu vào ứng viên trong RFC gồm DNS và URL
    • UUID Version 5 được tạo từ băm SHA-1 của dữ liệu do người dùng cung cấp
      • Tương tự v3, DNS và URL có thể được dùng làm đầu vào ứng viên
  • UUID được dành riêng

    • UUID Version 2 được dành riêng cho ID bảo mật và không có chi tiết đã biết

Tiêu chí lựa chọn trong thực tế

  • Trong hầu hết trường hợp, lựa chọn là v4 hoặc v7
  • Nếu chỉ cần một ID ngẫu nhiên, v4 là lựa chọn mặc định phù hợp
  • Nếu ID cần có khả năng sắp xếp, có thể cân nhắc v7
    • Ví dụ, khi dùng UUID làm khóa cơ sở dữ liệu, v7 có thể là một ứng viên
  • v5 hoặc v8 phù hợp hơn khi muốn đưa dữ liệu riêng vào trong UUID
    • Với các kiểu này, thường người dùng đã biết rõ mình cần chúng trong tình huống nào
  • Theo RFC, v7 cải tiến v1 và v6, vì vậy nếu có thể nên dùng v7 thay cho v1/v6
    • Nếu nhất thiết cần v1 hoặc v6, có thể dùng v6
  • v2 được dành riêng cho mục đích bảo mật chưa được đặc tả
  • v3 đã được thay thế bởi v5, phiên bản dùng hàm băm mạnh hơn; nếu cần v3 thì nhiều khả năng người dùng cũng đã biết rõ lý do

1 bình luận

 
GN⁺ 2024-08-27
Các ý kiến trên Hacker News
  • Nói rằng không có chi tiết về UUID v2 chỉ đúng nếu bạn chỉ đọc RFC vốn nổi tiếng là mơ hồ: https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#t...
    Hầu như bạn sẽ không gặp nó, nhưng cũng có UUID version 0. Nó đáng được nhắc đến vì là nguồn gốc của các bit dự trữ cho phép các “phiên bản” khác về sau được định nghĩa theo cách tương thích. Tôi đã tổng hợp phần khảo cứu liên quan trong thư viện UUID của mình: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
    Vì nó khá hay nên tôi quyết định hỗ trợ, nhưng vẫn còn phải quyết định cách xử lý rollover ngày tháng và các Apollo UID còn cũ hơn

    • Công bằng mà nói, RFC9562 có trích dẫn hai tài liệu đặc tả UUID Version 2. Tuy vậy RFC4122 thì theo gu của tôi hơi giống mật mã quá
      Tôi đang bắt đầu làm một RFC mang tính thông tin về các kiểu UUID lịch sử được định nghĩa trong không gian Variant 0-7, nhằm giúp mọi người hiểu hơn. Nếu muốn thảo luận hoặc rà soát câu chữ, có thể xem https://github.com/yocto/draft-yocto-uuid
  • Có thể tìm thấy chi tiết trong 2 phút. Bấm vào liên kết trong bài để đến phần định nghĩa DCE của RFC 9562, theo liên kết đầu tiên trong đoạn đó để vào đặc tả, rồi tìm “UUID” để tới phụ lục A là có hết
    Tên gọi hơi gây nhầm là “Universal Unique Identifier”, nhưng nội dung cần thiết đều có. Ít nhất cũng nên tự bấm thử các liên kết mình đưa vào bài

    • Có hai điều có thể học ở đây. Một là đọc đặc tả UUID v2, hai là suy ra thông tin ngữ cảnh tại thời điểm tạo từ UUID
      Xét theo ngữ cảnh, vì các phiên bản khác được mô tả là được tạo từ những yếu tố cụ thể, nên câu đó rõ ràng đang nói về ý thứ hai. Câu văn hơi không rõ, nhưng khó nói là nó dẫn dắt hiểu lầm
    • Tôi cũng đã đi đúng quy trình đó, và vì có vẻ tác giả không mấy quan tâm đến chi tiết nên tôi đã không đọc hết bài
      Dù vậy phụ lục đọc cũng thú vị, như một snapshot của thời điểm đó
    • Có lẽ bài viết do mô hình ngôn ngữ viết cũng nên
  • Ước gì có một chuẩn UUID ngắn kiểu 73WakrfVbNJBaAmhQtEeDv hay bK7nP9xM
    Nói nghiêm ngặt thì vì ở đâu đó vẫn có thể trùng nên không phải UUID, nhưng tôi muốn có một tổ hợp chuẩn cho ID vừa ngẫu nhiên vừa đủ ngắn để nhớ

    • Gần nhất tôi nghĩ đến là ULID. Nó là base32 dài 26 ký tự, ngắn, 128 bit và có thể sắp xếp theo thứ tự từ điển
      Tôi nghĩ lý do không có chuẩn phổ biến hơn là vì phải đánh đổi điều gì đó. 128 bit có rủi ro va chạm thấp cho gần như mọi mục đích, nhưng càng nhỏ hơn thì càng phải xét tình huống cụ thể và tác động của va chạm, nên khó chuẩn hóa. Dùng các mã hóa khác như base64 hay base85 thì ngắn hơn, nhưng phải hy sinh những thứ như phân biệt hoa thường và độ an toàn trong URL: https://github.com/ulid/spec
    • Nếu cần UUID gọn hơn, có thể chuyển UUID hiện có sang base64 an toàn cho URL để biểu diễn ở dạng 22 ký tự
      Nó chỉ là một cách biểu diễn khác của cùng UUID và có thể chuyển ngược. Rốt cuộc UUID là một giá trị 128 bit, nên đây giống cách ghi thay thế hơn là chuyển đổi thực sự
    • Sqids có thể là thứ phù hợp. Nó tạo ID ngắn hơn UUID rất nhiều, nhưng không duy nhất toàn cục và được sinh từ chuỗi số nguyên: https://sqids.org/
    • Thường thì tạo số ngẫu nhiên N bit rồi mã hóa bằng base58. N chọn bao nhiêu tùy nhu cầu
      Bạn sẽ mất lợi thế sắp xếp đơn điệu có trong một số phiên bản UUID, nhưng base58 an toàn cho URL và không chứa ký tự đặc biệt. Giá trị vẫn có thể được lưu dưới dạng nhị phân. Ví dụ trong Postgres có thể dùng bytea thay vì cột văn bản
    • Hiện đang có công việc chuẩn hóa các kỹ thuật mã hóa thay thế để biểu diễn UUID 128 bit ở dạng văn bản ngắn hơn
      Có thể xem thảo luận tại đây: https://github.com/uuid6/new-uuid-encoding-techniques-ietf-d...
  • Timestamp của UUID v7 là một thay đổi lớn với Databend. Chúng tôi dùng nó để nhanh chóng tìm các tệp metadata theo timestamp trên AWS S3, nên các tác vụ như vacuum nhanh hơn nhiều
    PR: https://github.com/datafuselabs/databend/pull/16049

    • Thú vị đấy, nhưng tôi không nghĩ ngay đến việc dùng UUID để tìm theo thời gian. Nếu là tôi, có lẽ tôi sẽ đặt một trường timestamp riêng
      Lợi thế lớn của UUID sắp xếp theo thời gian là tính cục bộ tốt hơn. Khi thêm mục mới vào chỉ mục, phần lớn sẽ là thao tác nối vào cuối, có thể rẻ hơn so với chèn ngẫu nhiên. Tuy nhiên cũng có thể tăng tranh chấp, nên một phương án lai đáng cân nhắc là đặt một số bit ngẫu nhiên trước timestamp để tạo các “shard” có thứ tự. Việc đọc cũng thường tập trung vào dữ liệu mới nhất, nên dữ liệu mới nằm cùng một chỗ và dễ vào cache là điều hữu ích
    • Nó cũng rất tốt cho kho khóa-giá trị như DynamoDB. Sạch sẽ hơn nhiều so với khóa tổng hợp có tiền tố là timestamp hoặc ngày ISO, và cũng tốt hơn việc lãng phí chỉ mục phụ cho timestamp
  • Khó hiểu mục đích của uuid2. Tôi cũng không biết là còn có những kiểu như vậy, và lần đầu thấy uuid2 khi yêu cầu Xandr xóa dữ liệu cá nhân của mình: https://news.ycombinator.com/item?id=40913915
    Đọc Wikipedia rồi vẫn không hiểu vì sao đã tạo ra cái gọi là “định danh duy nhất phổ quát” mà lại còn có nhiều kiểu, và vì sao một số trong đó ban đầu được thiết kế để có thể truy vết tới tận PC. Tôi thắc mắc liệu việc trộn một phần mã MAC có làm uuid2 gần với ngẫu nhiên hơn không, hay có lý do nào khác. Từ góc độ bảo vệ quyền riêng tư, tôi cũng thắc mắc liệu chỉ dùng một định danh dài với rất nhiều ký tự có thể chọn, để khả năng trùng lặp gần như bằng không, có được không

    • Mục đích ban đầu là nhận diện các thông điệp trong kiến trúc điện toán phân tán Apollo. UID và về sau là UUID là một cách có thể đảo ngược để biểu thị giao điểm của hai chiều
      Bất kỳ hai máy nào cũng có thể tạo cùng một UID/UUID từ cùng hai đầu vào, và bên nhận thông điệp đã được định danh có thể chuyển định danh đó ngược lại thành các thành phần ban đầu. Vì được thiết kế làm nhãn cho các thông điệp tạm thời, hai chiều đó là thời gian và ID phần cứng; ban đầu dùng số sê-ri Apollo, về sau dùng địa chỉ phần cứng Ethernet, v.v.
      Tôi cho rằng phần lớn sự nhầm lẫn đến từ việc trong triển khai AEGIS ban đầu, các kỹ sư Apollo bắt đầu dùng UID “canned”, tức tĩnh và đã biết rộng rãi, để định danh hệ thống tệp. Theo thời gian, mục đích sử dụng phổ biến của UUID đã chuyển hẳn từ định danh tạm thời trong đó trùng lặp là có chủ đích, sang định danh canned cần tránh trùng lặp; và hai chiều cũng trở thành ngẫu nhiên và lại ngẫu nhiên
      Lịch sử còn phức tạp hơn. Microsoft đã thuê một trong những nhân vật chủ chốt của Apollo để làm MSRPC cho Windows NT, và vì thế GUID cũng ra đời. GUID có cách bố trí trường khác UUID, và trái với điều nhiều tài liệu nói, nó không phải mixed-endian. Microsoft rất thích dùng GUID canned không chỉ để định danh các thông điệp RPC tạm thời, mà còn cho gần như mọi thứ cần định danh đã biết rộng rãi như lớp COM, codec media, v.v. Ví dụ: https://gix.github.io/media-types/
      Xin lỗi vì liên kết tới kho của tôi hai lần trong cùng một phần bình luận, nhưng tôi đã bắt đầu tổng hợp lịch sử này trong README của thư viện UUID của mình và cần tiếp tục lại. Apollo bắt đầu từ năm 1980, còn bản nháp RFC UUID của Leach/Salz mãi tới năm 1998 mới xuất hiện, nên có cực kỳ nhiều câu chuyện bị thiếu trong tiêu chuẩn hiện đại: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
  • UUID v4 chỉ là bộ sinh byte ngẫu nhiên có chèn dấu gạch nối ở các vị trí được quy định. Không nhất thiết phải dùng nó; tự tạo byte ngẫu nhiên có thể tiết kiệm dung lượng
    Cũng giảm được các thứ không cần thiết như dấu gạch nối, thông tin phiên bản

    • UUID là một số 128 bit, và biểu diễn chuỗi có dấu gạch nối chỉ là một trong nhiều cách biểu diễn số đó
      Tương tự như địa chỉ IPv4 là số 32 bit, còn “bốn cụm phân tách bằng dấu chấm” chỉ là một trong các cách biểu diễn. Nếu bạn nghĩ UUID là một định dạng chuỗi, thì ngay từ khái niệm cơ bản nhất về UUID bạn đã hiểu sai rồi. Ngay cả khi chỉ muốn một định danh ngẫu nhiên, tôi vẫn thấy việc UUID ngẫu nhiên có một vài bit cờ nhỏ nói rằng “cái này được chủ ý tạo ngẫu nhiên” là hay. Nó hữu ích khi bạn chỉ tìm thấy một định danh không có ngữ cảnh
    • UUID v4 cũng đặt cố định 4 bit để cho biết đây là phiên bản 4
      Có thể tranh luận việc tạo các không gian tên khác nhau giữa nhiều cách sinh có hữu ích hay không, nhưng xác suất một bộ sinh số ngẫu nhiên thông thường tạo ra UUIDv4 hợp lệ chỉ là 1/16. Tất nhiên nếu muốn tự viết bộ sinh UUID thì đặt đúng các bit là chuyện nhỏ
    • Đúng, nhưng điểm hấp dẫn với đa số lập trình viên là triển khai đơn giản. Gần như ngôn ngữ nào cũng có thư viện UUID hoạt động chỉ với một dòng
      Với Go thì chỉ cần uuid.New().String(), còn nếu đọc dữ liệu ngẫu nhiên bằng crypto/rand rồi chuyển sang base64 hoặc hex thì cần nhiều dòng và công sức hơn
  • Khuyến nghị không dùng các phiên bản dựa trên MAC. Về lý thuyết có thể bao gồm mọi phiên bản ngoài v4 và v7, nhưng v1 là tệ nhất
    v3 cũng có vấn đề là MD5 đã bị phá vỡ nghiêm trọng

    • MD5 bị “phá vỡ” với tư cách là hàm băm mật mã. Là một hàm băm phi mật mã thì nó vẫn hoàn toàn ổn
  • Tôi không biết các chi tiết ngoài số 4, nhưng thứ thật sự hữu ích còn thiếu có lẽ là cách dùng dữ liệu SHA256 và bộ đếm. Nó có dạng tương tự PBKDF2
    Có thể trở thành định danh phái sinh vẫn bảo toàn quyền riêng tư, và cũng có thể chứng minh một cách lỏng lẻo rằng một UUID cụ thể được phái sinh từ seed nào đó

    • Nếu thật sự cần, có thể xem UUIDv4 là định dạng mã hóa, tạo đầu ra 122 bit bằng thuật toán mật mã rồi mã hóa thành UUID
      Ngoài ra thì có lẽ bạn sẽ muốn đầu ra dài hơn
    • Nó giống v3, nhưng là dạng cho phép chỉ định thuật toán băm
  • Cứ dùng v7 là được
    Giờ đến lượt các chuyên gia bảo mật nói là không phải vậy

    • Nếu ngày tạo có thể là thông tin nhạy cảm hoặc UUID cần hoàn toàn không thể đoán được, hãy dùng v4. Còn lại thì dùng v7
    • Điều tôi luôn thấy tiếc ở UUID và ULID là, theo những gì tôi biết, không có cách tốt để tạo một cách xác định
      Với nhiều trường hợp sử dụng, sẽ rất hữu ích nếu xử lý lại dữ liệu vẫn tạo ra cùng một ID, nhưng tôi không biết cách chuẩn nào để đạt được điều đó
    • Tôi không hiểu vì sao các vấn đề liên quan đến bảo mật lại bị xem nhẹ như vậy