UUIDs in verteilten Systemen: IDs ohne Rückfrage
Über UUIDs wird meistens die falsche Frage gestellt. Sie lautet üblicherweise „welche Version nehmen wir denn nun” – dabei ist die interessantere Frage, warum man überhaupt eine braucht.
Denn eine Datenbank kann eindeutige Schlüssel längst selbst vergeben. Eine Sequenz, ein IDENTITY, ein AUTO_INCREMENT: fortlaufend, kompakt, sortiert, menschenlesbar. Für eine einzelne Anwendung mit einer einzelnen Datenbank gibt es wenig Grund, davon abzuweichen.
Der Grund kommt dazu, sobald das System nicht mehr aus einem Stück besteht.
Die Versionen, kurz
Vorweg die Landkarte. Seit RFC 9562 vom Mai 2024 – dem Nachfolger des alten RFC 4122 – gibt es im Wesentlichen diese Familien:
- v1 und v6 – Zeitstempel plus Knotenkennung, klassisch aus der MAC-Adresse. v6 ist das neu sortierte v1, bei dem die Zeit vorne steht.
- v3 und v5 – berechnet aus Namensraum und Name, per MD5 beziehungsweise SHA-1. Gleiche Eingabe, gleiche UUID – reproduzierbar und ohne Zufall.
- v4 – 122 Bit Zufall. Der Klassiker, und das, was die meisten Bibliotheken liefern, wenn man nichts weiter angibt.
- v7 – 48 Bit Unix-Zeitstempel in Millisekunden, vorne, gefolgt von Zufall. Die aktuelle Empfehlung für neue Schlüssel.
- v8 – bewusst freigelassen für eigene Formate.
Wer die Bitaufteilung im Detail sehen will, wirft eine UUID in uuid.rellit.de – dort wird sie zerlegt und erzeugt, samt Zeitstempel und Variante. Hier geht es um die Frage, wofür man das Ganze überhaupt einsetzt.
Der eigentliche Punkt: keine Rückfrage
Eine Sequenz hat eine unangenehme Eigenschaft: Sie braucht eine zentrale Instanz, die sie verwaltet. Wer eine ID will, muss fragen.
Solange alles an einer Datenbank hängt, fällt das nicht auf. Es fällt auf, sobald:
- mehrere Dienste Datensätze anlegen, die aufeinander verweisen,
- Daten in mehreren Regionen oder Rechenzentren entstehen,
- ein Client auch dann weiterarbeiten soll, wenn gerade keine Verbindung besteht,
- oder zwei getrennt gewachsene Systeme zusammengelegt werden.
In all diesen Fällen ist die zentrale Vergabestelle genau der Punkt, an dem es klemmt. Eine UUID löst das, indem jeder Beteiligte eine ID einfach selbst erzeugt – ohne Koordination, ohne Netzwerkzugriff, mit der Wahrscheinlichkeit einer Kollision als ausreichende Sicherheit.
Das klingt banal, hat aber Folgen, die weit über die Schlüsselvergabe hinausgehen.
Die ID existiert vor dem Datensatz
Wenn ich die ID selbst erzeuge, muss ich nicht mehr warten, bis die Datenbank mir verrät, wie der neue Datensatz heißt.
Ich kann einen ganzen Objektgraphen im Speicher aufbauen – Auftrag, Positionen, Verweise untereinander – und ihn anschließend in einem Rutsch schreiben. Ich kann Daten in einer Oberfläche anlegen, die noch gar nicht gespeichert sind, und trotzdem schon auf sie verweisen. Die Reihenfolge „erst speichern, dann Schlüssel erfahren, dann Verweise nachziehen” entfällt ersatzlos.
Wer schon einmal einen Import geschrieben hat, bei dem tausend Datensätze einzeln eingefügt werden mussten, nur um jeweils den erzeugten Schlüssel für die abhängigen Sätze zurückzulesen, weiß, was das wert ist.
Idempotenz, der unterschätzte Anwendungsfall
Für mich ist das hier das stärkste Argument.
Verteilte Systeme stellen selten genau einmal zu. Die üblichen Zusagen lauten „mindestens einmal” – und das heißt: Ein Aufruf läuft in ein Timeout, der Aufrufer weiß nicht, ob die Gegenseite ihn verarbeitet hat, und versucht es erneut. Bei einer Nachrichtenwarteschlange gilt dasselbe: Wird die Bestätigung nicht rechtzeitig gesetzt, kommt die Nachricht noch einmal.
Vergibt der Empfänger die ID, entsteht dabei ein zweiter Datensatz. Die Bestellung ist doppelt da, die Zahlung wurde zweimal gebucht.
Vergibt dagegen der Absender die ID, und zwar einmalig beim ersten Versuch, dann trägt der Wiederholungsversuch dieselbe ID wie das Original. Aus einem stillen Duplikat wird ein Schlüsselkonflikt – und der ist als solcher erkennbar und behandelbar: erkannt, verworfen, bestätigt, fertig. Genau das steckt hinter dem, was viele Schnittstellen als Idempotenzschlüssel anbieten.
Die Wiederholung ist damit nicht mehr gefährlich. Und das ist die Eigenschaft, auf der belastbare verteilte Verarbeitung überhaupt erst aufbaut.
Zusammenführen ohne Nummernkreis-Schlacht
Der dritte Fall tritt fast immer irgendwann ein: Zwei Bestände sollen in einen.
Zwei Mandanten werden zusammengelegt. Ein aufgekauftes System wird übernommen. Eine Außenstelle, die offline gearbeitet hat, synchronisiert ihre Daten. Mit fortlaufenden Nummern beginnt hier die Übung, die jeder kennt, der sie einmal machen musste: Nummernkreise abgrenzen, Offsets addieren, Fremdschlüssel umschreiben – und am Ende hoffen, dass nichts übersehen wurde.
Mit UUIDs entfällt der Schritt. Die Schlüssel waren von Anfang an über Systemgrenzen hinweg eindeutig, die Daten lassen sich zusammenschütten.
Ein Nebeneffekt, der oft als Sicherheitsvorteil verkauft wird: Fortlaufende Nummern verraten Geschäftszahlen. Wer Rechnung 1042 bekommt und einen Monat später Rechnung 1102, kann rechnen. Und wer eine id=1042 in einer URL sieht, probiert erfahrungsgemäß auch mal 1043.
Das ist ein realer Vorteil – aber bitte nicht als Schutzmaßnahme verstehen. RFC 9562 ist an der Stelle deutlich: UUIDs dürfen nicht als Zugriffsberechtigung behandelt werden, deren bloßer Besitz Zugriff gewährt. Eine schwer zu erratende ID ersetzt keine Berechtigungsprüfung, sie verzögert höchstens den Versuch.
Der Preis: der Index
So weit die guten Nachrichten. Jetzt die Rechnung.
Ein v4-Schlüssel ist reiner Zufall, und genau deshalb ein schlechter Nachbar für einen Index. Fortlaufende Schlüssel hängen neue Sätze immer rechts an: Es wird stets dieselbe Seite beschrieben, die liegt im Cache, alles ist schnell. Zufällige Schlüssel dagegen verteilen jeden Schreibzugriff über den gesamten Baum. Seiten müssen mitten im Bestand aufgeteilt werden, der Füllgrad sinkt, und statt einer heißen Seite braucht die Datenbank plötzlich einen großen Teil des Index im Speicher. Bei einem gruppierten Index – also dem, nach dem die Tabelle physisch sortiert liegt – schlägt das voll durch.
Genau dafür wurde v7 entworfen. Der Zeitstempel steht in den führenden Bits, neue Werte sind damit größer als alle vorherigen, und das Anhängen rechts funktioniert wieder. Dazu bekommt man den Erzeugungszeitpunkt geschenkt.
Die Speicherform ist dabei fast noch wichtiger als die Version: Eine UUID gehört als 16 Byte in ein binäres Feld, nicht als 36 Zeichen langer Text in eine Spalte. Das ist der Ratschlag mit dem größten Effekt, und er wirkt in jedem Index, der den Schlüssel mitführt.
Was bleibt
UUIDs sind kein Ersatz für fortlaufende Nummern, sondern eine Antwort auf eine andere Frage. Sie kosten Platz und, je nach Version und Datenbank, Schreibleistung. Dafür bekommt man die Unabhängigkeit von einer zentralen Vergabestelle – und damit Wiederholbarkeit, Zusammenführbarkeit und die Möglichkeit, Daten dort entstehen zu lassen, wo sie anfallen.
Für neue Schlüssel ist v7 die naheliegende Wahl. Wo der Erzeugungszeitpunkt nichts verloren hat, bleibt v4 richtig. Und wenn dieselbe Eingabe zuverlässig dieselbe ID ergeben soll, sind v5 und v8 die Werkzeuge, an die zu selten gedacht wird.
Zum Anschauen, Erzeugen und Auseinandernehmen: uuid.rellit.de.