UUID v4 vs. v7 vs. ULID vs. Snowflake — welche ID wann?

Wer eine neue Tabelle anlegt, greift fast reflexhaft zur UUIDv4 als Primärschlüssel — sie ist überall verfügbar, eindeutig und einfach. Doch v4 hat in großen Tabellen ein hartnäckiges Performance-Problem, das mit UUIDv7 (RFC 9562, 2024), ULID oder Snowflake elegant gelöst wird. Dieser Artikel ordnet die vier Varianten ein und gibt eine pragmatische Empfehlung.

Warum überhaupt zufällige IDs?

Klassische auto-incrementierende Integer-IDs sind kompakt und sortierbar, haben aber zwei harte Probleme. Erstens leaken sie Geschäftsinformationen: wer /invoices/4711 abruft, weiß sofort, dass es ca. 4711 Rechnungen gibt — und kann durch Inkrementieren weitere Tickets, Bestellungen oder User-Profile entdecken. Zweitens skalieren sie schlecht über verteilte Systeme, weil sie eine zentrale Sequenz brauchen.

Zufällige oder pseudo-zufällige IDs lösen beide Probleme: sie sind nicht enumerierbar und können von jedem Knoten unabhängig erzeugt werden, ohne Kollisionsrisiko. Der Standard dafür ist seit Jahrzehnten die UUID — ein 128-Bit-Wert, der je nach Variante unterschiedliche Eigenschaften hat.

UUIDv4 — der Klassiker

UUIDv4 ist im Kern 122 Bit Zufall plus 6 Bit Versions- und Varianten-Marker. Erzeugt wird sie aus einem kryptografisch sicheren Zufallsgenerator. Die Kollisionswahrscheinlichkeit ist astronomisch niedrig — selbst bei einer Billion erzeugter IDs pro Sekunde über 100 Jahre würde man rein statistisch keine Doppelung erwarten.

Genau diese Zufälligkeit ist Stärke und Schwäche zugleich. Stärke: keine zentrale Koordination, kein Leak, kein Timestamp drin. Schwäche: in einem B-Tree-Index sind aufeinanderfolgende Einfügungen über die gesamte Index-Struktur verteilt — jeder Insert berührt eine andere Page, der Cache hilft kaum, der Index fragmentiert.

Das Index-Problem in der Praxis

Ein Beispiel: eine MySQL-Tabelle mit 100 Millionen Zeilen und UUIDv4 als Primärschlüssel hat einen InnoDB-B-Tree von mehreren GB. Beim Einfügen einer neuen Zeile landet sie irgendwo mitten im Index, eine Page muss gelesen, modifiziert und zurückgeschrieben werden. Bei zehntausend Inserts pro Sekunde sind das zehntausend zufällige Disk-Seeks (oder zumindest Cache-Misses) — die Schreibperformance bricht ein.

Bei einer monoton wachsenden ID (auto-increment, Timestamp-basiert, …) landen Inserts dagegen immer am Ende des Index, in derselben oder einer benachbarten Page. Der Cache trifft fast immer, der Index bleibt kompakt. Genau hier setzen die jüngeren ID-Schemata an.

UUIDv7 — Zeitstempel-Präfix nach RFC 9562

UUIDv7 wurde 2024 mit RFC 9562 standardisiert. Sie kombiniert die ersten 48 Bit als Unix-Timestamp in Millisekunden mit 74 Bit Zufall plus den üblichen 6 Bit Versions-Marker. Ergebnis: die ID ist zeitlich sortierbar, bleibt aber pro Millisekunde durch 2^74 mögliche Zufalls-Suffixe praktisch kollisionsfrei.

Für Datenbanken ist das ein Gamechanger: Einfügen erfolgt fast monoton steigend, der Index bleibt sequenziell, die Schreibperformance entspricht praktisch der von Integer-IDs. Gleichzeitig bleibt der Vorteil der Random-UUID erhalten — keine Enumerierbarkeit, keine zentrale Sequenz, Multi-Region-fähig. Postgres, MySQL und SQL Server haben seit 2024/25 native Unterstützung oder Extensions.

ULID — der inoffizielle Vorläufer

