Verwenden von UUIDs anstelle von ObjectIDs in MongoDB

77

Wir migrieren aus Leistungsgründen eine Datenbank von MySQL nach MongoDB und überlegen, was für IDs der MongoDB-Dokumente verwendet werden soll. Wir diskutieren zwischen der Verwendung von ObjectIDs, der MongoDB-Standardeinstellung, oder der Verwendung von UUIDs (die wir bisher in MySQL verwendet haben). Bisher müssen wir folgende Argumente unterstützen, um diese Optionen zu unterstützen:

ObjectIDs: ObjectIDs sind die MongoDB-Standardeinstellungen, und ich gehe davon aus (obwohl ich nicht sicher bin), dass dies einen Grund hat, was bedeutet, dass ich davon ausgehe, dass MongoDB sie effizienter handhaben kann als UUIDs oder einen anderen Grund hat, sie zu bevorzugen. Ich fand auch diese Stackoverflow-Antwort , in der erwähnt wird, dass die Verwendung von ObjectIDs die Indizierung effizienter macht. Es wäre jedoch schön, einige Metriken darüber zu haben, wie viel diese "effizientere" ist.

UUIDs: Unser grundlegendes Argument für die Verwendung von UUIDs (und es ist sehr wichtig) ist, dass sie auf die eine oder andere Weise von praktisch jeder Datenbank unterstützt werden. Dies bedeutet, dass wir uns für die Clients dieser API nicht ändern, wenn wir uns aus irgendeinem Grund dazu entschließen, von MongoDB zu etwas anderem zu wechseln, und dass wir bereits eine API haben, die Dokumente basierend auf ihren IDs aus der Datenbank abruft, da die IDs fortgesetzt werden können genau das gleiche sein. Wenn wir ObjectIDs verwenden würden, wäre ich mir nicht sicher, wie wir sie in eine andere Datenbank migrieren würden.

Hat jemand einen Einblick, ob eine dieser Optionen besser ist als die andere und warum? Haben Sie jemals UUIDs in MongoDB anstelle von ObjectIDs verwendet und wenn ja, auf welche Vorteile / Probleme sind Sie gestoßen?

Christina
quelle

Antworten:

35

Ich denke, das ist eine großartige Idee, und Mongo auch. Sie listen UUIDs als eine der allgemeinen Optionen für das _idFeld auf .

Überlegungen:

  • Leistung - Wie in anderen Antworten erwähnt, zeigen Benchmarks , dass UUIDs bei Einfügungen zu einem Leistungsabfall führen. Im schlimmsten Fall (gemessen von 10 bis 20 Millionen Dokumenten in einer Sammlung) sind sie etwa 2-3 mal langsamer - der Unterschied zwischen dem Einfügen von 2.000 (UUID) und 7.500 (ObjectID) Dokumenten pro Sekunde. Dies ist ein großer Unterschied, aber seine Bedeutung hängt ganz von Ihrem Anwendungsfall ab. Werden Sie Millionen von Dokumenten gleichzeitig stapelweise einfügen? Bei den meisten Apps, die ich erstellt habe, werden häufig einzelne Dokumente eingefügt. In diesem Test ist der Unterschied viel geringer (6.250 -vs- 7.500; ~ 20%). Der ID-Typ ist einfach nicht der begrenzende Faktor.
  • Portabilität - Andere DBs bieten sicherlich eine gute UUID-Unterstützung, sodass die Portabilität verbessert wird. Da UUIDs größer sind (mehr Bits), ist es alternativ möglich, eine ObjectID in die "Form" einer UUID zu packen . Dieser Ansatz ist nicht so gut wie die direkte Portabilität, bietet Ihnen jedoch einen Weg nach vorne.

