- In einem Kubernetes-basierten PostgreSQL-Cluster kann sich bei Netzwerkausfällen Replication Lag ansammeln, wodurch ein sicheres Failover unmöglich wird. Beschrieben wird ein Ansatz, um diese strukturelle Schwäche zu beheben
- Die bisherige Architektur priorisierte Availability vor Durability: Während der Primary weiter Schreibvorgänge annahm, fielen die Replikate immer weiter zurück, bis es keinen Kandidaten mehr gab, der ohne Datenverlust hätte befördert werden können
- Als Lösung wurde für Failover-Kandidaten synchronous replication eingeführt und mit dem Open-Source-HA-Manager Patroni orchestriert
- In einem hybriden Replikationsmodell nehmen nur die Standby-Knoten im Leader-Pool an der synchronen Replikation teil, während Read Replicas asynchron bleiben, um Durability und Latenz auszubalancieren
- Trotz Leistungskosten wie 53 % höherer Schreiblatenz bei
remote_applywurde durch die Validierung von fünf Ausfallszenarien ein sicheres automatisches Failover erreicht
Das im Game Day aufgedeckte Problem
- Datadog führt regelmäßig Game Days durch, um Schwachstellen in Systemen und Prozessen proaktiv zu finden. Dabei wird die Plattform absichtlich belastet, um das Verhalten unter realen Bedingungen zu verstehen
- Bei einem Game Day wurde in der Staging-Umgebung ein Availability-Zone-(AZ)-Ausfall simuliert, der Netzwerklatenz verursachte und Schwächen in der PostgreSQL-Architektur offenlegte
- Die Primary-/Writer-Knoten mehrerer Kubernetes-basierter PostgreSQL-Cluster liefen in der betroffenen AZ
- Durch den starken Anstieg der Netzwerklatenz konnte die Primary nicht mehr stabil mit den Replikaten kommunizieren, der Replication Lag nahm zu → Schreibvorgänge stauten sich → Anwendungen lieferten veraltete Daten aus
- Es gab kein ausreichend aktuelles Replikat, daher war ein Failover nicht sicher und der Cluster kam faktisch zum Stillstand
- Unter normalen Bedingungen funktionierte diese Architektur gut, aber bei bestimmten Netzwerkstörungen priorisierte sie Availability vor Durability, sodass es keinen sicheren Wiederherstellungspfad gab
- Die Primary nahm weiter Schreibvorgänge an, obwohl die Replikation verzögert war, wodurch der Replication Lag weiter anwuchs und die Replikate noch weiter zurückfielen
- Dadurch konnte kein Failover-Kandidat ohne Risiko von Datenverlust befördert werden; die einzige Option war, auf nachlassende Latenz und das Aufholen der Replikate zu warten
- Ziel war es, Failover automatisch und sicher zu machen, ohne die Performance-Eigenschaften von PostgreSQL stärker als nötig zu beeinträchtigen
Referenzarchitektur: PostgreSQL auf Kubernetes
- Der Kubernetes-basierte PostgreSQL-Cluster besteht aus zwei Pools: Leader Pool und Read Replica Pool. PostgreSQL ist ein Single-Writer-System
- Durch die Trennung von Lese- und Schreibzugriffen lässt sich die Leseleistung ohne zusätzliche Last auf den Leader skalieren, während die Schreiblatenz vorhersehbar und stabil bleibt
- Der Leader Pool besteht aus einem einzelnen aktiven Writer-Knoten, der alle Schreibvorgänge verarbeitet, und zwei Standby-Knoten
- Die Standbys verarbeiten keinen Anwendungstraffic, können aber bei einem Leader-Ausfall befördert werden
- Der Read Replica Pool besteht aus mehreren Knoten für schreibgeschützten Traffic. Er ist für Leseskalierung und Query-Isolation optimiert und bewusst von Failover ausgeschlossen
Die Rolle von Patroni und ZooKeeper
- Patroni verwaltet Replikation, Failover und Leader Election und verwendet ZooKeeper als Distributed Configuration Store (DCS)
- ZooKeeper speichert Metadaten wie den aktuellen Leader-Key/-Lock, die Cluster-Konfiguration und den Replikationsstatus jedes Members, etwa den neuesten LSN
- Patroni trifft auf Basis dieser Informationen konservative Entscheidungen über Promotion und Demotion und priorisiert Datenkonsistenz vor aggressivem Failover
- Wenn ein neuer Knoten dem Cluster beitritt, prüft er zuerst in ZooKeeper, ob bereits ein Leader existiert
- Gibt es keinen Leader, versucht er durch Anlegen eines temporären znode den Leader-Key zu erhalten; ZooKeeper stellt sicher, dass nur ein einzelner Knoten den Key erhalten kann und verhindert so mehrere Primaries
- Existiert bereits ein Leader, konfiguriert sich der Knoten als Replikat und startet Streaming Replication
- Bei einer Network Partition kann ein Replikat, das die Verbindung zum Leader oder zu ZooKeeper verliert, den Clusterstatus nicht mehr verifizieren; Patroni pausiert oder stuft diesen Knoten herab
- Verliert der Leader die Verbindung, sorgt Patroni zusammen mit ZooKeeper dafür, dass nur ein qualifiziertes Standby den Leader-Lock erhält, und gewährleistet so kontrolliertes Failover auch bei partiellen Netzwerkausfällen
- Nach Wiederherstellung der Verbindung stuft sich der bisherige Leader selbst zum Standby herab, wenn er den Leader-Lock nicht erneut erwerben kann, um Split Brain zu verhindern
Warum sicheres Failover unmöglich war
- Im Single-Writer-Modell wählt Patroni bei einem Ausfall aus den gesunden Standbys einen neuen Leader
- Um Datenverlust zu verhindern, führt Patroni vor einer Promotion Sicherheitsprüfungen durch. Entscheidend ist, ob der Replication Lag innerhalb des Schwellenwerts
maximum_lag_on_failoverliegt- Wenn ein Standby hinter dem Leader zurückliegt, kann eine Promotion zu fehlenden oder inkonsistenten Daten führen
- Beim Game Day verlor die Primary die Verbindung, und der Replication Lag aller Standbys überschritt den Schwellenwert, weshalb Patroni das Failover korrekterweise verweigerte
- Dass der Cluster ohne sicheren schreibfähigen Primary zurückblieb, lag also nicht an Patroni, sondern daran, dass es keinen sicheren Promotion-Kandidaten gab
Zwei Modi der Streaming Replication
- Bei Streaming Replication sendet der Leader fortlaufend den Write-Ahead Log (WAL) mit allen Änderungen an die Replikate, die den WAL lokal anwenden, um synchron zu bleiben
-
Asynchrone Replikation (Standard)
- Der Leader wartet vor dem Commit einer Transaktion nicht auf eine Bestätigung der Replikate
- Minimale Schreiblatenz, hoher Durchsatz
- Fällt der Leader jedoch aus, können Transaktionen verloren gehen, die auf der Primary bereits committet, aber noch nicht repliziert wurden
-
Synchrone Replikation
- Der Leader wartet, bevor er dem Client antwortet, auf die Bestätigung von mindestens einem Replikat
- Dadurch sinkt das Risiko deutlich, dass mindestens ein Replikat zu weit zurückliegt, und es wird erst geantwortet, wenn eine committete Transaktion auch auf einem anderen Knoten vorhanden ist, was stärkere Durability bietet
- Ein Failover-Kandidat ist dadurch mit hoher Wahrscheinlichkeit aktuell genug, um ohne Risiko einer Datenverzweigung befördert zu werden
Hybride Replikationskonfiguration
- Um Durability, Latenz und Durchsatz auszubalancieren, wurde ein hybrides Replikationsmodell eingeführt
- Standby-Knoten im Leader Pool nehmen an synchroner Replikation teil; der Leader committet Schreibvorgänge erst nach Bestätigung durch den zugewiesenen synchronen Standby
- Read Replicas bleiben asynchron, da sie nur Read-only-Traffic verarbeiten und nicht als Failover-Ziel dienen, wodurch die Replikationslast im Leader Pool begrenzt bleibt
- So gelten die strengen Durability-Garantien nur für Failover-Kandidaten, ohne allen Read Replicas dieselben Latenzkosten aufzuerlegen
Tuning von PostgreSQL und Patroni für sicheres Failover
- Zur Aktivierung synchroner Replikation wurden Parameter sowohl in PostgreSQL als auch in Patroni angepasst
-
Wichtige angepasste Parameter
synchronous_mode: aktiviert synchrone Replikation in Patroni; beitrueerfolgt Commit nach Bestätigung synchroner Standbys gemäßsynchronous_node_count(Standard false → true, von Patroni verwaltet, erforderlich)synchronous_node_count: Anzahl synchroner Standby-Knoten; wird zur Erzeugung der Listesynchronous_standby_namesverwendet (Standard 1 → 1, von Patroni verwaltet, optional)synchronous_mode_strict: erzwingt strikten synchronen Modus; beitrueund ohne verfügbares Replikat werden Schreibvorgänge blockiert, statt auf asynchron umzuschalten (Standard false → true, von Patroni verwaltet, optional)synchronous_commit: PostgreSQL-Einstellung für Commit-Durability (Standard on → remote_apply, von PostgreSQL verwaltet, optional)
- Nach der Umstellung sendet der Leader eine Transaktionsantwort erst dann an den Client, wenn der synchrone Standby den Empfang und die Anwendung der Daten bestätigt hat
Balance zwischen Durability und Latenz
- Synchrone Replikation verbessert die Durability, erhöht aber die Schreiblatenz, weil der Leader auf Bestätigungen synchroner Standbys wartet; unter Dauerlast kann das auch den Durchsatz beeinflussen
- Die Performance-Auswirkungen hängen von der Zahl synchroner Standbys (
synchronous_node_count) und vom übersynchronous_commitgewählten Durability-Niveau ab -
Trade-offs je nach Durability-Stufe von
synchronous_commitremote_apply: wartet, bis das Replikat den WAL geschrieben, geflusht und angewendet hat; stärkste Konsistenz, höchste Latenzon(internremote_flush): wartet, bis das Replikat den WAL auf die Festplatte geflusht hat; starke Durability, aber auf dem Standby noch nicht lesbarremote_write: wartet, bis der WAL den OS-Cache des Replikats erreicht hat, nicht die Festplatte; geringere Latenz, aber anfällig bei OS-Crashslocal: Commit nach lokalem Disk-Flush ohne Warten auf Standbys; keine node-übergreifende Durabilityoff: Commit vor lokalem WAL-Flush; geringste Latenz, höchstes Risiko für Datenverlust
Performance-Benchmarking synchroner Replikation
- Da bei synchroner Replikation jedes Commit auf die Bestätigung mindestens eines Standbys wartet, erhöht sie die Latenz. Um die Auswirkungen zu quantifizieren, wurde mit dem PostgreSQL-Standard-Lasttesttool pgbench benchmarked (Patroni-Version 3.2.1)
- Verwendet wurde die TPC-B-Transaktionssuite, die einen einfachen Lese-/Schreib-Mix simuliert. Gemessen wurden zwei Kennzahlen
- Durchschnittliche Latenz: durchschnittliche Verarbeitungszeit pro Transaktion (ms)
- Transaktionen pro Sekunde (tps): Anzahl abgeschlossener Transaktionen ohne Verbindungsaufbauzeit
-
Testparameter
- Variiert wurden Scale Factor (Datenbankgröße), Zahl der Clients (gleichzeitiger Nutzer-Traffic), Zahl der Threads (CPU/Parallelität) und Zahl der Transaktionen (Workload-Intensität), um produktionsähnliche Bedingungen nachzubilden
- Synchrone Replikation im Quorum-Commit-Modus wurde in diesem Benchmark nicht getestet
-
Benchmark-Ergebnisse
- Zunahme der Schreiblatenz:
remote_apply53 %,on46 %,remote_write38 %,local32 % - Rückgang des Durchsatzes (tps):
remote_apply34 %,on31 %,remote_write27 %,local23 % remote_applywartete bis zur WAL-Wiedergabe und -Anwendung auf dem Replikat und zeigte deshalb durchgängig die höchste Latenz und den niedrigsten Durchsatz, war aber wegen der stärksten Konsistenz am besten für sicheres Failover geeignet
- Zunahme der Schreiblatenz:
-
Einsatz in Produktion
- Nach dem Benchmarking wurde
remote_applyauf mehrere Cluster mit hoher Schreiblast ausgerollt; auch unter anhaltender Produktionslast gab es auf Anwendungsebene keine signifikanten Auswirkungen auf Schreiblatenz oder Durchsatz - Zur Risikominimierung wurde schrittweise nach Rechenzentrum und Workload-Tier ausgerollt, mit Bake-in-Phasen und kontinuierlichem Monitoring zwischen den Stufen
- Beispiel: Ein Workload zur Verarbeitung von Ressourcen mit hohem Durchsatz lief nach Aktivierung synchroner Replikation trotz erhöhter DB-Schreiblatenz ohne zusätzliche Verarbeitungsverzögerung oder nachgelagerte Backlogs weiter
synchronous_commitkann ohne Downtime mitpatronictl edit-configsofort angepasst werden und bietet so Flexibilität, die Commit-Durability für extrem durchsatzstarke Workloads schnell zu reduzieren
- Nach dem Benchmarking wurde
Validierung des Failovers anhand von Ausfallszenarien
- Es wurde geprüft, ob synchrone Replikation und strikte Failover-Kontrolle Datenintegrität schützen, Split Brain verhindern und automatische Wiederherstellung sicherstellen
-
Szenario 1: Verlust eines synchronen Standbys
- Fällt ein synchroner Standby aus, versucht Patroni, einen anderen geeigneten Standby zuzuweisen, damit synchrone Replikation erhalten bleibt
- Patroni auf dem Leader-Knoten erkennt über
pg_stat_replicationunterbrochene, blockierte oder verzögerte Streaming-Verbindungen und verfolgt die Membership der Replikate über ZooKeeper - Es berechnet die Liste gesunder, geeigneter Streaming-Replikate neu und aktualisiert
synchronous_standby_namesgemäßsynchronous_node_count, sodass der Cluster weiter mit aktivierter synchroner Replikation läuft
-
Szenario 2: Verlust aller synchronen Standbys
- Das Verhalten hängt vom Wert von
synchronous_mode_strictab -
Nicht-strikter Modus: Schreibverfügbarkeit hat Vorrang
- Patroni leert
synchronous_standby_namesund deaktiviert synchrone Replikation vorübergehend; bis wieder ein gesundes Replikat beitritt, schaltet der Leader auf asynchron um und erlaubt weiter Schreibvorgänge
- Patroni leert
-
Strikter Modus: Schreibvorgänge werden aus Sicherheitsgründen blockiert
- Patroni setzt
synchronous_standby_namesauf*; auch ohne expliziten synchronen Standby akzeptiert PostgreSQL Schreibtransaktionen und committet sie lokal, blockiert aber die Antwort an den Client, bis ein Replikat den WAL bestätigt - Sobald ein geeignetes Replikat wieder verbunden ist und synchron replizieren kann, weist Patroni ihm die Rolle des synchronen Standbys zu
- Patroni setzt
- Das Verhalten hängt vom Wert von
-
Szenario 3: Alle Standby- und Replikat-Knoten nicht verfügbar
- Sind alle Replikate nicht verfügbar und
synchronous_mode_strict = true, hält PostgreSQL Transaktionsbestätigungen zurück, bis mindestens ein geeignetes Replikat zurückkehrt - Die Datenkonsistenz bleibt erhalten, aber auf Anwendungsebene entsteht vorübergehend Schreibunfähigkeit
- Sind alle Replikate nicht verfügbar und
-
Szenario 4: Leader-Ausfall während eines synchronen Commits
- Ein Edge Case, bei dem der Leader auf die Bestätigung des synchronen Standbys wartet und vor deren Eingang ausfällt
- Häufige Ursachen: Transaktionsabbruch durch den Client während des Commits, Crash oder Beendigung des PostgreSQL-Prozesses auf dem Leader, Network Partition während der Commit-Phase
- Wenn PostgreSQL den WAL lokal geflusht, aber nicht erfolgreich an den Standby repliziert hat, erscheint die Transaktion mangels Bestätigung nicht auf dem Replikat
- Stürzt der Leader ab, bevor der WAL an den synchronen Standby repliziert ist, und wird genau dieser Standby befördert, kann die Transaktion verloren gehen und die Historie des alten Leaders vom neuen Primary abzweigen
- Der bisherige Leader verwendet pg_rewind, um den Punkt der Timeline-Abzweigung zu ermitteln und sein Datenverzeichnis auf die Timeline des neuen Primary zurückzusetzen, verwirft dabei nicht replizierte lokale Änderungen und tritt wieder als Standby bei
- Dieses Verhalten ist das Ergebnis der internen Behandlung synchroner Commits in PostgreSQL, nicht von Patroni, und unterstreicht die Notwendigkeit sorgfältigen Tunings und Monitorings von Quorum-Einstellungen und Bestätigungsmechanismen
-
Szenario 5: ZooKeeper nicht verfügbar
- Ist ZooKeeper nicht verfügbar, kann Patroni weder Leadership verifizieren noch einen neuen Leader wählen und wechselt deshalb in einen konservativen Modus, um Dateninkonsistenzen zu vermeiden
-
Failsafe-Modus deaktiviert
- Selbst wenn ZooKeeper nicht erreichbar ist, laufen Schreibvorgänge weiter, solange der Leader erreichbar ist und alle Knoten gesund sind, jedoch nur bis zum Ablauf der TTL des Leader-Locks
- Nach Ablauf des Intervalls zur Aktualisierung des Leader-Keys und ohne erfolgreiche Lock-Erneuerung stuft Patroni den Leader herab und schaltet den Cluster in den Read-only-Modus
-
Failsafe-Modus aktiviert
- Verliert der Leader die Verbindung zu ZooKeeper, prüft Patroni über die REST API fortlaufend, ob alle Cluster-Member erreichbar sind
- Schreibvorgänge laufen nur weiter, wenn alle Member erreichbar sind; andernfalls erfolgt eine Herabstufung in Read-only
Manuelles Failover und Switchover bei synchroner Replikation
- Neben automatischem Failover und Switchover auf Basis von Health Checks und ZooKeeper-Koordination unterstützt Patroni auch manuelle Operationen per
patronictl. Bei aktivierter synchroner Replikation sind jedoch nicht alle Standbys gültige Kandidaten, daher greifen Guardrails zum Schutz der Datenintegrität -
Failover auf ein asynchrones Standby
patronictl failover: schlägt fehl, wenn der ausgewählte Knoten asynchron istpatronictl switchover: schlägt fehl, wenn der ausgewählte Knoten asynchron ist- Ein erzwungenes Failover auf einen asynchronen Knoten würde bei aktiver synchroner Replikation die Durability-Garantien umgehen und könnte Datenverlust verursachen
-
Ziel ist ein synchroner Standby
patronictl failover: erfolgreich, der Leader wechselt auf den synchronen Standbypatronictl switchover: erfolgreich, mit sauberer Übergabe zwischen Leader und synchronem Standby
- Nach Prüfung des Verhaltens in verschiedenen
synchronous_commit-Modi und der Patroni-Guardrails wurde synchrone Replikation in Produktionsclustern mit hoher Schreiblast, hoher Leselast und gemischten Workloads aktiviert, ohne messbare Auswirkungen auf Latenz und Durchsatz - Bei Problemen kann mit der Einstellung
synchronous_mode: falseohne Downtime sicher auf asynchrone Replikation zurückgeschaltet werden
Warum DRBD nicht gewählt wurde
- Bei der Bewertung von Hochverfügbarkeit wurde auch das Block-Level-Replikationssystem DRBD (Distributed Replicated Block Device) betrachtet, das das gesamte Volume inklusive PostgreSQL-Datenverzeichnis und WAL-Dateien zwischen Servern spiegelt und so nahezu in Echtzeit Standby-Replikate erzeugt
- DRBD kann geringere Latenz als die eingebaute Streaming Replication von PostgreSQL bieten, würde aber einen erheblichen Architekturwechsel erfordern, einschließlich neuer Infrastruktur, Monitoring und Betriebs-Playbooks
- Angesichts der ausgereiften Kubernetes-basierten Umgebung und der feingranularen Steuerung durch synchrone Replikation in PostgreSQL fiel die Wahl auf Replikation auf Datenbankebene, da sie bessere Sichtbarkeit, Flexibilität und operative Zuverlässigkeit bietet
Monitoring synchroner Replikation
- Nach Aktivierung synchroner Replikation wurden Replikationsstatus und Failover-Bereitschaft eng überwacht; besonders zwei Signale trugen zur Stabilität im großen Maßstab bei
-
SyncRep-Wait-Events
- Sie treten auf, wenn die Primary vor Abschluss eines Commits und Rückgabe des Status auf die Bestätigung des synchronen Standbys wartet. Ein gewisses Maß ist normal, aber lange oder häufige Waits deuten auf Performance-Probleme des Replikats oder Netzwerklatenz zwischen den Knoten hin
- Warum wichtig: Lange Waits erhöhen die Schreiblatenz und senken den Durchsatz
- Beobachtet werden Dauer und Häufigkeit der Wait-Events
SyncRepundWalSenderWaitForReply, erfasst in der Datadog-Metrikpostgresql.activity.waits, gefiltert nach dem Tagwait_event:SyncRep(intern per Abfrage der Tabellepg_stat_activity)
-
Kein synchroner Standby erkannt
- Wenn Patroni über längere Zeit keinen gesunden synchronen Standby erkennt, verliert der Cluster die Fähigkeit zu sicherem Failover
- Warum wichtig: Ohne synchronen Standby ist der Cluster bei einem Failover anfällig für Datenverlust
- Alarmkriterium: Wenn
patroni_sync_standbydauerhaft leer bleibt, wird ein Health Alert für Hochverfügbarkeit (HA) ausgelöst; die Daten stammen aus OpenMetrics, eine native Datadog-Integration gibt es dafür nicht
- Synchrone Replikation verbessert die Durability, kann aber Availability und Performance verschlechtern, wenn Replikate fehlerhaft oder nicht erreichbar sind; Monitoring von Wait Times und Standby-Verfügbarkeit ist entscheidend, um unter Last Availability und Performance zu erhalten
Sichere Failover-Fähigkeit von Anfang an mitdenken
- Der simulierte AZ-Ausfall legte eine kritische Schwäche in der PostgreSQL-Architektur offen: Weil Replikate hinter dem Leader zurückfielen, blieb nur die Wahl zwischen dem Warten auf die Behebung der Netzwerkstörung oder dem Inkaufnehmen einer Datenverzweigung, ein in Produktion nicht akzeptabler Trade-off
- Durch Einführung Patroni-basierter synchroner Replikation und Tuning von Durability und Latenz wurde Failover auch unter verschlechterten Netzwerkbedingungen möglich und sicher. Benchmarking und wiederholte Ausfallsimulationen bestätigten vorhersehbare Wiederherstellung ohne spürbare Performance-Einbußen im großen Maßstab
- Indem synchrone Replikation bei Ausfällen Schreibvorgänge blockiert, macht sie Fehler für vorgelagerte Services explizit sichtbar. Anders als bei asynchroner Replikation gehen Schreibvorgänge nicht stillschweigend verloren, sondern können durch Retry- oder Queueing-Strategien behandelt werden, wodurch der Fehlermodus sichtbarer und besser beherrschbar wird
- Künftig werden quorum-basierte Commit-Modi und tiefere Observability für den Replikationsstatus weiter untersucht
Noch keine Kommentare.