Wann sollte ich uuid.uuid1 () vs. uuid.uuid4 () in Python verwenden?

207

Ich verstehe die Unterschiede zwischen den beiden aus den Dokumenten.

uuid1():
Generieren Sie eine UUID aus einer Host-ID, einer Sequenznummer und der aktuellen Uhrzeit

uuid4():
Generiere eine zufällige UUID.

So uuid1verwendet Maschine / Sequenz / Zeitinfo eine UUID zu erzeugen. Was sind die Vor- und Nachteile der Verwendung?

Ich weiß, uuid1()dass Datenschutzbedenken bestehen können, da diese auf Maschineninformationen basieren. Ich frage mich, ob es bei der Auswahl des einen oder anderen etwas Feineres gibt. Ich benutze uuid4()gerade, da es eine völlig zufällige UUID ist. Aber ich frage mich, ob ich uuid1das Kollisionsrisiko verringern sollte .

Grundsätzlich suche ich nach Tipps für Best Practices zur Verwendung des einen gegen das andere. Vielen Dank!

Raketenmonkeys
quelle
3
Hier ist ein alternativer Ansatz zur UUID. Obwohl die Wahrscheinlichkeit einer Kollision infinitesimal ist, garantiert die UUID keine Eindeutigkeit. Um die Eindeutigkeit zu gewährleisten, können Sie den zusammengesetzten Schlüssel als [<System-ID>, <Lokal-ID>] verwenden. Jedes System, das an der gemeinsamen Nutzung von Daten teilnimmt, muss über eine eigene eindeutige ID des Systems verfügen, die entweder während der Systemeinrichtung zugewiesen oder aus einem gemeinsamen Pool von IDs bezogen wurde. Die lokale ID ist eine eindeutige ID innerhalb eines bestimmten Systems. Dies ist mit mehr Aufwand verbunden, garantiert jedoch die Einzigartigkeit. Entschuldigung für das offtopic, ich versuche nur zu helfen.
o
3
Kümmert sich nicht um die "Datenschutzbedenken", die er erwähnte
Shrey

Antworten:

253

uuid1()Es wird garantiert, dass keine Kollisionen auftreten (unter der Annahme, dass Sie nicht zu viele gleichzeitig erstellen). Ich würde es nicht verwenden, wenn es wichtig ist, dass keine Verbindung zwischen dem uuidund dem Computer besteht, da die Mac-Adresse verwendet wird, um es für alle Computer eindeutig zu machen.

Sie können Duplikate erstellen, indem Sie mehr als 2 14 uuid1 in weniger als 100 ns erstellen. Dies ist jedoch in den meisten Anwendungsfällen kein Problem.

uuid4()generiert, wie Sie sagten, eine zufällige UUID. Die Wahrscheinlichkeit einer Kollision ist sehr, sehr, sehr gering. Klein genug, dass Sie sich darüber keine Sorgen machen sollten. Das Problem ist, dass ein schlechter Zufallszahlengenerator die Wahrscheinlichkeit von Kollisionen erhöht.

Diese ausgezeichnete Antwort von Bob Aman fasst es gut zusammen. (Ich empfehle, die ganze Antwort zu lesen.)

Ehrlich gesagt, wird in einem einzigen Anwendungsbereich ohne böswillige Akteure das Aussterben allen Lebens auf der Erde lange vor einer Kollision auftreten, selbst bei einer UUID der Version 4, selbst wenn Sie einige UUIDs pro Sekunde generieren.