ULID (Universally Unique Lexicographically Sortable Identifier, 2016) hatte UUIDv7 lange vorweggenommen: 48 Bit Timestamp, 80 Bit Zufall, lexikografisch sortierbar in Crockford-Base32 (26 Zeichen statt 36 Zeichen Standard-UUID). Viele Sprachen und Datenbanken haben ULID-Libraries, lange bevor RFC 9562 fertig war.

Funktional ist ULID fast identisch mit UUIDv7. Unterschiede: kürzere String-Form (gut für URLs), Crockford-Base32 vermeidet visuell ähnliche Zeichen, aber es fehlt der formale RFC-Standard. Wer heute neu startet, sollte UUIDv7 nehmen — wer bereits ULID nutzt, hat keinen Grund zu migrieren.

Snowflake — Twitter, Discord, X

Snowflake-IDs sind 64-Bit-Integer (kein UUID-Format!): typisch 41 Bit Timestamp, 10 Bit Maschinen-ID, 12 Bit Sequenz-Counter pro Millisekunde. Damit passen sie in einen BIGINT, sind extrem kompakt, sortierbar nach Zeit und können auf bis zu 1.024 Knoten parallel ohne Koordination 4.096 IDs pro Millisekunde pro Knoten erzeugen.

Trade-off: Snowflake leakt den Zeitstempel sehr genau (und damit das Erstellungsdatum von Objekten — wie bei Twitter, wo das Erstellungsdatum eines Tweets direkt aus der ID rekonstruiert werden kann). Wer das nicht will, nimmt UUIDv7. Snowflake glänzt da, wo extreme Schreibrate und kompakte IDs zählen — und Implementierungen wie Sonyflake oder TSID erweitern das Konzept.

Entscheidungshilfe

Eine kompakte Faustregel:

  • UUIDv4: für kleine Tabellen, externe API-IDs, Tokens — überall wo Sortierung egal ist und maximale Zufälligkeit zählt.
  • UUIDv7: heutiger Default für neue Tabellen mit hohem Schreibvolumen. Standardisiert, weit unterstützt, monoton, kein Index-Schmerz.
  • ULID: wenn Sie schon Bestands-Code damit haben oder die kürzere String-Form (26 Zeichen) für URLs wichtig ist.
  • Snowflake: bei sehr hohen Schreibraten (Twitter, Discord, Trading), wenn 64-Bit-BIGINT statt 128-Bit-UUID viel Speicher spart und Timestamp-Leak akzeptabel ist.

Häufige Fragen

Kann ich von UUIDv4 auf UUIDv7 migrieren?

Bestehende IDs bleiben gültig — das UUID-Format ist abwärtskompatibel. Du kannst ab einem bestimmten Stichtag neue Inserts mit v7 anlegen und die alten v4-IDs unverändert lassen. Der Index profitiert dann zumindest für die neuen Einträge sofort. Eine echte Backfill-Migration ist meist nicht sinnvoll, weil alte UUIDs in externen Systemen verlinkt sind.

Ist UUIDv7 wirklich Multi-Region-sicher?

Ja — der RFC fordert eine Clock-Synchronisation auf Millisekunden-Niveau, was über NTP problemlos erreichbar ist. Sollten zwei Knoten gleichzeitig im selben Millisekunden-Fenster IDs erzeugen, sorgen die 74 Zufalls-Bits dafür, dass eine Kollision praktisch ausgeschlossen ist. Lediglich für eine globale Sortierreihenfolge braucht man weiterhin eine eindeutige Ordnung — die ist im UUID-Standard nicht garantiert.

Soll ich UUIDs als BINARY(16) oder als String speichern?

Performance: immer BINARY(16). Eine UUID als String (CHAR(36)) belegt 2,25x so viel Platz, lädt langsamer und macht Indexe größer. Postgres hat einen nativen uuid-Typ, MySQL und SQL Server brauchen BINARY(16) mit Hilfsfunktionen. Der einzige Grund für Strings: Debug-Komfort beim Loggen — aber das löst man besser mit einem hex/uuid-Display in den Tools.

Hinweis: Dieser Artikel ist ein praxisorientierter Überblick und ersetzt keine vollständige Performance-Analyse. Datenbank-Hersteller, Index-Strategien und Zugriffsmuster können Empfehlungen verschieben — vor großen Architektur-Entscheidungen immer Benchmarks im realen Workload fahren.

Kommentare