PostgreSQL Replication Cluster mit BaRMan, RepMgr und KeepAliveD

Wir von eXirius liefern und betreiben schon seit vielen Jahren PostgreSQL als Appliance-Lösung für verschiedenste Anwendungsbereiche. Z.B. verwendet Logistik-Software unsere Appliance ebenso wie GIS- und andere Anwendungen. Die Robustheit des PostgreSQL-Datenbanksystems bildet dabei, ebenso wie sein enormer Funktionsumfang und seine Erweiterbarkeit, eine perfekte Grundlage für einen zuverlässigen Betrieb.

Ergänzende Softwarekomponenten, u.a. der Backup and Recovery Manager “BaRMan”, sorgen dafür, dass die Daten immer noch da sind bzw. auf einfache und zuverlässige Art wieder hergestellt werden können – auch wenn sie “mal weg wären” – z.B. weil sie ein Anwender versehentlich oder vorsätzlich zerstört hat bzw. der PostgreSQL-Server “kaputt” ist. Letzteres ist zum Glück ein Fall, der so gut wie nie eintritt, aber Vorsorgen ist bekanntlich besser als Nachsehen. Wie ein gelegentlicher Blick in die Community-Kanäle zeigt, ist die Wahrscheinlichkeit für derart ernste Probleme bei PostgreSQL zwar verschwindend gering, aber leider doch nicht NULL ;-). Insbesondere kann es beim Verzicht auf vorbeugende Maßnahmen passieren, dass die unmittelbar vor einem Crash gespeicherten Daten verloren gehen. Deswegen muss man dafür sorgen, dass die vorgegebene RPO, also die im Fehlerfall maximal tolerierbare Menge an verloren gegangenen Daten, strikt eingehalten wird. BaRMan und PostgreSQL lassen sich so konfigurieren und betreiben, dass man sehr nahe an den idealen und wünschenswerten Wert “RPO=0” (also überhaupt keine Datenverluste) heran kommen kann. Dazu später mehr.

Der BaRMan erzeugt und verwaltet physische Backups, also konsistente 1:1-Kopien der Datendateien, in denen sich die gesamten in der PostgreSQL-Datenbank gespeicherten Informationen befinden. Daneben gibt es noch logische Backups, häufig auch Dumpfiles genannt. Diese enthalten alle (SQL-) Befehle, die notwendig sind, um die zum Zeitpunkt der Backuperstellung vorhandenen Daten wieder herstellen zu können. Die Wiederherstellung aus einem vorhandenen Backup wird auch Restore genannt.

Auch bei physischen Backups ist es so, dass aus dem Backup zunächst einmal nur der Stand der Daten zum Backupzeitpunkt restauriert werden kann. Im Unterschied zur Wiederherstellung aus logischen Backups kann bei aus physischen Backups restaurierten Datenbanken anschließend aber noch ein Recovery durchgeführt werden. Dabei werden, sofern vorhanden, die in den so genannten Write ahead logs (WALs) gespeicherten Änderungsinformationen auf die wiederhergestellten Datendateien angewendet. Somit kann der Stand der PostgreSQL-“Datenbank” (genauer: des PostgreSQL- Clusters, siehe unten) zu einem späteren (als dem Backup-) Zeitpunkt, z.B. auch “unmittelbar vor einem Crash”, erzeugt werden. Man spricht von einem Point-in-time recovery (PITR) bzw. einem Crash recovery.

Schematische Darstellung von Restore und Recovery

Beide Backuparten haben individuelle Vor- und Nachteile. Daher sollte bei einem produktiven System stets zweigleisig gefahren und sowohl logische als auch physische Backups angefertigt werden. Für einen gewissen, vorab zu definierenden Zeitraum sollten auch die WALs aufbewahrt werden, so dass sie für ein Recovery verfügbar sind. BaRMan verwaltet aus diesem Grund nicht nur physische Backups, sondern auch WALs und ermöglicht es auf sehr einfache Weise, sowohl Restores als auch beide Arten von Recoverys durchzuführen. Wenn man über ausreichend Platz verfügt, kann das Restore/Recovery in ein weiteres PostgreSQL-System erfolgen, so dass die ursprüngliche Datenbank davon unbeeinflusst bleibt.

Sowohl das Erstellen eines logischen Backups als auch die Wiederherstellung aus einem solchen benötigen tendenziell mehr Zeit als physische Backups/Restores. U.a. deswegen und weil auf der Basis von logischen Backups kein (Crash- oder Point-in-time-) Recovery möglich ist, ist es keine gute Idee, auf physische Backups sowie die Aufbewahrung der WALs vollständig zu verzichten und an deren Stelle ausschließlich, z.B. in kurzen Zeitabständen, logische Backups anzufertigen.

