Sử dụng UUID thay vì ObjectID trong MongoDB


82

Chúng tôi đang di chuyển cơ sở dữ liệu từ MySQL sang MongoDB vì lý do hiệu suất và xem xét những gì sẽ sử dụng cho ID của tài liệu MongoDB. Chúng tôi đang tranh luận giữa việc sử dụng ObjectID, là mặc định của MongoDB hay sử dụng UUID thay thế (đó là những gì chúng tôi đã sử dụng cho đến bây giờ trong MySQL). Cho đến nay, các đối số mà chúng tôi có để hỗ trợ bất kỳ tùy chọn nào trong số này là:

ObjectID: ObjectID là mặc định của MongoDB và tôi cho rằng (mặc dù tôi không chắc) rằng điều này là có lý do, nghĩa là tôi mong đợi MongoDB có thể xử lý chúng hiệu quả hơn UUID hoặc có một lý do khác để thích chúng hơn. Tôi cũng tìm thấy câu trả lời stackoverflow này đề cập rằng việc sử dụng ObjectID giúp lập chỉ mục hiệu quả hơn, tuy nhiên, sẽ rất tuyệt nếu có một số số liệu về mức độ "hiệu quả hơn" này.

UUID: Lập luận cơ bản của chúng tôi ủng hộ việc sử dụng UUID (và nó khá quan trọng) là chúng được hỗ trợ, bằng cách này hay cách khác, bởi hầu như bất kỳ cơ sở dữ liệu nào. Điều này có nghĩa là nếu một cách nào đó, chúng tôi quyết định chuyển từ MongoDB sang thứ khác vì bất kỳ lý do gì và chúng tôi đã có một API truy xuất tài liệu từ DB dựa trên ID của chúng, không có gì thay đổi đối với khách hàng của API này vì các ID có thể tiếp tục hoàn toàn giống nhau. Nếu chúng ta sử dụng ObjectID, tôi không thực sự chắc chắn về việc chuyển chúng sang DB khác như thế nào.

Có ai có hiểu biết về việc liệu một trong những tùy chọn này có thể tốt hơn lựa chọn kia hay không và tại sao? Bạn đã bao giờ sử dụng UUID trong MongoDB thay vì ObjectID chưa và nếu có thì những ưu điểm / vấn đề bạn gặp phải là gì?

Câu trả lời:


41

Tôi nghĩ đây là một ý tưởng tuyệt vời và Mongo cũng vậy; họ liệt kê UUIDs là một trong những lựa chọn phổ biến đối với các _idlĩnh vực .

Cân nhắc:

  • Hiệu suất - Như các câu trả lời khác đã đề cập, điểm chuẩn hiển thị UUID gây giảm hiệu suất cho các lần chèn. Trong trường hợp xấu nhất được đo (đi từ 10 triệu đến 20 triệu tài liệu trong một bộ sưu tập), chúng chậm hơn khoảng 2-3 lần - sự khác biệt giữa việc chèn 2.000 (UUID) và 7.500 (ObjectID) tài liệu mỗi giây. Đây là một sự khác biệt lớn nhưng ý nghĩa của nó phụ thuộc hoàn toàn vào trường hợp sử dụng của bạn. Bạn sẽ chèn hàng triệu tài liệu cùng một lúc chứ? Đối với hầu hết các ứng dụng mà tôi đã xây dựng, trường hợp phổ biến là chèn các tài liệu riêng lẻ. Trong thử nghiệm đó, sự khác biệt nhỏ hơn nhiều (6.250-chọi- 7.500; ~ 20%). Loại ID đơn giản không phải là yếu tố giới hạn.
  • Tính di động - Các DB khác chắc chắn có xu hướng hỗ trợ UUID tốt nên tính di động sẽ được cải thiện. Ngoài ra, vì UUID lớn hơn (nhiều bit hơn) nên có thể đóng gói lại một ObjectID thành "hình dạng" của một UUID . Cách tiếp cận này không tốt bằng tính di động trực tiếp nhưng nó mang lại cho bạn một con đường phía trước.

