- UUID v4 hat einen hohen Grad an Zufälligkeit und verursacht dadurch ineffiziente Indizes und übermäßiges I/O, was bei der Verwendung als Primärschlüssel in PostgreSQL zu Leistungseinbußen führt
- Durch zufällige Inserts kommt es häufig zu Page Splits und Indexfragmentierung, was zu größeren WAL-Logs und Schreibverzögerungen führt
- UUIDs sind 16 Byte groß und belegen damit doppelt so viel Platz wie
bigint, was die Cache-Trefferquote senkt und zu Speicherverschwendung führt
- Sie werden oft fälschlich als Sicherheitskennungen verstanden, aber laut RFC 4122 sind UUIDs kein Sicherheitsmechanismus zum Schutz vor Erraten
- Für neue Datenbanken wird empfohlen, ganzzahlige sequenzbasierte Schlüssel zu verwenden; falls unvermeidbar, sollte man zeitlich sortierbare UUID v7 einsetzen
Leistungsprobleme von UUID v4
- Datenbanken in PostgreSQL, die UUID-v4-Primärschlüssel verwenden, zeigen seit den letzten 10 Jahren konsistent Leistungseinbußen und übermäßiges I/O
- Bei UUID v4 werden 122 Bit zufällig erzeugt, wodurch eine Sortierung im Index nicht möglich ist
- Da Inserts nicht auf sequenziellen Seiten gespeichert werden, kommt es zu Random Access; auch bei Updates und Deletes sind ineffiziente Suchvorgänge nötig
- B-Tree-Indizes setzen sortierte Daten voraus, aber UUID v4 hat keine natürliche Reihenfolge, wodurch die Insert-Effizienz gering ist
- Jeder Insert wird auf eine beliebige Seite geschrieben, wodurch Splits in der Mitte von Seiten häufig auftreten
- Das führt zu Schreiblatenz und einem Anstieg des WAL
Struktur von UUIDs und Alternativen
- UUIDs sind 128-Bit-Identifikatoren (16 Byte) und werden in PostgreSQL als binärer
uuid-Typ gespeichert
- UUID v4 basiert auf Zufallsbits, UUID v7 enthält in den ersten 48 Bit einen Zeitstempel und ist dadurch indexfreundlicher
- In PostgreSQL 18 (geplant für 2025) soll UUID v7 standardmäßig unterstützt werden
- UUID v7 lässt sich zeitlich sortieren und verbessert dadurch Seitendichte und Cache-Effizienz
Warum UUIDs gewählt werden – und ihre Grenzen
- UUIDs werden verwendet, wenn in Multi-Client- oder Microservices-Umgebungen kollisionsfreie Identifikatoren erzeugt werden müssen
- Beispiel: gleichzeitige ID-Erzeugung in mehreren Datenbankinstanzen
- RFC 4122 weist jedoch ausdrücklich darauf hin, dass man nicht davon ausgehen sollte, dass UUIDs schwer zu erraten sind; als Sicherheitskennung sind sie daher ungeeignet
- Die Kollisionswahrscheinlichkeit liegt bei 50 % erst nach der Erzeugung von 2.71×10¹⁸ UUIDs; praktisch sind Kollisionen zwar unwahrscheinlich, aber die Leistungskosten sind hoch
Platz- und I/O-Ineffizienz von UUIDs
- UUIDs benötigen doppelt so viel Platz wie
bigint (8 Byte) und viermal so viel wie int (4 Byte)
- Bei großen Tabellen führt das zu mehr Speicherbedarf sowie längeren Backup- und Restore-Zeiten
- Ergebnisse eines Experiments zur Dichte von Indexseiten
- Integer-Index: 97.64%
- UUID-v4-Index: 79.06%
- UUID-v7-Index: 90.09%
- In einem Test von Cybertec wurden bei Index-Lookups mit UUID v4 8.5 Millionen zusätzliche Seitenzugriffe und 31229% mehr I/O gemessen
- Unter denselben Bedingungen hatte ein
bigint-Index 27,332 Buffer-Zugriffe, UUID v4 dagegen 8,562,960 Buffer-Zugriffe
Auswirkungen auf Cache und Speicher
- Aufgrund ihrer zufälligen Verteilung senken UUIDs die Cache-Hit-Rate des Buffer Cache
- Es müssen mehr Seiten in den Cache geladen werden, und benötigte Seiten werden häufiger verdrängt (Eviction)
- Die schlechtere Cache-Effizienz verursacht Query-Latenz und erhöht den Speicherverbrauch
- Um die Leistung zu erhalten, werden regelmäßige Index-Neuaufbauten (
REINDEX CONCURRENTLY) oder der Einsatz von pg_repack empfohlen
Maßnahmen zur Leistungsverbesserung
- Mehr Speicher: empfohlen werden RAM-Kapazitäten in Höhe des Vierfachen der Datenbankgröße (z. B. DB 25 GB → 128 GB RAM)
work_mem anpassen: Bei Sortieroperationen kann mehr zugewiesener Speicher die Leistung verbessern
- In Rails-Umgebungen kann über die Einstellung
implicit_order_column statt UUID ein sortierbares Feld wie created_at verwendet werden
- Mit dem
CLUSTER-Befehl lässt sich eine Tabelle anhand eines sortierbaren Feldes neu anordnen, allerdings ist dafür ein exklusiver Lock erforderlich
Empfehlung: Ganzzahlige Schlüssel und Sequenzen
- Für neue Datenbanken werden ganzzahlige, sequenzbasierte Schlüssel empfohlen
integer (4 Byte) bietet etwa 2 Milliarden Werte, bigint (8 Byte) deutlich mehr eindeutige Werte
- Für die meisten Business-Anwendungen reicht
integer aus; für größere Systeme ist bigint geeignet
- Ein realistischer Ersatz für UUID v4 ist UUID v7 oder die Erweiterung
sequential_uuids
Zusammenfassung
- UUID v4 führt aufgrund seiner Zufälligkeit zu ineffizienten Indizes, hohem I/O und geringer Cache-Effizienz
- Es ist nicht als Sicherheitskennung geeignet und verursacht erheblichen Platzverbrauch
- Sequenzbasierte Ganzzahlschlüssel sind für die meisten Anwendungen besser geeignet
- Wenn UUIDs unvermeidbar sind, sollte man zeitlich sortierbare UUID v7 wählen
- In PostgreSQL sollte man vermeiden,
gen_random_uuid() als Primärschlüssel zu verwenden
Noch keine Kommentare.