Schematische Darstellung von Point in time-Recovery

Das Zurückspielen der Daten aus einem (logischen oder physischen) Backup ist zwar technisch für einen erfahrenen PostgreSQL-Administrator nur eine Fingerübung, führt aber i.d.R. zu mehr oder weniger langen Ausfallzeiten, in denen die Datenbank und die betroffenen Systeme den Anwendern folglich auch nicht zur Verfügung stehen. Viele Systeme verwalten Datenmengen, bei denen es kein großes Problem ist, die so genannte RTO, also die maximal tolerierbare Wiederherstellungszeit, die ähnlich der RPO durch die Anforderungen an die Verfügbarkeit der Systeme vorgegeben wird, einhalten zu können. Es gibt aber auch Datenbanken und Anwendungen, deren RTO-Wert nahe 0 liegt und/oder deren Datenvolumina so umfangreich sind, dass eine Wiederherstellung in einer akzeptablen Zeitspanne unmöglich ist. “Schlimmstenfalls”, jedenfalls aus der Perspektive des Datenbank-Administrators, kommt beides, also RTO nahe 0 und große Datenmengen, zusammen.

Um dieses Problem zu lösen, bietet PostgreSQL out-of-the-box die Möglichkeit, alle Änderungen an der Datenbank transparent und instantan auch an weitere PostgreSQL-Server zu senden. Diese nehmen die Änderungsinformationen entgegen und wenden sie auf ihren Datenbestand an. Diese so genannte “Replikation” gibt es in unterschiedlichen Ausprägungen. Um ein solches Standby- System aufzubauen, dessen Datenbestand immer mit dem des Primärsystems übereinstimmt, wird die Primärdatenbank in einem ersten Schritt “geklont”, d.h. identisch kopiert, und im weiteren Verlauf mit Hilfe von “physikalischer Replikation” aktuell gehalten. Dabei werden fortlaufend alle Änderungen an der Datenbank 1:1 vom Primär- ans Standbysystem übertragen. Benutzt man hierfür WAL-Streaming, ist das Standby-System mit nur minimalster zeitlicher Verzögerung auf dem aktuellen Stand. Es gibt sogar die Möglichkeit, diese Replikation als “synchron” zu konfigurieren. Dann werden der Anwendung die Änderungen an den Daten vom Primärsystem erst dann als “vollzogen” bestätigt, wenn sie auch bei einer vorher konfigurierten Anzahl von Standbysystemen angekommen sind und von diesen verarbeitet wurden. In diesem Fall gibt es also mindestens ein Standbysystem, das auf dem exakt gleichen Stand ist wie das Primärsystem.

Schematische Darstellung der Funktionsweise eines PostgreSQL-Clusters mit RepMgr und BaRMan

Die Standby-Knoten eines PostgreSQL-Replication-Clusters können prinzipiell für den Primärserver einspringen, wenn der einmal ausfallen sollte, weil sie ja per se die aktuellen Daten beinhalten. Leider ist es bei PostgreSQL aber so, dass Datenänderungen ausschließlich auf dem Primärsystem möglich sind. Die Standbysysteme können zwar auch verwendet werden, aber nur lesend. Ein Load-Balancing für Leseabfragen ist also relativ leicht zu realisieren, zumal man clientseitig oftmals die Möglichkeit hat, einen “Multi-Host-Connectstring” zu verwenden, also mehrere alternative Datenbankserver anzugeben. Für Schreiboperationen besteht diese Option aber nicht, so dass sich die Anwendung dafür stets mit dem Primärsystem verbinden muss.

Hat man also ein oder mehrere Standbysysteme, die mittels physikalischer Replikation aktuell gehalten werden, bilden sie zusammen mit dem Primärsystem einen so genannten PostgreSQL- Replication-Cluster. Dabei ist das Zwischenwort, also “Replication”, in der PostgreSQL- Welt durchaus wichtig. Das hängt damit zusammen, dass man bereits ein einzelnes PostgreSQL- System als “PostgreSQL-Cluster” bezeichnet, und zwar vor dem Hintergrund, dass ein solches System architekturbedingt in der Lage ist, mehrere mehr oder weniger unabhängige Datenbanken zur Verfügung zu stellen. Andere Datenbanksysteme sprechen hier von “Instanz”, und im Fall von PostgreSQL wäre so etwas wie “Multi-DB-Instanz” eine mögliche Begriffsalternative zu “PostgreSQL-Cluster”, ist aber nicht üblich. Die einzelnen Systeme, also der Primär- und die Standby-Server, werden im Kontext eines Replication-Clusters als Knoten oder, “neudeutsch”, Nodes bezeichnet.