Phản bác một số câu trả lời khác:

  • UUID có hỗ trợ riêng - Bạn có thể sử dụng UUID()chức năng trong Mongo Shell giống hệt như cách bạn sử dụng ObjectID(); để chuyển đổi một chuỗi thành đối tượng BSON tương đương.
  • UUID không đặc biệt lớn - Chúng là 128 bit so với ObjectID là 96 bit. (Chúng phải được mã hóa bằng kiểu con nhị phân 0x04.)
  • UUID có thể bao gồm dấu thời gian - Cụ thể, UUIDv1 mã hóa dấu thời gian với độ chính xác 60 bit, so với 32 bit trong ObjectID. Đây là độ chính xác cao hơn 6 bậc, vì vậy nano giây thay vì giây. Nó thực sự có thể là một cách tốt để lưu trữ tạo dấu thời gian với độ chính xác hơn so với hỗ trợ đối tượng Mongo / JS Date, tuy nhiên ...
    • Việc xây dựng trong UUID()chức năng chỉ tạo v4 UUIDs (ngẫu nhiên) như vậy, để tận dụng này này, bạn muốn để dựa vào vào ứng dụng của bạn hoặc lái xe Mongo để tạo ID.
    • Không giống như ObjectID, do cách UUID được phân chia , dấu thời gian không cung cấp cho bạn một thứ tự tự nhiên. Điều này có thể tốt hoặc xấu tùy thuộc vào trường hợp sử dụng của bạn.
    • Bao gồm dấu thời gian trong ID của bạn thường là một Ý tưởng Xấu. Bạn sẽ làm rò rỉ thời gian đã tạo của tài liệu ở bất kỳ nơi nào ID bị lộ. Để làm cho các vấn đề tồi tệ hơn, các UUID v1 cũng mã hóa một số nhận dạng duy nhất cho máy mà chúng được tạo trên đó có thể hiển thị thông tin bổ sung về cơ sở hạ tầng của bạn (ví dụ: số lượng máy chủ). Tất nhiên, các ObjectID cũng mã hóa một dấu thời gian nên điều này cũng đúng với chúng một phần.

48

Trường _idMongoDB có thể có bất kỳ giá trị nào bạn muốn miễn là bạn có thể đảm bảo rằng nó là duy nhất cho bộ sưu tập. Khi dữ liệu của bạn đã có khóa tự nhiên, không có lý do gì để không sử dụng khóa này thay cho các ObjectID được tạo tự động.

ObjectID được cung cấp như một giải pháp mặc định hợp lý để tạo thời gian an toàn cho việc tạo một khóa duy nhất của riêng mình (và để ngăn cản người mới bắt đầu cố gắng sao chép SQL AUTO INCREMENT , một ý tưởng tồi trong cơ sở dữ liệu phân tán).

Bằng cách không sử dụng ObjectID, bạn cũng bỏ lỡ một tính năng tiện lợi khác: ObjectID cũng bao gồm một dấu thời gian unix khi nó được tạo và nhiều trình điều khiển cung cấp khả năng trích xuất nó và chuyển đổi nó thành một ngày. Điều này đôi khi có thể làm cho một create-datetrường riêng biệt trở nên thừa.

Nhưng khi bạn không phải lo lắng, bạn có thể tự do sử dụng UUID của mình làm _idtrường.


1
Cảm ơn bạn, sự thật là tôi không thực sự quan tâm đến việc ID nắm giữ thông tin về ngày tạo (tôi đã có đó là một cột riêng biệt). Có lẽ bạn có bất kỳ thông tin chi tiết nào về sự khác biệt hiệu suất giữa hai loại không?
Christina

9
Xin chào Christina, thực sự có một bức ảnh thú vị trong Trình điều khiển Java MongoDB cho bạn thấy thời gian chèn khi so sánh giữa các giá trị ObjectId và UUID jira.mongodb.org/browse/JAVA-403 . Thích thú khi nghe về cách tiếp cận cuối cùng bạn đã thực hiện.
Roman Blachman,

1
UUIDv1 cũng bao gồm một dấu thời gian và với độ chính xác cao hơn ~ 6 bậc. UUIDv1 mã hóa 60 bit thời gian (nano giây) so với ObjectIDs 32 bit (giây).
Molomby

8

Cân nhắc lượng dữ liệu bạn sẽ lưu trữ trong mỗi trường hợp.

Một MongoDB ObjectID có kích thước 12 byte, được đóng gói để lưu trữ và các phần của nó được tổ chức để thực hiện (tức là dấu thời gian được lưu trữ trước, đây là tiêu chí sắp xếp hợp lý).

Ngược lại, UUID chuẩn là 36 byte, chứa dấu gạch ngang và thường được lưu trữ dưới dạng chuỗi. Hơn nữa, ngay cả khi bạn tách các ký tự không phải số và có ý định lưu trữ bằng số, bạn vẫn phải bằng lòng với phần "thời hạn" của nó (phần của UUID v1 dựa trên dấu thời gian) nằm ở giữa UUID và không t cho vay tốt để phân loại. Có những nghiên cứu được thực hiện cho phép lưu trữ UUID hiệu quả và tôi thậm chí đã viết một thư viện Node.js để hỗ trợ việc quản lý nó.

Nếu bạn định sử dụng UUID, hãy xem xét tổ chức lại nó để lập chỉ mục và sắp xếp tối ưu; nếu không, bạn có thể sẽ đụng phải một bức tường hiệu suất.


có lẽ sẽ nói thêm rằng nó nên được xem xét cẩn thận vì không phải trong mọi trường hợp, bạn sẽ không phải thứ gì đó có thể sắp xếp / dự đoán được. ví dụ: khi tạo id phiên, bạn nên sử dụng phiên bản uuid v4 (ngẫu nhiên).
Robin F.