Georg Schölly
quelle
Entschuldigung, ich habe kommentiert, ohne vollständig recherchiert zu haben. Es sind Bits reserviert, um zu verhindern, dass eine UUID der Version 4 mit einer UUID der Version 1 kollidiert. Ich werde meinen ursprünglichen Kommentar entfernen. Siehe tools.ietf.org/html/rfc4122
Mark Ransom
1
@gs Ja, macht Sinn mit dem, was ich gelesen habe. uuid1 ist "einzigartiger", während uuid4 anonymer ist. Verwenden Sie also grundsätzlich uuid1, es sei denn, Sie haben einen Grund, dies nicht zu tun. @mark Lösegeld: Tolle Antwort, ist nicht aufgetaucht, als ich nach uuid1 / uuid4 gesucht habe. Es scheint, direkt aus dem Maul des Pferdes.
Rocketmonkeys
6
uuid1erzeugt nicht unbedingt eindeutige UUIDs, wenn Sie mehrere pro Sekunde auf demselben Knoten erzeugen. Beispiel : [uuid.uuid1() for i in range(2)]. Es sei denn natürlich, es passiert etwas Seltsames, das ich vermisse.
Michael Mior
1
@Michael: uuid1hat eine Sequenznummer (4. Element in Ihrem Beispiel). Wenn Sie also nicht alle Bits im Zähler verbrauchen, haben Sie keine Kollision.
Georg Schölly
3
@Michael: Ich habe versucht, die Umstände zu untersuchen, unter denen Kollisionen auftreten, und die gefundenen Informationen hinzugefügt.
Georg Schölly
32

Eine Instanz , wenn man bedenkt , kann uuid1()eher als uuid4()ist , wenn UUIDs auf separaten Maschinen hergestellt werden , zum Beispiel , wenn mehrere Online - Transaktionen Prozess auf mehreren Maschinen sind für die Zwecke Skalierung.

In einer solchen Situation besteht das Risiko von Kollisionen aufgrund schlechter Auswahlmöglichkeiten bei der Initialisierung der Pseudozufallszahlengeneratoren, und auch die möglicherweise höhere Anzahl erzeugter UUIDs, erhöht die Wahrscheinlichkeit, dass doppelte IDs erstellt werden.

Ein weiteres Interesse uuid1()in diesem Fall besteht darin, dass die Maschine, auf der jede GUID ursprünglich erstellt wurde, implizit aufgezeichnet wird (im "Knoten" -Teil der UUID). Dies und die Zeitinformationen können hilfreich sein, wenn auch nur beim Debuggen.

mjv
quelle
20

Mein Team hatte gerade Probleme mit der Verwendung von UUID1 für ein Datenbank-Upgrade-Skript, bei dem wir innerhalb weniger Minuten ~ 120.000 UUIDs generiert haben. Die UUID-Kollision führte zu einer Verletzung einer Primärschlüsseleinschränkung.

Wir haben Hunderte von Servern aktualisiert, aber auf unseren Amazon EC2-Instanzen ist dieses Problem einige Male aufgetreten. Ich vermute, dass eine schlechte Taktauflösung und die Umstellung auf UUID4 das Problem für uns gelöst haben.

Mattias Lagergren
quelle
5

Eine Sache, die Sie bei der Verwendung beachten uuid1sollten: Wenn Sie den Standardaufruf verwenden (ohne clock_seqParameter anzugeben), besteht die Möglichkeit, dass Sie auf Kollisionen stoßen: Sie haben nur 14 Bit Zufälligkeit (wenn Sie 18 Einträge innerhalb von 100 ns generieren, haben Sie eine Wahrscheinlichkeit von ungefähr 1% für eine Kollision Geburtstagsparadoxon / Angriff). Das Problem tritt in den meisten Anwendungsfällen nie auf, aber auf einer virtuellen Maschine mit schlechter Taktauflösung wird es Sie beißen.

Guillaume
quelle
7
@ Guilaume es wäre wirklich nützlich, ein Beispiel für eine gute Praxis mit clock_seq....
Eric
@Guilaume Wie haben Sie diese Chance von 1% berechnet? 14 Bits Zufälligkeit bedeuten, dass die Kollision garantiert auftritt, wenn Sie> = 2 ^ 14 IDs pro 100 ns
generieren.
1
@maks Wie gesagt, du solltest dir das Geburtstagsparadox ansehen .
Guillaume
3

Vielleicht ist etwas, das nicht erwähnt wurde, das der Lokalität.

Eine MAC-Adresse oder eine zeitbasierte Bestellung (UUID1) kann zu einer höheren Datenbankleistung führen, da es weniger Arbeit ist, Zahlen näher zusammen zu sortieren als zufällig verteilte Zahlen (UUID4) (siehe hier ).

Ein zweites verwandtes Problem ist, dass die Verwendung von UUID1 beim Debuggen hilfreich sein kann, selbst wenn Ursprungsdaten verloren gehen oder nicht explizit gespeichert werden (dies steht offensichtlich im Widerspruch zu dem vom OP erwähnten Datenschutzproblem).