Ganz nebenbei: Dass man auf den Standby-Knoten keine Datenänderungen vornehmen kann, hat auch ganz direkte Auswirkungen auf das oben beschriebene Szenario einer “synchronen Replikation”: Der große Haken bei dieser Sache ist nämlich, dass der Primärserver nur so lange funktionsfähig bleibt, und daher Änderungen an den Daten möglich sind, wie es genügend viele funktionierende Standbysysteme gibt. Der Ausfall des oder der Standby-Knoten “reißt” also auch den Primärknoten “mit in den Abgrund” und führt zu dessen Ausfall. Im Sinne der Hochverfügbarkeit sollte man sich also sehr genau überlegen, ob dies akzeptabel und “im wahren Sinne des Erfinders” ist, denn eigentlich wollte man mit Hilfe der Standby-Knoten ja die Verfügbarkeit (und nicht das Ausfallrisiko) erhöhen. Zwar ist ein mit synchronem WAL-Streaming betriebener PostgreSQL-Replication-Cluster die Lösung für das angesprochene “RPO≈0”-Problem, aber aus den genannten Gründen, nicht grundsätzlich ideal.

Aus dem in den letzten Abschnitten Beschriebenen ergeben sich im Falle eines Primärsystemausfalls für einen PostgreSQL-Replication-Cluster mindestens drei essenziell zu erledigende Aufgaben:

  1. Einer der Standby-Knoten muss zum neuen Primärsystem erkoren werden (“Promotion”), so dass auch Datenänderungen wieder möglich sind.
  2. Der neue Primärknoten muss bekannt gemacht werden, damit die Clients zum Schreiben den richtigen Knoten auswählen können.
  3. Zusätzlich muss der ehemalige Primärknoten zuverlässig daran gehindert werden, nach der Promotion eines Standbyknotens wieder als Primärknoten in Erscheinung zu treten (“Fencing”).

Kann das erneute Auftreten eines ehemaligen Primärknotens als solcher nicht verhindert werden, entsteht eine gefährliche “Doppelspitze”, ein “Split-Brain”. Das ist durchaus als katastrophal zu bezeichnen, denn es führt fast zwangsläufig zu einem nur mit sehr hohem (manuellen) Aufwand (bzw. schlimmstenfalls überhaupt nicht) reparablen “Datenmischmasch” und somit zu Datenverlusten. Je länger die Split-Brain-Situation andauert, umso schlimmer wird es. Es handelt sich also um einen Zustand, der unter allen Umständen zu vermeiden ist. Dieses Thema ist im übrigen nicht auf PostgreSQL beschränkt, sondern betrifft potenziell alle Hochverfügbarkeits- und Lastverteilungscluster, die nicht ausschließlich lesende Operationen zulassen, sondern auch schreibende.

Der zweitgenannte Punkt, den “neuen” Primärknoten bekannt zu machen, kann einigermaßen einfach und (fast) mit bewährten Linux-“Bordmitteln”, nämlich durch die Definition einer virtuellen IP-Adresse für den PostgreSQL-Replication-Cluster umgesetzt werden. Dabei wird ausgenutzt, dass ein Netzwerkendgerät prinzipiell mehr als eine IP-Adresse besitzen darf. Man definiert also eine “gemeinsame” IP-Adresse und weist sie stets dem gerade aktuellen Primärknoten zu. Software wie KeepAliveD ist ideal geeignet, diese Aufgabe zu übernehmen. Ein mögliches Problem ergibt sich, je nach (ggf. virtualisierter) Hardware-Architektur höchstens noch dadurch, dass die Zuordnung von MAC- zu IP-Adressen in Switches häufig zwischengespeichert wird (im sogenannten ARP-Cache) und beim Wechsel der virtuellen Replication-Cluster-IP-Adresse diese anfänglich noch zur falschen MAC-Adresse aufgelöst wird. Das kann dann zu Verzögerungen und leichten “Netzwerk-Hängern” beim Zugriff auf den neuen Primärserver führen, die sich in aller Regel aber mehr oder weniger schnell “von alleine” lösen.

