10 Punkte von GN⁺ 2025-12-16 | Noch keine Kommentare. | Auf WhatsApp teilen
  • 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.

Noch keine Kommentare.