Ask HN: Tôi đã thực sự gặp va chạm UUID v4...
(news.ycombinator.com)- Cơ sở dữ liệu hôm nay đã phát hiện UUID v4 trùng lặp, và giá trị hiện có hoàn toàn giống với
b6133fd6-70fe-4fe3-bed6-8ca8fc9386cdcủa một bản ghi được thêm vào năm 2025 - Gói đang dùng là uuid trên npm, và họ nói rằng nó được tạo bằng cách
import { v4 as uuidv4 } from "uuid";rồiconst document_id = uuidv4();và đưa vào cơ sở dữ liệu - Cơ sở dữ liệu chỉ có khoảng 15.000 bản ghi, nên xét về mặt thống kê thì điều này có vẻ bất khả thi, và họ đang hỏi liệu có ai từng gặp chuyện tương tự hay chưa
1 bình luận
Ý kiến Hacker News
jandrewrogers: Điều này phổ biến một cách đáng ngạc nhiên. Độ an toàn của UUIDv4 dựa trên giả định rằng có nguồn entropy chất lượng cao, nhưng giả định đó rất dễ bị phá vỡ bởi lỗi phần cứng, bug phần mềm thông thường, hoặc việc lập trình viên chưa hiểu đúng về entropy
Việc phát hiện nguồn entropy đã hỏng khá tốn kém nên hầu như không ai làm, và thường chỉ biết sau khi đã xảy ra va chạm. Vì vậy ở nhiều hệ thống đòi hỏi độ tin cậy và mức bảo đảm cao, UUIDv4 bị cấm dùng một cách rõ ràng
Càng có nhiều nguồn entropy càng tốt, và khá nhiều trong số đó nên là phi xác định. Ngay cả ở game quy mô nhỏ, nếu trộn các giá trị như tọa độ chuột, khoảng thời gian giữa các lần bấm nút, số frame trước khi nhấn nút bắt đầu vào seed ban đầu, thì dù bên trong vẫn dùng bộ sinh số giả ngẫu nhiên, việc dự đoán cũng sẽ khó hơn nhiều. Nếu CloudFlare dùng ít hơn 100 nguồn entropy thì tôi sẽ thấy thất vọng
Điều đó xảy ra nếu không kiểm tra giá trị trả về kiểu như “đã yêu cầu N byte nhưng chỉ nhận được 3 byte, nên cần yêu cầu lại N-3 byte”, như bên Go ngày xưa. Trên hầu hết phần cứng hay hệ điều hành chuyện này không dễ lộ ra, nên mọi người không kiểm tra, rồi một ngày nó bùng phát thành hàng chục nghìn va chạm trong môi trường production
throwaway_19sz: Nghe buồn cười khó tin nhưng là thật. 10 năm trước, một người bạn của tôi gia nhập một startup tăng trưởng cao với vai trò CTO, công ty có khoảng 200 lập trình viên, và ngay tuần đầu đã phát hiện có một microservice chỉ để tạo UUID
Có 3 kỹ sư chuyên trách cho đúng một endpoint, thậm chí còn có cả người phụ trách cơ sở dữ liệu. Mỗi khi cần UUID “an toàn” mới, mọi đội đều phải gọi dịch vụ này; dịch vụ sẽ tạo UUID, kiểm tra trong DB nội bộ xem UUID đó đã từng được cấp chưa, nếu chưa thì insert rồi mới trả về. Không rõ có phải chỉ để yên tâm tinh thần hay không, nhưng đội đó còn có cả bảng kanban và sprint riêng
Về sau tôi vào một startup khác, cứ mỗi lần ai đó nghĩ ra thêm một nỗi lo mới là lại mọc ra một microservice mới và một team mới. Mục tiêu theo quý còn nêu rõ là phải mở rộng quy mô đội kỹ thuật, và các team 3-4 người tự tạo việc cho mình trong các buổi sprint và planning. Tôi từng đề xuất chuyển nhân lực từ các dự án ổn định sang việc gấp, nhưng bị chặn vì xung đột với KPI phải nâng số lượng kỹ sư lên một mốc nhất định
Để có tính sẵn sàng cao và triển khai toàn cầu, cũng có thể shard bằng cách cấp cho mỗi instance một dải ID riêng. Chỉ cần dành vài bit cao cho ID trung tâm dữ liệu, vài bit nữa cho instance tạo ID bên trong đó. Khoan đã, hình như tôi từng thấy cái này ở đâu rồi… Không biết Twitter giờ còn dùng cách này hay cuối cùng đã thay rồi
Mỗi ngày họ lấy dump DB để kiểm tra khi tạo ID “tạm”, rồi chỉ sau khi gửi đúng vào CMDB thì nó mới thành trạng thái “chính thức”. Còn có cả guardrail để ID tạm không bị dùng trong production, và quy trình tái sử dụng các ID chính thức chưa dùng. Lần cuối tôi nghe thì họ đang ở tháng thứ 18 của một dự án 6 tháng để chuyển cache DB cục bộ sang Zookeeper
CodesInChaos: Thường là do bộ sinh số giả ngẫu nhiên bị thiếu seed. Điều quan trọng là UUID được tạo ở backend hay frontend
Frontend vốn dĩ không đáng tin vì nhiều lý do, bao gồm cả va chạm có chủ đích, nên vẫn cần xử lý xung đột. Backend thì có thể làm cho ổn định. Trước đây VM từng gặp vấn đề này, nhưng giờ lẽ ra đã được giải quyết, dù các process bị sandbox quá chặt có thể vẫn dính nếu dùng đường sinh số ngẫu nhiên thay thế không an toàn. Việc fork process hay VM cũng có thể tạo va chạm do sao chép trạng thái
kst: Tôi nhớ đến một đoạn trong “Pro Git”. <https://git-scm.com/book/en/v2>
Ví dụ ở đó nói rằng nếu toàn bộ 6,5 tỷ người trên Trái Đất mỗi giây đều tạo ra một lượng code bằng toàn bộ lịch sử Linux kernel rồi push vào một kho Git khổng lồ, thì cũng phải mất khoảng 2 năm xác suất va chạm đối tượng SHA-1 mới lên 50%. Vì thế tôi thích cách diễn đạt rằng xác suất xảy ra va chạm SHA-1 tự nhiên còn thấp hơn xác suất cả nhóm của bạn đều bị sói tấn công trong cùng một đêm, mỗi vụ hoàn toàn không liên quan. SHA-1 hash không phải số ngẫu nhiên và là 160-bit nên khác với UUIDv4, nhưng tôi rất thích phép ví von các cuộc sói tấn công không liên quan
Đại loại là nếu cứ mỗi 1 tỷ năm đi một bước quanh Trái Đất tại xích đạo, và sau mỗi vòng lại lấy ra một giọt nước từ Thái Bình Dương, rồi khi biển cạn thì chồng thêm một tờ giấy, lặp lại cho đến khi chạm Mặt Trời, thì ba chữ số đầu của bộ đếm tính bằng 52! giây vẫn không đổi
e12e: Có thảo luận liên quan ở đây: https://github.com/uuidjs/uuid/issues/546
Ví dụ, họ đã test
crypto.getRandomValues()trên googlebot và thấy nó là xác địnhadyavanapalli: Việc đang nói tới hiếm đến mức khả năng cả Trái Đất bị tiểu hành tinh phá hủy ngay lúc này còn cao hơn
Tôi nhớ đã đọc về một phụ nữ thực sự bị thiên thạch đâm trúng nhưng sống sót chỉ bị thương ở chân. Nếu có va chạm UUID thì khả năng áp đảo là do bug phần mềm hoặc máy tính có gì đó không ổn, cũng có thể là tia vũ trụ. Tia vũ trụ tác động vào bộ nhớ hay CPU xảy ra thường xuyên hơn người ta tưởng
juancn: Có phải bộ sinh số ngẫu nhiên được khởi tạo kỳ quặc hoặc thiếu entropy không? Nếu không tùy biến thì nó dùng
crypto.getRandomValues(rnds8), màgetRandomValueskhông quy định mức entropy tối thiểuGeee: Theo diễn giải đa thế giới của cơ học lượng tử, hẳn sẽ có một nhánh vũ trụ nơi mọi UUID đều giống hệt nhau. Tôi tự hỏi những người ở thế giới đó đang nghĩ gì
mittermayr: Tôi hoàn toàn đồng ý rằng chuyện này nghe vô lý. Dù vậy nếu đoán thì khác biệt là trước đây họ tạo UUIDv4 trên điện thoại của người dùng rồi gửi lên DB, còn UUID bị trùng sáng nay thì được tạo trên máy chủ Ubuntu
Tôi không rõ UUIDv4 được tạo như thế nào, hay đặc tính của máy sinh có tham gia vào thuật toán không, nhưng thay đổi duy nhất tôi nghĩ ra là trước đây tạo trên thiết bị, còn vài tháng gần đây thì chuyển sang tạo trên server
Nhưng trên server thì đặc biệt là vào năm 2026, chuyện đó không nên xảy ra. Trước đây seed ngẫu nhiên trên VM từng là vấn đề, nhưng giờ đáng lẽ đỡ hơn rồi. Dù một UUID có được tạo lỗi đi nữa, xác suất một UUID thật sự ngẫu nhiên đâm trúng nó vẫn cực thấp, nên có lẽ cả hai bộ sinh đều có vấn đề
dweez: Đến lúc đọc lại bài thú vị này rồi: https://jasonfantl.com/posts/Universal-Unique-IDs/
Nếu biến cả vũ trụ thành một cỗ máy tính khổng lồ chỉ để tạo UUID cho đến khi nhiệt chết, thì không gian ID sẽ cần bao nhiêu bit?
beejiu: Tôi muốn biết UUID được tạo ở phía client hay phía server. Nếu là phía client thì có thể do bot crawl. Ví dụ Googlebot chạy JavaScript với “tính ngẫu nhiên” mang tính xác định
Những kiểu giải thích này có vẻ hợp lý hơn va chạm ngẫu nhiên thật sự đến vài bậc độ lớn
merlindru: Khả năng cao là vấn đề seed. Nếu chứng minh được không phải vậy thì có lẽ bạn sẽ nổi tiếng một chút đấy
erlkonig: Tôi luôn nói với team rằng nếu có đủ dữ liệu thì các giá trị ngẫu nhiên sớm muộn cũng có thể đụng nhau, và khi đó ta sẽ thấy phần mềm của mình vững đến đâu
Vậy mà vẫn có rất nhiều lập trình viên giàu kinh nghiệm, team lead, CIO tin rằng điều đó là bất khả thi nên không hề viết code xử lý trường hợp đó. Kết quả là một bộ sinh số ngẫu nhiên tệ có thể phá hệ thống nhanh hơn rất nhiều so với dự kiến, và còn có thể làm hỏng đồng thời nếu không có cơ chế phát hiện và tạo lại. Tôi thấy điều đó giống kiểu không kiểm tra
malloc()có thành công hay không. Tôi hay hỏi rằng “nếu thực sự bất khả thi thì có phải ta đang dùng quá nhiều bit không?”leni536: Không phải do ngẫu nhiên trùng hợp; chắc chắn có bug ở đâu đó. Tôi nhìn lướt thì package này có vẻ gọi
crypto.randomUUID()của runtime JS, mà cái này lẽ ra luôn phải được seed đúngKhả năng runtime có bug có vẻ cực thấp, nhưng cũng không biết được. Tôi tò mò họ đang dùng runtime JS nào
jbverschoor: Nguyên nhân có vẻ hợp lý nhất là package sinh số ngẫu nhiên mà
uuidphụ thuộc gần đây đã bị xâm nhập, khiến các số “ngẫu nhiên” trở nên dễ đoán. Nếu vậy thì một cuộc tấn công chuỗi cung ứng có thể đã đặt rất nhiều dự án liên quan đến mật mã, SSL, tiền tệ vào trạng thái nguy hiểmuuid/src/rng.tsđược thay đổi 3 tuần trước, mảng ngẫu nhiên đã bị đổi thànhconst. Điều đó làm mọi lời gọi cùng chia sẻ một mảng ngẫu nhiên giống nhauCác lời gọi sau sẽ cập nhật mã ngẫu nhiên trước đó, nên nếu bạn tạo ra thứ gì quan trọng thì chỉ biết chúc may mắn. Mã cũ dùng
slice()để tạo một bản sao mới. Có thể là thay đổi vô tình, nhưng tôi không hiểu sao nó lọt qua được, vì có vẻ ngay cả bài test tạo hai số ngẫu nhiên rồi kiểm tra chúng khác nhau cũng sẽ không quapif: Ngay cả khi có nguồn entropy chất lượng cao, ta cũng không thể biến “có lẽ như vậy” thành “chắc chắn như vậy”. Nếu cần giá trị khó đoán, hãy tìm đến mật mã, nhưng nếu cần tính duy nhất được đảm bảo thì bạn phải tự tạo ra nó
athrowaway3z: Một quy tắc kinh nghiệm đơn giản là nghĩ xem bạn có thể đưa timestamp vào ID ngoài phần ngẫu nhiên không. Thường thì câu trả lời là có, và UUIDv7 là đủ
Nếu bạn đã xem xét vấn đề đủ sâu để tự viết ra bằng chứng rằng rò rỉ thông tin là không thể chấp nhận được, thì xin chúc mừng. Hệ thống đó có lẽ đủ phức tạp và chậm để dùng hash mật mã mạnh, hoặc nếu ngại thì dùng UUIDv5 cũng được
darqis: PostgreSQL 18 hỗ trợ uuidv7 native, chỉ cần để mặc định là
uniquevàuuid7()tumdum_: Bộ sinh số giả ngẫu nhiên có seed tệ
serf: Xác suất là 1 trên 4.72 × 10²⁸, tức cỡ 1 trên 47,3 octillion. Nếu là thật, trước khi đi mua vé số tôi sẽ nghi race condition hoặc một lỗi đơn giản nào khác
evnix: Bỏ qua hết toán xác suất đi, thực tế chúng ta đang sống là ngay cả khi dùng bộ tạo số ngẫu nhiên phần cứng tốt nhất, mức độ ngẫu nhiên vẫn có thể kém hơn tưởng tượng
Ở những nơi bảo mật không quá quan trọng, tôi sẽ chuyển sang thứ như TSID, hoặc uuidv7, để trong thực tế chuyện này gần như không xảy ra. Theo tôi như vậy còn tốt hơn là over-engineer code retry
jordiburgos: Mong là đừng dùng
b6133fd6-70fe-4fe3-bed6-8ca8fc9386cd. Tôi vừa kiểm tra DB của mình thì thấy nó đã được dùng rồi16b55183-1697-496e-bc8a-854eb9aae0f3, và có lẽ còn vài cái nữa. Nếu mọi người cùng đăng danh sách ở đây thì có lẽ sẽ kiểm tra được trùng lặp chăng?pyuser583: Không biết hiện nay UUID được ưa chuộng nhất là loại nào nhỉ
smokel: Tôi đã nhiều lần đổ lỗi cho compiler, tia vũ trụ, hiệu ứng lượng tử, ít nhất là một bug kernel cực kỳ obscure, rồi cuối cùng nhận ra chính tôi mới là nguyên nhân gây bug
Va chạm trong 15.000 bản ghi là quá hiếm, nên tôi sẽ nghi các nguyên nhân khác trước: xử lý trùng lặp, request bị gửi lại, object bị tái sử dụng, log gây hiểu lầm, tái sử dụng định danh từ đường code khác. Nếu chia sẻ thêm một chút mã xung quanh thì có thể cùng xem được
wazoox: Chuyện này chưa xảy ra với tôi, nhưng hai ngày trước tôi tìm thấy trong sâu thẳm codebase PHP production một thứ như sau: một hàm
createUUID()cắt và ghép giá trị tạo từmd5(uniqid('', true))thành hình dạng giống UUIDTôi không hiểu vì sao thứ kinh dị này đến giờ vẫn chưa cắn trúng điểm yếu chết người của chúng tôi
sedatk:
uuidjs/uuidcó cảnh báo rằng trên các client dùng bộ sinh số ngẫu nhiên xác định như Googlebot, nó có thể tạo ra UUID trùng lặpĐiều này có thể gây vấn đề cho các ứng dụng kỳ vọng UUID tạo phía client luôn là duy nhất, nên cần có chiến lược kiểm tra trùng lặp và fail một cách graceful, hoặc vô hiệu hóa thao tác ghi từ client Googlebot: https://github.com/uuidjs/uuid/commit/91805f665c38b691ac2cbd...
xyzzy123: Tôi từng có một hệ thống phân tán chạy trên Linux bị fail bài test tải lâu dài do UUID trùng lặp
Sau thời gian dài điều tra, hóa ra là bug kernel, chính xác hơn là race condition. Trên hệ thống đa xử lý, khi hai process cùng đọc
/dev/randomđồng thời thì rất hiếm, khoảng một trên một triệu lần, chúng có thể nhận cùng một chuỗi byte. Tôi sẽ kiểm tra phần khởi tạo bộ sinh số ngẫu nhiên trước tiênbaq: Có vẻ VM đang chạy đã làm bay sạch entropy do mọi thứ đều bị ảo hóa
glaslong: Có lẽ phải đi mua vài cái đèn dung nham thôi
0xfffafaCrash: Tôi muốn biết UUID được tạo ở frontend hay backend. Nếu là frontend, thay vì vấn đề entropy, tôi sẽ cược vào khả năng mã phía client hoặc request đã bị can thiệp để chèn vào UUID đã biết sẵn
latentframe: Một trong những câu nguy hiểm nhất trong kỹ thuật là không thể xảy ra về mặt thống kê. Khi quy mô đủ lớn, các trường hợp cực đoan không còn là lý thuyết nữa mà trở thành sự kiện vận hành
8organicbits: Năm ngoái tôi từng viết bài về va chạm thực tế, bao gồm cả thư viện đã gây ra chuyện đó: https://alexsci.com/blog/uuid-oops/
Để UUID có khả năng chống va chạm, có rất nhiều ràng buộc phải được tuân thủ nghiêm ngặt, và trong trường hợp này rất có thể bộ sinh số ngẫu nhiên có vấn đề
nu11ptr: Cuối cùng thì vẫn là vấn đề nguồn entropy. Vì vậy tôi luôn tạo và insert trong một vòng lặp. Nếu có va chạm thì vẫn có thể xử lý một cách graceful
sbuttgereit: Nó không “bất khả thi về mặt kỹ thuật”. Trái lại, về mặt kỹ thuật thì hoàn toàn có thể. Nếu có ngẫu nhiên tốt thì chỉ là cực, cực kỳ hiếm, chứ không có gì trong UUIDv4 ngăn chặn trùng lặp một cách kỹ thuật
beardyw: Có thể là câu hỏi ngớ ngẩn, nhưng chẳng lẽ không thể gắn thêm ngày tháng, dù chỉ tính theo giây ở dạng hex? Chỉ thêm vài byte thôi cũng có vẻ đủ để cái đang ổn bây giờ vẫn được đảm bảo ổn trong tương lai
mdavid626: Cũng có thể có cách giải thích khác. Ví dụ ai đó đã chỉnh tay request hoặc động vào DB
radial_symmetry: Tôi cũng từng gặp một lần và tưởng mình sắp phát điên, nhưng đọc comment ở đây thấy yên tâm hơn
NKosmatos: Nó không “bất khả thi về mặt kỹ thuật”. Không phải bất khả thi, chỉ là cực, cực kỳ hiếm. Chắc phải đi mua vé số thôi
Mỗi lần dùng từ “improbable” tôi lại nhớ đến https://hitchhikers.fandom.com/wiki/Infinite_Improbability_D...
sudb: Đây là lần đầu tôi cảm thấy được “đền đáp” vì đã chọn CUID2 cho một trong các dự án của mình là ý hay thật: https://github.com/paralleldrive/cuid2