Ask HN: Ich habe tatsächlich eine UUID-v4-Kollision erlebt...
(news.ycombinator.com)- Die Datenbank hat heute eine doppelte UUID v4 erkannt, und der vorhandene Wert war exakt derselbe wie
b6133fd6-70fe-4fe3-bed6-8ca8fc9386cdin einem 2025 hinzugefügten Datensatz - Verwendet wird das npm-Paket uuid; die UUID wird nach
import { v4 as uuidv4 } from "uuid";mitconst document_id = uuidv4();erzeugt und dann in die Datenbank geschrieben - In der Datenbank gibt es nur etwa 15.000 Datensätze, sodass das statistisch unmöglich erscheint, und es wird gefragt, ob jemand schon einmal dasselbe erlebt hat
1 Kommentare
Hacker-News-Kommentare
jandrewrogers: Das ist überraschend häufig. Die Sicherheit von UUIDv4 beruht auf der Annahme, dass eine hochwertige Entropiequelle vorhanden ist, aber diese Annahme wird durch Hardwarefehler, gewöhnliche Software-Bugs und ein mangelndes Verständnis von Entropie bei Entwicklern leicht gebrochen.
Zu erkennen, dass die Entropiequelle kaputt ist, ist ziemlich teuer, daher macht es fast niemand, und am Ende merkt man es erst, nachdem es bereits zu Kollisionen gekommen ist. Deshalb ist UUIDv4 in vielen hochzuverlässigen und hochsichernden Systemen ausdrücklich verboten.
Je mehr Entropiequellen, desto besser, und ein guter Teil davon sollte nichtdeterministisch sein. Selbst in kleinen Spielen wird es deutlich schwerer, Vorhersagen zu treffen, wenn man Werte wie Mauskoordinaten, Abstände zwischen Tastendrücken oder die Anzahl der Frames vor dem Drücken des Startknopfs in den initialen Seed einmischt, selbst wenn intern ein Pseudozufallszahlengenerator verwendet wird. Ich wäre enttäuscht, wenn CloudFlare weniger als 100 Entropiequellen einsetzen würde.
Das passiert, wenn man die Rückgabewerte nicht prüft, wie früher im Go-Umfeld: „Es wurden N Bytes angefordert, aber nur 3 Bytes zurückgegeben, also müssen die restlichen N-3 Bytes erneut angefordert werden.“ Auf den meisten Hardware- und Betriebssystemkombinationen tritt das nicht auf, deshalb prüfen die Leute es nicht, und eines Tages zeigt es sich in der Produktion durch zehntausende Kollisionen.
throwaway_19sz: Kaum zu glauben, aber wahr: Vor 10 Jahren stieg ein Freund von mir als CTO bei einem schnell wachsenden Startup ein, einem Unternehmen mit etwa 200 Entwicklern, und entdeckte in seiner ersten Woche, dass es dort einen Mikroservice nur zur UUID-Erzeugung gab.
Für diesen einen Endpunkt gab es drei dedizierte Engineers und sogar noch jemanden für die Datenbank. Wenn ein Team eine neue „sichere“ UUID brauchte, musste es diesen Service aufrufen; der erzeugte die UUID, prüfte in seiner eigenen DB, ob sie bereits ausgegeben worden war, fügte sie ein, wenn nicht, und gab sie dann zurück. Ob das dem Seelenfrieden diente, weiß ich nicht, aber dieses Team hatte sogar sein eigenes Kanban-Board und seine eigenen Sprints.
In einem späteren Startup wurde jedes Mal, wenn jemand eine neue Sorge erfand, ein neuer Mikroservice und ein neues Team daraus. Ein Quartalsziel war ausdrücklich, die Größe der Engineering-Organisation zu erhöhen, und 3- bis 4-Personen-Teams erfanden in ihren eigenen Sprints und Planungsmeetings neue Arbeit für sich selbst. Ich schlug vor, Leute aus stabilen Projekten auf dringende Themen zu verlagern, aber das wurde blockiert, weil es mit KPIs kollidierte, nach denen die Zahl der Engineers auf einen bestimmten Wert steigen musste.
Für Hochverfügbarkeit und weltweite Verteilung kann man das auch sharden, indem jede Instanz einen eigenen ID-Bereich bekommt. Ein paar der oberen Bits reserviert man für die Rechenzentrums-ID, ein paar weitere für die ID-Generator-Instanz darin. Moment mal, das habe ich doch schon mal irgendwo gesehen … Ich frage mich, ob Twitter das noch immer so macht oder irgendwann ersetzt hat.
Man bekam tägliche DB-Dumps, prüfte damit bei der Erzeugung „temporärer“ IDs, und erst nach korrekter Einreichung in die CMDB wurden sie „bestätigt“. Es gab auch Guardrails, damit temporäre IDs nicht in Produktion landeten, sowie ein Verfahren, ungenutzte bestätigte IDs wiederzuverwenden. Als ich zuletzt davon hörte, lief ein auf sechs Monate angesetztes Projekt schon seit 18 Monaten, um den lokalen DB-Cache auf Zookeeper umzustellen.
CodesInChaos: Meistens liegt es an einem schlecht geseedeten Pseudozufallszahlengenerator. Es macht einen Unterschied, ob die UUID im Backend oder im Frontend erzeugt wurde.
Dem Frontend kann man aus mehreren Gründen grundsätzlich nicht trauen, einschließlich absichtlicher Kollisionen, daher braucht man Kollisionsbehandlung. Das Backend kann man zuverlässig bauen. Früher gab es solche Probleme mit VMs, heute sollte das eigentlich gelöst sein, aber stark sandboxed Prozesse können immer noch betroffen sein, wenn sie einen unsicheren Ersatzpfad für Zufallszahlen verwenden. Auch Forks von Prozessen oder VMs können durch kopierten Zustand Kollisionen erzeugen.
kst: Mich erinnert das an eine Passage aus „Pro Git“. <https://git-scm.com/book/en/v2>
Dort hieß es sinngemäß: Wenn alle 6,5 Milliarden Menschen auf der Erde jede Sekunde Code in der Größenordnung der kompletten Linux-Kernel-Historie erzeugen und in ein einziges riesiges Git-Repository pushen würden, bräuchte es ungefähr zwei Jahre, bis die Wahrscheinlichkeit einer SHA-1-Objektkollision 50 % erreichte. Deshalb mochte ich die Formulierung, dass eine natürliche SHA-1-Kollision unwahrscheinlicher sei, als dass alle Mitglieder eines Teams in derselben Nacht unabhängig voneinander von Wölfen angegriffen würden und dabei sterben. SHA-1-Hashes sind keine Zufallszahlen und haben 160 Bit, also ist das nicht dasselbe wie UUIDv4, aber die Metapher mit den unabhängigen Wolfsangriffen gefällt mir.
Die Analogie dort ist ungefähr so: Wenn man am Äquator alle eine Milliarde Jahre einen Schritt um die Erde macht und bei jeder Runde einen Tropfen Wasser aus dem Pazifik entfernt, und wenn der Ozean leer ist, ein Blatt Papier stapelt, und diesen Prozess so oft wiederholt, bis der Stapel die Sonne erreicht, dann ändern sich die ersten drei Ziffern eines Timers von 52! Sekunden immer noch nicht.
e12e: Eine verwandte Diskussion gibt es hier: https://github.com/uuidjs/uuid/issues/546
Dort steht zum Beispiel, dass
crypto.getRandomValues()beim Testen mit googlebot deterministisch war.adyavanapalli: Das, worüber hier geredet wird, ist so selten, dass in diesem Moment eher die gesamte Erde von einem Asteroiden zerstört wird.
Ich habe tatsächlich mal von einer Frau gehört, die von einem Meteoriten getroffen wurde und mit einer Beinverletzung überlebt hat. Wenn es zu einer UUID-Kollision gekommen ist, ist es mit überwältigender Wahrscheinlichkeit ein Software-Bug oder ein Computerfehler, vielleicht auch kosmische Strahlung. Dass kosmische Strahlung Speicher oder CPU beeinflusst, kommt öfter vor, als man denkt.
juancn: Ist vielleicht die Initialisierung des Zufallszahlengenerators seltsam oder fehlt Entropie? Wenn man nichts angepasst hat, wird
crypto.getRandomValues(rnds8)verwendet, undgetRandomValuesgibt keine Mindestentropiemenge an.Geee: Nach der Viele-Welten-Interpretation der Quantenmechanik gibt es sicher irgendwo einen Universumszweig, in dem alle UUIDs gleich sind. Ich frage mich, was die Leute dort darüber denken.
mittermayr: Ich stimme völlig zu, dass das keinen Sinn ergibt. Wenn ich trotzdem raten müsste: Früher wurde UUIDv4 auf dem Handy des Nutzers erzeugt und an die DB geschickt, während die heute Morgen kollidierte UUID auf einem Ubuntu-Server erzeugt wurde – das wäre ein Unterschied.
Ich weiß nicht genau, wie UUIDv4 erzeugt wird oder ob Eigenschaften der Maschine, auf der sie erzeugt wird, in den Algorithmus eingehen, aber die einzige Änderung, die mir einfällt, ist: Früher wurden sie auf dem Gerät erzeugt, seit ein paar Monaten auf dem Server.
Aber auf dem Server sollte das insbesondere 2026 nicht passieren. Früher gab es Probleme mit dem Seeding von VM-Zufallszahlen, heute sollte das viel seltener sein. Selbst wenn eine der UUIDs schlecht erzeugt wurde, ist die Wahrscheinlichkeit, dass eine wirklich zufällige UUID genau damit kollidiert, extrem gering, also müssten wahrscheinlich beide Generatoren kaputt sein.
dweez: Es ist wieder Zeit für diesen schönen Artikel: https://jasonfantl.com/posts/Universal-Unique-IDs/
Wenn man das gesamte Universum in einen riesigen Computer verwandelt, der bis zum Wärmetod nichts anderes tut, als UUIDs zu erzeugen: Wie viele Bits bräuchte dann der ID-Raum?
beejiu: Ich frage mich, ob die UUID clientseitig oder serverseitig erzeugt wird. Falls clientseitig, könnten Crawler-Bots die Ursache sein. Googlebot führt JavaScript zum Beispiel mit deterministischem „Zufall“ aus.
Erklärungen dieser Art sind um mehrere Größenordnungen plausibler als eine echte zufällige Kollision.
merlindru: Wahrscheinlich ein Seed-Problem. Wenn du beweisen kannst, dass es das nicht war, wirst du vielleicht sogar ein bisschen berühmt.
erlkonig: Ich sage meinem Team immer wieder, dass bei genug Daten auch zufällige Werte irgendwann kollidieren können und man dann sieht, wie robust die Software wirklich ist.
Trotzdem gibt es viele erfahrene Entwickler, Teamleiter und CIOs, die glauben, das sei unmöglich, und deshalb keinerlei Code schreiben, um damit umzugehen. Dann kann ein schlechter Zufallszahlengenerator ein System viel schneller zerstören als erwartet, und es kann sogar zu gleichzeitiger Korruption ohne Erkennung und Neuerzeugung kommen. Für mich ist das dieselbe Sorte Leute, die auch nicht prüfen, ob
malloc()erfolgreich war. Ich frage dann gern: „Wenn es unmöglich ist, verwenden wir dann nicht viel zu viele Bits?“leni536: Das war kein Zufall, da steckt irgendwo ein Bug. Auf den ersten Blick scheint das Paket einfach
crypto.randomUUID()der JS-Laufzeit aufzurufen, und das sollte immer korrekt geseedet sein.Ein Bug in der Laufzeit wirkt extrem unwahrscheinlich, aber man weiß nie. Ich würde gern wissen, welche JS-Laufzeit hier verwendet wird.
jbverschoor: Die plausibelste Ursache ist, dass das Zufallszahlengenerator-Paket, von dem
uuidabhängt, kürzlich kompromittiert wurde und dadurch „zufällige“ Zahlen vorhersagbar geworden sind. Das könnte durch einen Supply-Chain-Angriff viele Krypto-, SSL- und Zahlungsprojekte gefährdet haben.uuid/src/rng.tsdas Zufallsarray zuconstgeändert. Dadurch teilen sich alle Aufrufe dasselbe Zufallsarray.Spätere Aufrufe aktualisieren den vorherigen Zufallscode, also viel Glück, wenn man damit etwas Wichtiges erzeugt hat. Früher hat der Code mit
slice()eine frische Kopie erzeugt. Vielleicht war das unbeabsichtigt, aber ich verstehe nicht, wie das durchgegangen ist – ein Test, der einfach zwei Zufallszahlen erzeugt und vergleicht, ob sie unterschiedlich sind, hätte doch schon fehlschlagen müssen.pif: Selbst mit einer hochwertigen Entropiequelle kann man „wahrscheinlich“ nicht in „garantiert“ verwandeln. Wenn man schwer zu erratende Werte braucht, sollte man sich an Kryptografie orientieren; wenn man garantierte Eindeutigkeit braucht, muss man sie selbst konstruieren.
athrowaway3z: Eine einfache Faustregel ist zu überlegen, ob man zusätzlich zu einem Zufallswert auch einen Zeitstempel in die ID packen kann. Meist lautet die Antwort ja, und UUIDv7 ist dann völlig ausreichend.
Wenn du das Problem so tief durchdacht hast, dass du selbst einen Beweis dafür schreiben könntest, dass Informationsleckage inakzeptabel ist, dann Glückwunsch. Dann ist dein System wahrscheinlich auch komplex und langsam genug, um einen starken kryptografischen Hash zu verwenden – oder, wenn du es bequemer willst, UUIDv5.
darqis: PostgreSQL 18 unterstützt uuidv7 nativ, und als Standard reicht
uniquemituuid7().tumdum_: Ein schlecht geseedeter Pseudozufallszahlengenerator.
serf: 1 zu 4,72 × 10²⁸, also ungefähr 1 zu 47,3 Oktillionen. Wenn das wirklich passiert ist, würde ich eher Race Conditions oder andere einfache Fehler vermuten, bevor ich einen Lottoschein kaufe.
evnix: Selbst wenn man die ganze Wahrscheinlichkeitsmathematik beiseitelässt: In der realen Welt, in der wir leben, können selbst die besten Hardware-Zufallszahlengeneratoren weniger zufällig sein, als man denkt.
Wo Sicherheit nicht kritisch ist, würde ich lieber auf etwas wie TSID oder auf uuidv7 wechseln, damit so etwas in der Praxis praktisch nicht mehr vorkommt. Das erscheint mir besser, als den Code mit Retries zu überkonstruieren.
jordiburgos: Ich hoffe nur, dass niemand
b6133fd6-70fe-4fe3-bed6-8ca8fc9386cdbenutzt. Ich habe in meiner DB nachgesehen – ist schon vergeben.16b55183-1697-496e-bc8a-854eb9aae0f3, und vermutlich noch mehr. Wenn hier alle ihre Listen posten, könnten wir dann nicht auf Duplikate prüfen?pyuser583: Ich frage mich, welche UUIDs heutzutage bevorzugt werden.
smokel: Ich habe schon oft Compiler, kosmische Strahlung, Quanteneffekte oder zumindest obscure Kernel-Bugs verantwortlich gemacht und später gemerkt, dass am Ende ich selbst der Bug war.
Kollisionen bei 15.000 Datensätzen sind so unwahrscheinlich, dass ich zuerst andere Ursachen vermuten würde: fehlerhafte Deduplizierung, erneut gesendete Requests, wiederverwendete Objekte, irreführende Logs oder Wiederverwendung von Identifikatoren in einem anderen Codepfad. Wenn du etwas mehr vom umgebenden Code zeigst, kann man gemeinsam genauer hinschauen.
wazoox: Mir ist das noch nicht passiert, aber vor zwei Tagen habe ich tief in einer laufenden PHP-Codebasis Folgendes gefunden: eine Funktion
createUUID(), die einen mitmd5(uniqid('', true))erzeugten Wert zurechtschnitt und im UUID-Format zusammenklebte.Ich weiß nicht, wie dieser Horror unser aller Halsschlagader bisher noch nicht erreicht und gebissen hat.
sedatk: In
uuidjs/uuidgibt es eine Warnung, dass auf Clients mit deterministischem Zufallszahlengenerator wie Googlebot doppelte UUIDs entstehen können.Für Apps, die davon ausgehen, dass clientseitig erzeugte UUIDs immer eindeutig sind, kann das ein Problem sein; empfohlen wird daher, auf Duplikate zu prüfen und sauber zu scheitern oder Schreiboperationen für Googlebot-Clients zu deaktivieren: https://github.com/uuidjs/uuid/commit/91805f665c38b691ac2cbd...
xyzzy123: In einem Linux-basierten verteilten System ist bei mir einmal ein längerer Lasttest wegen doppelter UUIDs fehlgeschlagen.
Nach langer Untersuchung stellte sich ein Kernel-Bug heraus, genauer eine Race Condition. Wenn auf einem Multiprozessorsystem zwei Prozesse gleichzeitig
/dev/randomlesen, können sie in sehr seltenen Fällen – etwa einmal pro Million – dieselben Bytes erhalten. Ich würde zuerst auf die Initialisierung des Zufallszahlengenerators schauen.baq: Klingt, als hätte die laufende VM sämtliche Entropie durch Virtualisierung weggefiltert.
glaslong: Ich sollte wohl ein paar Lava-Lampen kaufen.
0xfffafaCrash: Ich frage mich, ob die UUID im Frontend oder im Backend erzeugt wurde. Falls im Frontend, würde ich eher darauf wetten, dass nicht ein Entropieproblem vorliegt, sondern manipulierte Client-Seite oder Requests, über die eine bereits bekannte UUID eingeschleust wurde.
latentframe: Eine der gefährlichsten Aussagen im Engineering ist „statistisch unmöglich“. In ausreichender Größenordnung sind Extremfälle keine Theorie mehr, sondern Produktionsereignisse.
8organicbits: Ich habe letztes Jahr über eine echte Kollision geschrieben, inklusive der betreffenden Bibliothek: https://alexsci.com/blog/uuid-oops/
Damit UUIDs kollisionsresistent sind, müssen viele Bedingungen strikt eingehalten werden, und in diesem Fall scheint ein Problem beim Zufallszahlengenerator sehr wahrscheinlich.
nu11ptr: Am Ende ist es eben doch eine Frage der Entropiequelle. Deshalb erzeuge ich die ID immer in einer Schleife und führe dann das Insert aus. Wenn es eine Kollision gibt, kann man elegant damit umgehen.
sbuttgereit: „Technisch unmöglich“ ist es nicht. Technisch ist es sehr wohl möglich. Mit guter Zufälligkeit nur eben extrem, extrem selten, aber nichts an UUIDv4 verhindert technisch, dass doppelte Werte erzeugt werden.
beardyw: Vielleicht eine dumme Frage, aber könnte man nicht einfach ein Datum anfügen, meinetwegen auch nur in Hex mit Sekundenauflösung? Ein paar zusätzliche Bytes sollten doch ausreichen, damit das, was heute okay ist, auch in Zukunft noch okay bleibt.
mdavid626: Es gibt auch andere Erklärungen. Zum Beispiel könnte jemand manuell Requests oder die DB verändert haben.
radial_symmetry: Mir ist so etwas auch einmal passiert, und ich dachte schon, ich werde verrückt. Diese Kommentare zu lesen beruhigt mich.
NKosmatos: „Technisch unmöglich“ ist es nicht. Es ist nicht unmöglich, nur sehr, sehr unwahrscheinlich. Vielleicht solltest du Lotto oder Powerball spielen.
Immer wenn ich „improbable“ höre, muss ich an https://hitchhikers.fandom.com/wiki/Infinite_Improbability_D... denken.
sudb: Zum ersten Mal habe ich das Gefühl, dass es tatsächlich eine gute Idee war, in einem meiner Projekte CUID2 zu wählen: https://github.com/paralleldrive/cuid2