Gegen einige der anderen Antworten:

  • UUIDs werden nativ unterstützt - Sie können die UUID()Funktion in der Mongo Shell genauso verwenden, wie Sie sie verwenden würden ObjectID(). um einen String in ein äquivalentes BSON-Objekt zu konvertieren.
  • UUIDs sind nicht besonders groß - Sie sind 128 Bit im Vergleich zu ObjectIDs mit 96 Bit. (Sie sollten mit einem binären Subtyp codiert werden 0x04.)
  • UUIDs können einen Zeitstempel enthalten. Insbesondere codiert UUIDv1 einen Zeitstempel mit einer Genauigkeit von 60 Bit im Vergleich zu 32 Bit in ObjectIDs. Dies ist mehr als 6 Größenordnungen präziser, also Nanosekunden statt Sekunden. Es kann tatsächlich eine anständige Methode zum Speichern von Zeitstempeln sein, die genauer sind als die Unterstützung von Mongo / JS-Datumsobjekten ...
    • Die eingebaute UUID()Funktion generiert nur (zufällige) v4-UUIDs. Um dies zu nutzen, sollten Sie sich bei der ID-Erstellung auf Ihre App oder Ihren Mongo-Treiber stützen.
    • Im Gegensatz zu ObjectIDs gibt Ihnen der Zeitstempel aufgrund der Art und Weise, wie UUIDs aufgeteilt werden , keine natürliche Reihenfolge. Dies kann je nach Anwendungsfall gut oder schlecht sein.
    • Das Einfügen von Zeitstempeln in Ihre IDs ist oft eine schlechte Idee. Am Ende verlieren Sie die erstellte Zeit von Dokumenten überall dort, wo eine ID verfügbar ist. Um die Sache noch schlimmer zu machen, codieren v1-UUIDs auch eine eindeutige Kennung für den Computer, auf dem sie generiert werden, wodurch zusätzliche Informationen über Ihre Infrastruktur (z. B. Anzahl der Server) verfügbar gemacht werden können. Natürlich codieren ObjectIDs auch einen Zeitstempel, so dass dies teilweise auch für sie gilt.
Molomby
quelle
46

Das _idFeld MongoDB kann einen beliebigen Wert haben, solange Sie sicherstellen können, dass es für die Sammlung eindeutig ist. Wenn Ihre Daten bereits einen natürlichen Schlüssel haben, gibt es keinen Grund, diesen nicht anstelle der automatisch generierten ObjectIDs zu verwenden.

ObjectIDs werden als vernünftige Standardlösung bereitgestellt, um einen eigenen eindeutigen Schlüssel sicher zu generieren (und Anfänger davon abzuhalten, SQLs zu kopieren, AUTO INCREMENT was in einer verteilten Datenbank eine schlechte Idee ist).

Wenn Sie keine ObjectIDs verwenden, verpassen Sie auch eine weitere praktische Funktion: Eine ObjectID enthält auch einen Unix-Zeitstempel, als sie generiert wurde, und viele Treiber bieten eine Funktion zum Extrahieren und Konvertieren in ein Datum. Dies kann manchmal ein separates create-dateFeld überflüssig machen.

Wenn Sie jedoch keine Bedenken haben, können Sie Ihre UUIDs als _idFeld verwenden.

Philipp
quelle
1
Vielen Dank, die Wahrheit ist, dass mir IDs mit Informationen zum Erstellungsdatum nicht wirklich wichtig sind (das habe ich bereits als separate Spalte). Haben Sie vielleicht einen Einblick in die Leistungsunterschiede zwischen den beiden?
Christina
9
Hallo Christina, tatsächlich gibt es im MongoDB Java-Treiber ein interessantes Foto, das die Einfügezeit im Vergleich zwischen ObjectId- und UUID-Werten zeigt . Jira.mongodb.org/browse/JAVA-403 . Fasziniert von dem Ansatz, den Sie am Ende gewählt haben.
Roman Blachman
1
UUIDv1 enthält auch einen Zeitstempel und mit ~ 6 Größenordnungen mehr Präzision. UUIDv1 codiert 60 Bit Zeit (Nanosekunden) im Vergleich zu ObjectIDs 32 Bit (Sekunden).
Molomby
8

Berücksichtigen Sie die Datenmenge, die Sie jeweils speichern würden.

Eine MongoDB- Objekt-ID ist 12 Byte groß, wird zur Speicherung gepackt und ihre Teile sind nach Leistung organisiert (dh der Zeitstempel wird zuerst gespeichert, was ein logisches Bestellkriterium ist).