cz
quelle
1

Zusätzlich zur akzeptierten Antwort gibt es eine dritte Option, die in einigen Fällen nützlich sein kann:

v1 mit zufälligem MAC ("v1mc")

Sie können einen Hybrid zwischen v1 und v4 erstellen, indem Sie absichtlich v1-UUIDs mit einer zufälligen Broadcast-MAC-Adresse generieren (dies ist in der v1-Spezifikation zulässig). Die resultierende vU-UUID ist zeitabhängig (wie reguläres v1), es fehlen jedoch alle hostspezifischen Informationen (wie v4). Es ist auch in seiner Kollisionsbeständigkeit viel näher an v4: v1mc = 60 Bit Zeit + 61 zufällige Bits = 121 eindeutige Bits; v4 = 122 zufällige Bits.

Der erste Ort, an dem ich darauf stieß, war die Funktion uuid_generate_v1mc () von Postgres . Ich habe seitdem das folgende Python-Äquivalent verwendet:

from os import urandom
from uuid import uuid1
_int_from_bytes = int.from_bytes  # py3 only

def uuid1mc():
    # NOTE: The constant here is required by the UUIDv1 spec...
    return uuid1(_int_from_bytes(urandom(6), "big") | 0x010000000000)

(Hinweis: Ich habe eine längere + schnellere Version, die das UUID-Objekt direkt erstellt; kann posten, wenn jemand möchte)


Bei GROSSEN Anrufvolumina pro Sekunde kann dies die Zufälligkeit des Systems erschöpfen. Sie könnenrandom stattdessen das stdlib- Modul verwenden (es wird wahrscheinlich auch schneller sein). Aber seien Sie gewarnt: Es dauert nur einige hundert UUIDs, bis ein Angreifer den RNG-Status bestimmen und somit zukünftige UUIDs teilweise vorhersagen kann.

import random
from uuid import uuid1

def uuid1mc_insecure():
    return uuid1(random.getrandbits(48) | 0x010000000000)
Eli Collins
quelle
Diese Methode scheint "wie" v4 (hostunabhängig) zu sein, aber schlimmer (weniger Bits, Abhängigkeit vom Zufall usw.). Gibt es irgendwelche Vorteile gegenüber nur uuid4?
Rocketmonkeys
Dies ist in erster Linie nur ein Upgrade für Fälle, in denen v1 für seine zeitbasierten Eigenschaften nützlich ist, jedoch eine stärkere Kollisionsbeständigkeit und Privatsphäre des Hosts erwünscht sind. Ein Beispiel ist ein Primärschlüssel für eine Datenbank - im Vergleich zu Version 4 haben Benutzeroberflächen von Version 1 eine bessere Lokalität beim Schreiben auf die Festplatte, eine nützlichere natürliche Sortierung usw. Aber wenn Sie einen Fall haben, in dem ein Angreifer 2 ** vorhersagt 61 Bit ist ein Sicherheitsproblem (z. B. als uuid a nonce), dann $ diety ja, verwenden Sie stattdessen uuid4 (ich weiß, dass ich es tue!). Betreff: Da es schlimmer ist, weil es Urandom verwendet, bin ich mir nicht sicher, was Sie meinen - unter Python verwendet uuid4 () auch Urandom.
Eli Collins
Gutes Zeug, das macht Sinn. Es ist gut zu sehen, was Sie nicht nur tun können (Ihren Code), sondern auch, warum Sie es wollen. Betreff: urandom, ich meine, Sie verbrauchen 2x die Zufälligkeit (1 für uuid1, eine andere für das urandom), um die Systementropie schneller zu verbrauchen.
Rocketmonkeys
Es ist ungefähr halb so viel wie uuid4: uuid1 () verwendet 14 Bits für clock_seq, was auf 2 Bytes Urandom aufrundet. Der uuid1mc-Wrapper verwendet 48 Bit, die 6 Bytes Urandom zugeordnet werden sollen, für insgesamt Urandom (8), die pro Aufruf verbraucht werden. Während uuid4 bei jedem Aufruf direkt urandom (16) aufruft.
Eli Collins