PostgreSQL gehört zu den beliebtesten relationalen Datenbanksystemen weltweit und zeichnet sich durch seine Stabilität, umfangreiche Funktionen und ausgezeichnete Performance im Single-Node-Betrieb aus. Dennoch stößt selbst dieser bewährte Monolith mit steigenden Datenmengen und zunehmender Nutzerlast früher oder später an seine Grenzen. Besonders in Szenarien, bei denen hohe Skalierbarkeit, Verfügbarkeit und strikte Transaktionsgarantien erforderlich sind, reichen einfache PostgreSQL-Instanzen oft nicht mehr aus. Zunehmend wenden sich Entwickler daher Lösungen mit sharding-Mechanismen oder verteilten Datenbanken zu, die horizontal skalierbar sind und besser mit verteilten Workloads umgehen können. Doch Sharding ist nicht gleich verteiltes System – gerade hier liegen wesentliche Unterschiede, die für Datenbankarchitekturen grundlegend sind und oft missverstanden werden.
Sharding bezeichnet im Kern eine horizontale Datenaufteilung: Eine große Datenbank wird in mehrere kleinere Teile aufgeteilt, sogenannte Shards. Jeder Shard verwaltet dabei nur einen Teil der Daten, typischerweise basierend auf einem Schlüssel- oder Wertebereich. In vielen Lösungen, darunter auch populäre Erweiterungen für PostgreSQL wie Citus, laufen die einzelnen Shards auf unabhängigen PostgreSQL-Instanzen. Diese kommunizieren oftmals nicht direkt miteinander, sondern werden zentral koordiniert. Nutzeranfragen werden vorab analysiert und an die zuständigen Shards weitergeleitet, was die Verarbeitung und Speicherung der Daten verteilt.
Theoretisch ermöglicht Sharding somit Lineare Skalierbarkeit und Entlastung eines einzelnen Datenbank-Servers. Allerdings endet hier oft die Ähnlichkeit zu echten verteilten Datenbanksystemen. In einem verteilten System arbeiten Knoten eng zusammen und tauschen nicht nur Daten aus, sondern auch Statusinformationen über den aktuellen Zustand von Transaktionen und Sperren, sodass komplexe, konsistente globale Zustände möglich sind. In einem sharding-basierten Ansatz ohne solchen globalen Koordinator und Synchronisationsmechanismus fehlen jedoch zentrale Garantien über Gleichzeitigkeit und Isolation von Transaktionen, sobald sie mehrere Shards betreffen. Genau hier beginnt die Herausforderung: Relationale Datenbanken, allen voran PostgreSQL, garantieren ACID-Eigenschaften – Atomicity, Consistency, Isolation und Durability.
Diese Eigenschaften gewährleisten, dass Transaktionen entweder vollständig durchgeführt oder vollständig abgebrochen werden, dass der Datenbankzustand stets konsistent bleibt, dass parallele Transaktionen sich nicht gegenseitig beeinflussen und dass einmal geschriebene Daten dauerhaft gespeichert bleiben. Einzelne PostgreSQL-Instanzen erfüllen diese Anforderungen exzellent. Doch per Sharding verteilen sie die Last auf mehrere Knoten, die unabhängig voneinander agieren und deshalb keine globale Sicht auf den gesamten Zustand haben. Entsprechend gelten die ACID-Garantien dann nur noch lokal pro Shard. Citus als Beispiel bestätigt dieses Problem besonders gut.
Citus erweitert PostgreSQL um einen verteilten Koordinator, der Anfragen auf mehrere Shards verteilt, verwaltet aber nicht den globalen Snapshot, der für eine konsistente Sicht zeitgleich veränderter Daten über alle Shards hinweg nötig wäre. Werden Transaktionen nur innerhalb eines einzelnen Shards ausgeführt, entspricht das Verhalten dem eines klassischen PostgreSQL-Servers mit garantierter ACID-Konformität. Das Problem tritt aber bei Transaktionen auf, die Daten über mehrere Shards hinweg verändern. Hier bietet Citus zwar atomare Commit-Garantien mittels eines sogenannten Two-Phase-Commit-Protokolls (2PC), das heißt, Änderungen werden entweder an allen betroffenen Shards durchgeführt oder an keinem, doch Isolation und Sichtbarkeitsgarantien bleiben schwächer ausgeprägt. Die Transaktionen sind also nicht wirklich gleichzeitig isoliert voneinander, ohne dass zwischenzeitliche Zustände auftreten können, die von Anwendungen ungewollt wahrgenommen werden – sogenannte „intermediäre Zustände“.
Dieses Fehlverhalten lässt sich gut an einem einfachen Beispiel darstellen: Stellen Sie sich vor, Sie haben zwei Konten bei der Bank, ein Konto X und ein Konto Y, die jeweils auf unterschiedlichen Shards gespeichert werden. Nun soll eine Überweisung von 50 Euro von Konto X zu Konto Y stattfinden. Das Two-Phase-Commit garantiert, dass entweder beide Konten aktualisiert werden oder keine Änderung erfolgt. Allerdings besteht keine Garantie, dass ein Leseprozess während der Transaktion gleichzeitig die gewandelten Beträge auf beiden Konten sieht. Ein Beobachter könnte daher einen unlogischen Gesamtbetrag von 150 Euro sehen, obwohl er eigentlich 200 Euro erwarten würde.
Dieses Problem verhindert, dass Anwendungen die gleiche konsistente Sicht auf Daten erhalten, wie sie bei einem monolithisch betriebenen PostgreSQL-Server möglich wäre. Viele Entwickler tendieren dazu, Sharding-Lösungen wie Citus als „verteilte“ Datenbanken zu betrachten. Doch die fehlenden globalen Isolationsebenen und der Verzicht auf verteilte Schnappschüsse bedeuten, dass es sich technisch gesehen nicht um vollständig verteilte Systeme handelt. Diese sind vielmehr „verteilt“, aber nicht strikt konsistent im Sinne eines globalen, gemeinsam koordinierten Datenbank-Snapshots. Die Konsequenz ist eine schwächere Konsistenz, die in Form von „eventual consistency“ zum Vorschein kommt.
Möchte man diese Einschränkungen nicht hinnehmen, sind echte verteilte Datenbanken wie YDB, CockroachDB oder YugabyteDB interessant, die ACID-Garantien auch über mehrere Knoten hinweg liefern und somit eine einheitliche, konsistente Sicht auf Daten gewährleisten. Die Implementierung dieser verteilten Datenbanken beruht auf komplexen Algorithmen und Protokollen, die garantiert, dass alle Knoten eine Transaktion entweder gemeinsam bestätigen oder zurückrollen, und diese Zustandsänderungen gleichzeitig sichtbar werden. Natürlich sind diese Verfahren mit einem höheren Kommunikationsaufwand verbunden, der sich als zusätzliche Latenz in der Transaktion niederschlägt. Dies zeigt sich insbesondere bei der Anzahl der erforderlichen Round-Trip-Zeiten (RTTs) zwischen den Datenbank-Knoten. Während einfacher PostgreSQL in einem einzelnen Server so gut wie keine netzwerkbedingte Verzögerung aufweist, verursacht ein synchron replizierter Standby-Server mindestens eine RTT pro Transaktion.
Citus, der ein Two-Phase-Commit nutzt, benötigt zwei weitere RTTs, insgesamt also mindestens drei. Vollständig verteilte Systeme wie YDB benötigen noch mehr, da Sie zusätzliche Mechanismen zur Koordination globaler Zustände anwenden. Die gute Nachricht: In Rechenzentren mit geringer Latenz, innerhalb derselben Region oder sogar über wenige Availability Zones hinweg, betragen die RTTs nur wenige Millisekunden oder noch weniger. Die dadurch entstehenden zusätzlichen Verzögerungen liegen oft in akzeptablen Bereichen und sind eine lohnende Investition, wenn dadurch echte Konsistenz und robuste Transaktionsverarbeitung gewährleistet werden. Die Entscheidung muss individuell abgewogen werden: Leistung und Durchsatz gegen konsistente Verlässlichkeit.
Abschließend lässt sich sagen, dass PostgreSQL unverändert eine erstklassige Lösung für eine Vielzahl von Anwendungsszenarien ist, die keine extremen Anforderungen an Skalierbarkeit oder konsistente Verteilung stellen. Für Systeme allerdings, die über mehrere Datenbankknoten mit vollständigen ACID-Transaktionen arbeiten müssen, ist Sharding allein keine ausreichende Lösung. Die bestehende Hoffnung, mit Lösungen wie Citus eine transparente verteilte Architektur aufzubauen, muss zurückhaltend bewertet werden, besonders wenn Fehlertoleranz, Datenintegrität und Isolation höchste Priorität haben. Daher sollten Entwickler die Unterschiede zwischen Sharding als Lastverteilungsmethode und echten verteilten Datenbanksystemen genau verstehen, um fundierte Entscheidungen für ihre Systeme treffen zu können. Moderne verteilte Datenbanken bieten zunehmend bessere Leistung bei gleichzeitig vollständiger ACID-Konformität und sind eine verlässliche Alternative, wenn PostgreSQL an seine Grenzen stößt.