Neben der beschriebenen “Failover”-Situation, bei der der bisherige Primärknoten unerwartet ausfällt und durch einen ehemaligen Standbyknoten ersetzt wird, gibt es auch noch ein “Switchover”. Bei einem Switchover handelt es sich um eine geplante Aktion, bei der der Primär- und ein Standbyknoten ihre Rollen tauschen, beispielsweise zwecks Wartung. Dabei ist dann auch kein Fencing erforderlich, da der ehemalige Primärknoten in diesem Szenario ja als Standby weiter läuft und der Admin die Situation “manuell” initiiert, also im wahrsten Wortsinne “im Griff” hat.

Hinter dem einfachen Begriff “Promotion” verbirgt sich ein ziemlich komplexer Vorgang, der, streng genommen, u.a. auch bedeutet, dass die Standbyknoten so umkonfiguriert werden müssen, dass sie, nach dem Umschalten auf einen neuen Primärknoten, ihre Änderungsinformationen ab sofort von diesem beziehen müssen. Das gilt sowohl für ein Failover als auch ein Switchover. Natürlich muss man an dieser Stelle das Rad nicht neu erfinden, sondern kann auf ein ganzes Arsenal von Software zurückgreifen, die einem viel von der Komplexität bei Einrichtung und Betrieb eines PostgreSQL-Replication-Clusters, inkl. Switchover und Failover, abnehmen kann. Alle einschlägigen Systeme, von denen hier exemplarisch der “Replication Manager” (RepMgr), “Patroni” und “Cloud Native Postgres for Kubernetes” genannt werden sollen, haben ihre Vor- und Nachteile, so dass man oft die Qual der Wahl hat.

Wir bei eXirius haben, wie oben beschrieben, als zuverlässige Komponente für physische Backups, den BaRMan im Einsatz. Dieser wurde von 2ndQuadrant entwickelt, einem Unternehmen, das inzwischen von EnterpriseDB (EDB) akquiriert wurde. BaRMan wird auch vom neuen “Besitzer” weiterentwickelt, sofern man ob der Tatsache, dass es sich um freie Software handelt, überhaupt von “Besitzer” reden möchte. Ganz genau so verhält es sich mit dem RepMgr. Die gemeinsame Vergangenheit unter der Haube von 2ndQuadrant (bzw. jetzt EDB) führt dazu, dass die beiden Systeme sehr gut aufeinander abgestimmt sind. So kann z.B. ein mit BaRMan erstelltes Backup als Grundlage für ein mit dem RepMgr erstelltes und von diesem verwaltetes neues Standby-System dienen. Das entlastet dadurch auch den Primärknoten vom Klonvorgang ganz zu Beginn der Erstellung eines neuen Standby-Knotens.

Einschlägige vergleichende Artikel sehen den RepMgr zwar durchaus auch kritisch, andererseits ist die gute Integration mit BaRMan ein nicht zu unterschätzender Vorteil. Zudem ist gerade in jüngster Zeit einiges an Entwicklungsarbeit in das Thema “automatisiertes Failover” geflossen. Davon ganz abgesehen ist ein automatisch ausgelöstes Failover für den Replication-Cluster ein recht komplexer Vorgang, bei dem “zu viel Automatismus” durchaus auch schädlich sein kann, z.B. wenn der Zustand des Replication- Clusters irrtümlich als defekt eingeschätzt wird und unnötigerweise und womöglich schnell aufeinander folgend Failovers ausgelöst werden. Eine mögliche Alternative wäre ein manuell angestoßenes Failover, das vom RepMgr selbstverständlich auch problemlos unterstützt wird, dem Admin aber die Möglichkeit offen lässt, die vorgefundene Situation korrekt einzuschätzen, ggf. andere notwendige Maßnahmen zu ergreifen und so ein wildes “Failover-Ping-Pong” zu verhindern.

Insofern empfehlen wir, grundsätzlich zu prüfen, ob die Hochverfügbarkeit wirklich (auch) auf der Ebene der Datenbank implementiert werden muss, oder ob es nicht doch ausreicht, ein zuverlässiges “Single-Instanz”-System aufzusetzen und die Hochverfügbarkeit z.B. in die Ebene eines Virtualisierungshosts zu verlagern – aber das ist eine andere Geschichte…

Wir favorisieren aufgrund guter Erfahrungen folgende Softwarearchitektur/-komponenten, um einen PostgreSQL- Replication-Cluster mit Switchover- und (ggf. automatischer) Failoverfunktionalität zu implementieren:

Dieser erste Teil der Artikelreihe zum Thema “PostgreSQL-Replication-Cluster” erklärt die grundlegende Architektur eines solchen und zeigt PostgreSQL-spezifische Besonderheiten und sich daraus ergebende Herausforderungen auf. In Zukunft folgende Teile werden detaillierter auf technische Aspekte der konkreten Umsetzung eingehen.