Còn về sharding thì sao, bạn có thể sử dụng UUID không băm để làm sharding hay bạn sẽ gặp vấn đề tương tự như đối với ObjectID, nơi mà các lần ghi mới sẽ kết thúc trong một phân đoạn?
mjaggard

1
không có lý do gì để lưu trữ UUID dưới dạng chuỗi ... UUID tiêu chuẩn chính xác là 16 byte và thường được lưu trữ dưới dạng byte thô ngay cả trong mongo. Không ai sử dụng v1 UUID, chỉ v4 (ngẫu nhiên) và v5 (sha1).
Dmitry Gusarov

3
Như @Dmitry lưu ý, UUID có 16 byte (128 bit) và thường không được lưu trữ dưới dạng chuỗi. MongoDB có hỗ trợ gốc và lưu trữ chúng ở dạng con Binary 0x04. Tuy nhiên, bạn nói đúng về sự cố dấu thời gian đáng tiếc, đó là một nỗi đau thực sự. Tôi ước có một phiên bản UUID chính thức hoạt động giống SQUUID hơn.
Molomby

1

Tôi đã tìm thấy những Điểm chuẩn này cách đây một lúc khi tôi có cùng một câu hỏi. Về cơ bản, chúng cho thấy rằng việc sử dụng Hướng dẫn thay vì ObjectId gây ra giảm Hiệu suất chỉ mục.

Dù sao thì tôi cũng khuyên bạn nên tùy chỉnh Điểm chuẩn để bắt chước tình huống thực tế cụ thể của bạn và xem các con số trông như thế nào, không thể dựa 100% vào Điểm chuẩn chung chung.


1

Chúng ta phải cẩn thận phân biệt chi phí của MongoDB chèn một thứ với chi phí để tạo ra thứ ngay từ đầu cộng với chi phí đó so với kích thước của trọng tải. Dưới đây là một ma trận nhỏ cho thấy phương pháp tạo dấu _idgạch chéo so với kích thước của trọng tải phụ có giá trị byte tùy chọn. Các thử nghiệm chỉ sử dụng javascript, được thực hiện trên máy chủ cục bộ của MacBook Pro cho 100.000 lần chèn bằng cách sử dụng insertManylô 100 không có giao dịch để cố gắng loại bỏ mạng, trò chuyện và các yếu tố khác. Hai lần chạy với batch = 1 cũng đã được thực hiện chỉ để làm nổi bật sự khác biệt đáng kể.


Method                                                                                         
A  :  Simple int:          _id:0, _id:1, ...                                                   
B  :  ObjectId             _id:ObjectId("5e0e6a804888946fa61a1976"), ...                       
C  :  Simple string:       _id:"A0", _id:"A1", ...                                             

D  :  UUID length string   _id:"9575edcc-cb70-4d63-97ed-ee5d624de87b0", ...                    
      (but not actually                                                                        
      generated by UUID()                                                                      

E  :  Real generated UUID  _id: UUID("35992974-21ea-4f61-b715-2dfaed663b73"), ...              
      (stored UUID() object)                                                                   

F  :  Real generated UUID  _id: "6b16f733-ff24-4172-83f9-e4f96ace6775"                         
      (stored as string, e.g.                                                                  
      UUID().toString().substr(6,36)                                                           

Time in milliseconds to perform 100,000 inserts on fresh (empty) collection.

Extra                M E T H O D   (Batch = 100)                                                               
Payload   A     B     C     D     E     F       % drop A to F                                  
--------  ----  ----  ----  ----  ----  ----    ------------                                   
None      2379  2386  2418  2492  3472  4267    80%                                            
512       2934  2928  3048  3128  4151  4870    66%                                            
1024      3249  3309  3375  3390  4847  5237    61%                                            
2048      3953  3832  3987  4342  5448  5888    49% 
4096      6299  6343  6199  6449  7634  8640    37%                                            
8192      9716  9292  9397 10816 11212 11321    16% 

Extra              M E T H O D   (Batch = 1)                                          
Payload   A      B      C      D      E      F       % drop A to F              
--------  -----  -----  -----  -----  -----  -----                              
None      48006  48419  49136  48757  50649  51280   6.8%                       
1024      50986  50894  49383  49373  51200  51821   1.2%                       


Đây là một thử nghiệm nhanh chóng nhưng có vẻ như rõ ràng rằng các chuỗi và int cơ bản _idcó tốc độ gần giống nhau nhưng thực sự tạo ra một UUID sẽ làm tăng thêm thời gian - đặc biệt nếu bạn lấy phiên bản chuỗi của UUID()đối tượng, ví dụ: UUID().toString().substr(6,36) Cũng cần lưu ý rằng việc xây dựng một ObjectIdxuất hiện nhanh chóng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.