Umgekehrt beträgt eine Standard-UUID 36 Byte, enthält Bindestriche und wird normalerweise als Zeichenfolge gespeichert. Selbst wenn Sie nicht numerische Zeichen entfernen und numerisch speichern möchten, müssen Sie sich dennoch mit dem "indexy" -Teil (der Teil einer UUID v1, der auf Zeitstempeln basiert) in der Mitte der UUID befinden und dies nicht tun. Es eignet sich gut zum Sortieren. Es wurden Studien durchgeführt, die eine performante UUID-Speicherung ermöglichen, und ich habe sogar eine Node.js-Bibliothek geschrieben , um die Verwaltung zu unterstützen.

Wenn Sie eine UUID verwenden möchten, sollten Sie sie für eine optimale Indizierung und Sortierung neu organisieren. Andernfalls stoßen Sie wahrscheinlich an eine Leistungswand.

sws
quelle
würde wahrscheinlich hinzufügen, dass es sorgfältig überlegt werden sollte, da Sie nicht in allen Fällen etwas Sortierbares / Vorhersehbares wollen. Wenn Sie beispielsweise Sitzungs-IDs generieren, sollten Sie die Version uuid v4 (zufällig) verwenden.
Robin F.
Wie wäre es mit Sharding? Können Sie eine nicht gehashte UUID zum Sharding verwenden oder hätten Sie das gleiche Problem wie bei ObjectID, bei dem die neuen Schreibvorgänge alle in einem Shard enden würden?
mjaggard
1
Kein Grund, die UUID als Zeichenfolge zu speichern ... Die Standard-UUID beträgt genau 16 Byte und wird normalerweise sogar im Mongo als Rohbyte gespeichert. Niemand verwendet die UUID v1, nur v4 (zufällig) und v5 (sha1).
Dmitry Gusarov
3
Wie @Dmitry feststellt, sind UUIDs 16 Byte (128 Bit) und werden im Allgemeinen nicht als Zeichenfolge gespeichert. MongoDB bietet native Unterstützung und speichert sie als binären Subtyp 0x04. Sie haben Recht mit dem unglücklichen Zeitstempel, das ist ein echter Schmerz. Ich wünschte, es gäbe eine offizielle UUID-Version, die sich eher wie eine SQUUID verhält.
Molomby
1

Ich habe diese Benchmarks vor einiger Zeit gefunden, als ich die gleiche Frage hatte. Sie zeigen im Grunde, dass die Verwendung einer Guid anstelle von ObjectId zu einem Rückgang der Indexleistung führt.

Ich würde auf jeden Fall empfehlen, dass Sie die Benchmarks so anpassen, dass sie Ihr spezifisches reales Szenario imitieren und sehen, wie die Zahlen aussehen. Man kann sich nicht zu 100% auf generische Benchmarks verlassen.

Eli
quelle
1

Wir müssen darauf achten, die Kosten für das Einfügen einer Sache durch MongoDB von den Kosten für die Generierung der Sache zuzüglich dieser Kosten im Verhältnis zur Größe der Nutzlast zu unterscheiden. Unten finden Sie eine kleine Matrix, die die Methode zum Generieren der _idKreuzung anhand der Größe einer optionalen zusätzlichen Nutzlast im Wert von Bytes zeigt. Bei den Tests wird nur Javascript verwendet, das auf dem MacBook Pro localhost für 100.000 Einfügungen unter Verwendung insertManyvon Stapeln von 100 ohne Transaktionen durchgeführt wird, um zu versuchen, Netzwerk-, Chat- und andere Faktoren zu entfernen. Zwei Läufe mit Batch = 1 wurden ebenfalls durchgeführt, um den dramatischen Unterschied hervorzuheben.


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%                       


Dies war ein schneller Test, aber es scheint klar zu sein, dass grundlegende Zeichenfolgen und Ints _idungefähr die gleiche Geschwindigkeit haben, aber tatsächlich eine UUID generieren. Dies erhöht die Zeit - insbesondere, wenn Sie die Zeichenfolgenversion des UUID()Objekts verwenden, z. B. UUID().toString().substr(6,36) ist es auch erwähnenswert, dass das Erstellen einer Zeichenfolge ObjectIdangezeigt wird so schnell sein.

Buzz Moschetti
quelle