In diesem Artikel erkläre ich, wie wir das Thema Hochverfügbarkeit bei PostgreSQL angegangen sind, warum es für uns wichtig wurde und was letztendlich das Ergebnis war.
Wir betreiben einen stark ausgelasteten Service: 2,5 Millionen Nutzer weltweit, über 50.000 aktive Nutzer täglich. Die Server befinden sich in einer Amazon-Region in Irland: ständig sind über 100 verschiedene Server in Betrieb, davon fast 50 mit Datenbanken.
Unser gesamter Backend besteht aus einer großen monolithischen stateful Anwendung in Java, die eine permanente WebSocket-Verbindung zum Client hält. Wenn mehrere Nutzer gleichzeitig an einem Board arbeiten, sehen sie alle Änderungen in Echtzeit, da wir jede Änderung in der Datenbank festhalten. Wir verarbeiten etwa 10.000 Anfragen pro Sekunde an unsere Datenbanken. In Spitzenzeiten schreiben wir in Redis 80.000 bis 100.000 Anfragen pro Sekunde.

Warum wir von Redis auf PostgreSQL umgestiegen sind
Ursprünglich arbeitete unser Service mit Redis, einem Key-Value-Speicher, der alle Daten im Arbeitsspeicher hält. Server.
Vorteile von Redis:
- Hohe Antwortgeschwindigkeit, da alles im Speicher gehalten wird;
- Bequeme Backup- und Replikationsmöglichkeiten.
Nachteile von Redis für uns:
- Es gibt keine echten Transaktionen. Wir haben versucht, sie auf Anwendungsebene zu simulieren. Leider hat das nicht immer gut funktioniert und erforderte das Schreiben von sehr komplexem Code.
- Das Datenvolumen ist durch den verfügbaren Speicher begrenzt. Wenn die Datenmenge zunimmt, wächst auch der Speicher, und schließlich stoßen wir an die Grenzen des ausgewählten Instanztyps, was in AWS einen Stopp unseres Dienstes zur Änderung des Instanztyps erforderlich macht.
- Es ist wichtig, ein niedriges Latency-Niveau aufrechtzuerhalten, da wir eine sehr hohe Anzahl an Anfragen haben. Das optimale Latency-Niveau für uns liegt bei 17-20 ms. Bei einem Wert von 30-40 ms erhalten wir lange Antwortzeiten auf die Anfragen unserer Anwendung, was zu einer Beeinträchtigung des Dienstes führt. Leider ist uns das im September 2018 passiert, als eine unserer Redis-Instanzen aus unerklärlichen Gründen eine doppelt so hohe Latenz aufwies. Um das Problem zu lösen, haben wir den Dienst mitten am Arbeitstag für eine außerplanmäßige Wartung gestoppt und die betroffene Redis-Instanz ersetzt.
- Es ist leicht, Inkonsistenzen in den Daten zu erhalten, selbst bei geringfügigen Fehlern im Code, und dann viel Zeit mit dem Schreiben von Code zur Behebung dieser Datenverschmutzung zu verbringen.
Wir haben die Herausforderungen erkannt und verstanden, dass wir auf eine benutzerfreundlichere Lösung mit reibungsloseren Transaktionen und weniger Abhängigkeit von der Latenz umsteigen müssen. Nach einer gründlichen Untersuchung und Analyse vieler Optionen haben wir uns für PostgreSQL entschieden.
Seit 1,5 Jahren verlagern wir unsere Daten auf die neue Datenbank und haben bisher nur einen kleinen Teil übertragen, weshalb wir derzeit parallel mit Redis und PostgreSQL arbeiten. Mehr über die Phasen des Umstiegs und den Datenaustausch zwischen den Datenbanken steht in der Artikel meines Kollegen.
Als wir mit der Migration begannen, lief unsere Anwendung direkt mit der Datenbank und griff sowohl auf den Master von Redis als auch auf PostgreSQL zu. Der PostgreSQL-Cluster bestand aus einem Master und einer Replica mit asynchroner Replikation. So sah das Schema für die Datenbankverbindungen aus:

Implementierung von PgBouncer
Während unseres Umzugs entwickelte sich auch das Produkt weiter: Die Anzahl der Benutzer und der mit PostgreSQL betriebenen Server nahm zu, sodass uns die Verbindungen ausgingen. PostgreSQL erstellt für jede Verbindung einen separaten Prozess und verbraucht Ressourcen. Die Anzahl der Verbindungen kann bis zu einem bestimmten Punkt erhöht werden, andernfalls besteht die Gefahr, dass die Datenbank nicht optimal arbeitet. In einer solchen Situation ist die Auswahl eines Verbindungspoolers der ideale Weg, der vor der Datenbank platziert wird.
Wir hatten zwei Optionen für den Verbindungspooler: Pgpool und PgBouncer. Der erste unterstützt jedoch keinen Transaktionsmodus mit der Datenbank, daher entschieden wir uns für PgBouncer.
Wir haben folgendes Arbeitsmuster eingerichtet: Unsere Anwendung verbindet sich mit einem PgBouncer, hinter dem die Master-PostgreSQL-Server stehen, und hinter jedem Master befindet sich eine Replik mit asynchroner Replikation.

Dabei konnten wir nicht alle Datenmengen in PostgreSQL speichern, und die Geschwindigkeit der Datenbank war uns wichtig. Daher begannen wir, PostgreSQL auf Anwendungsebene zu sharden. Das oben beschriebene Schema ist relativ praktisch dafür: Wenn ein neuer Shard hinzugefügt wird, reicht es, die PgBouncer-Konfiguration zu aktualisieren, und die Anwendung kann sofort mit dem neuen Shard arbeiten.
Ausfallsicherheit von PgBouncer
Dieses Schema funktionierte bis zu dem Zeitpunkt, als der einzige PgBouncer-Instanz ausfiel. Wir befinden uns in AWS, wo alle Instanzen auf Hardware betrieben werden, die gelegentlich ausfällt. In solchen Fällen wird die Instanz einfach auf neue Hardware übertragen und läuft wieder. So geschah es auch mit PgBouncer, doch er wurde nicht mehr erreichbar. Das Resultat dieses Ausfalls war, dass unser Dienst für 25 Minuten nicht verfügbar war. AWS empfiehlt in solchen Situationen, auf der Benutzerseite Redundanz zu nutzen, was zu diesem Zeitpunkt bei uns nicht implementiert war.
Nach diesem Vorfall haben wir ernsthaft darüber nachgedacht, die Ausfallsicherheit von PgBouncer und PostgreSQL-Clustern zu verbessern, da eine ähnliche Situation mit jeder Instanz in unserem AWS-Konto wieder auftreten könnte.
Wir haben das Failover-Schema für PgBouncer wie folgt aufgebaut: Alle Anwendungsserver greifen auf den Network Load Balancer zu, hinter dem zwei PgBouncer laufen. Jeder PgBouncer überwacht dieselben Master-PostgreSQL-Datenbanken jedes Shards. Falls es erneut zu einem Ausfall des AWS-Instances kommt, wird der gesamte Datenverkehr über einen anderen PgBouncer umgeleitet. Die Ausfallsicherheit des Network Load Balancers wird von AWS gewährleistet.
Dieses Schema ermöglicht es, problemlos neue PgBouncer-Server hinzuzufügen.

Erstellung eines ausfallsicheren PostgreSQL-Clusters
Bei der Lösung dieser Aufgabe haben wir verschiedene Optionen in Betracht gezogen: selbstgeschriebener Failover, repmgr, AWS RDS und Patroni.
Selbstgeschriebene Skripte
können die Funktion des Masters überwachen und im Falle eines Ausfalls die Replikate zum Master befördern sowie die Konfiguration von PgBouncer aktualisieren.
Die Vorteile dieses Ansatzes liegen in ihrer maximalen Einfachheit, da Sie die Skripte selbst schreiben und genau verstehen, wie sie funktionieren.
Nachteile:
- Der Master könnte nicht gestorben sein; stattdessen könnte ein Netzwerkfehler aufgetreten sein. Failover, ohne darüber Bescheid zu wissen, wird die Replikation zum Master voranbringen, während der alte Master weiterarbeitet. Infolgedessen haben wir zwei Server, die als Master fungieren, und wissen nicht, auf welchem die aktuellsten Daten liegen. Solch eine Situation wird auch als Split-Brain bezeichnet.
- Wir sind ohne Replikation geblieben. In unserer Konfiguration gibt es einen Master und eine Replikation; nach dem Failover wird die Replikation zum Master befördert, und wir haben keine weiteren Replikate mehr. Daher müssen wir manuell eine neue Replikation hinzufügen.
- Zusätzliche Überwachung des Failovers ist erforderlich, da wir 12 Shards von PostgreSQL haben, was bedeutet, dass wir 12 Cluster überwachen müssen. Bei einer Erhöhung der Anzahl der Shards dürfen wir nicht vergessen, das Failover zu aktualisieren.
Selbstprogrammierter Failover sieht sehr kompliziert aus und erfordert nicht triviale Unterstützung. Bei einem einzelnen PostgreSQL-Cluster wird dies die einfachste Lösung sein, ist aber nicht skalierbar, weshalb es für uns nicht geeignet ist.
Repmgr
Replication Manager für PostgreSQL-Cluster, der die Verwaltung eines PostgreSQL-Clusters übernimmt. Dabei bietet er jedoch keinen automatischen Failover "out of the box", weshalb eine eigene "Wrapper"-Lösung entwickelt werden muss. Das kann sogar komplizierter sein als mit selbstgeschriebenen Skripten, weshalb wir Repmgr nicht einmal ausprobiert haben.
AWS RDS
Bietet alles, was wir benötigen, kann Backups erstellen und unterstützt einen Connection-Pool. Verfügt über automatisches Switching: wenn der Master ausfällt, wird die Replikation zum neuen Master und AWS aktualisiert den DNS-Eintrag auf den neuen Master, während sich die Replikate in verschiedenen AZs befinden können.
Ein Nachteil ist das Fehlen feiner Einstellungen. Ein Beispiel für feine Einstellungen: In unseren Instanzen gibt es Einschränkungen für TCP-Verbindungen, die leider in RDS nicht umgesetzt werden können:
net.ipv4.tcp_keepalive_time=10
net.ipv4.tcp_keepalive_intvl=1
net.ipv4.tcp_keepalive_probes=5
net.ipv4.tcp_retries2=3
Zudem ist der Preis für AWS RDS fast doppelt so hoch wie der reguläre Preis für Instanzen, was der Hauptgrund für den Verzicht auf diese Lösung war.
Patroni
Dies ist ein Python-Template zur Verwaltung von PostgreSQL mit guter Dokumentation, automatischem Failover und dem Quellcode auf GitHub.
Vorteile von Patroni:
- Jeder Konfigurationsparameter ist klar beschrieben, sodass nachvollziehbar ist, wie alles funktioniert.
- Der automatische Failover funktioniert out of the box.
- In Python geschrieben, und da wir selbst viel in Python programmieren, wird es uns leichter fallen, Probleme zu beheben und möglicherweise sogar das Projekt weiterzuentwickeln.
- Verwalten Sie PostgreSQL vollständig, ändern Sie die Konfiguration sofort auf allen Knoten im Cluster, und wenn ein Neustart des Clusters für die Anwendung der neuen Konfiguration erforderlich ist, kann dies ebenfalls über Patroni erfolgen.
Nachteile:
- Aus der Dokumentation geht nicht klar hervor, wie man richtig mit PgBouncer arbeitet. Das als Nachteil zu werten, ist allerdings schwierig, da es Patrons Aufgabe ist, PostgreSQL zu verwalten, während wir uns um die Anbindung an Patroni kümmern müssen.
- Es gibt nur wenige Implementierungsbeispiele von Patroni in großen Umgebungen, jedoch viele Beispiele für Neubauten.
Letztendlich haben wir uns zur Erstellung eines ausfallsicheren Clusters genau für Patroni entschieden.
Prozess der Implementierung von Patroni
Vor Patroni hatten wir 12 PostgreSQL-Shards in einer Konfiguration mit einem Master und einem Replikat mit asynchroner Replikation. Die Anwendungsserver kommunizierten über einen Network Load Balancer mit den Datenbanken, hinter dem sich zwei Instanzen mit PgBouncer befanden, und dahinter lagen alle PostgreSQL-Server.

Für die Implementierung von Patroni mussten wir ein verteiltes Konfigurationsspeicher für den Cluster wählen. Patroni funktioniert mit verteilten Konfigurationsspeichersystemen wie etcd, Zookeeper und Consul. Wir haben bereits einen voll funktionsfähigen Consul-Cluster in der Produktion, der zusammen mit Vault betrieben wird und derzeit nicht anderweitig genutzt wird. Das ist eine ausgezeichnete Gelegenheit, Consul wie vorgesehen zu verwenden.
Wie funktioniert Patroni mit Consul
Wir haben einen Consul-Cluster, der aus drei Knoten besteht, und einen Patroni-Cluster, der aus einem Führer und Replikaten besteht (im Patroni wird der Master als Clusterführer bezeichnet und die Slaves als Replikate). Jede Instanz des Patroni-Clusters sendet ständig Informationen über den Zustand des Clusters an Consul. Daher kann man jederzeit die aktuelle Konfiguration des Patroni-Clusters und den aktuellen Führer in Consul einsehen.

Um Patroni mit Consul zu verbinden, genügt es, die offizielle Dokumentation zu studieren, aus der hervorgeht, dass der Host im Format http oder https angegeben werden muss, je nachdem, wie wir mit Consul arbeiten, sowie das Verbindungsprotokoll optional:
host: der host:port für den Consul-Endpunkt im Format: http(s)://host:port
scheme: (optional) http oder https, standardmäßig httpEs sieht einfach aus, aber hier beginnen die Fallstricke. Mit Consul arbeiten wir über eine gesicherte Verbindung via https, und unsere Verbindungs-Konfiguration wird folgendermaßen aussehen:
consul:
host: https://server.production.consul:8080
verify: true
cacert: {{ consul_cacert }}
cert: {{ consul_cert }}
key: {{ consul_key }}Doch so funktioniert es nicht. Beim Start kann Patroni sich nicht mit Consul verbinden, da es trotzdem versucht, über http zu gehen.
Das Verständnis des Problems wurde durch den Quellcode von Patroni unterstützt. Zum Glück ist er in Python geschrieben. Es stellt sich heraus, dass der Parameter host nicht geparsed wird, und das Protokoll muss in der scheme angegeben werden. So sieht der funktionierende Konfigurationsblock für die Arbeit mit Consul aus:
consul:
host: server.production.consul:8080
scheme: https
verify: true
cacert: {{ consul_cacert }}
cert: {{ consul_cert }}
key: {{ consul_key }}Consul-Template
Also, wir haben ein Speicherort für die Konfiguration ausgewählt. Jetzt müssen wir verstehen, wie PgBouncer seine Konfiguration beim Wechsel des Leaders im Patroni-Cluster umschaltet. In der Dokumentation gibt es darauf keine Antwort, da die Arbeit mit PgBouncer dort grundsätzlich nicht beschrieben wird.
Auf der Suche nach einer Lösung fanden wir einen Artikel (den Titel erinnere ich leider nicht), in dem stand, dass Consul-Template sehr hilfreich in der Verbindung zwischen PgBouncer und Patroni war. Das hat uns dazu angeregt, die Funktionsweise von Consul-Template zu erforschen.
Es stellte sich heraus, dass der Consul-Template kontinuierlich die Konfiguration des PostgreSQL-Clusters in Consul überwacht. Bei einem Leader-Wechsel aktualisiert er die PgBouncer-Konfiguration und sendet den Befehl zur Neuladung.

Ein großer Vorteil des Templates besteht darin, dass es in Codeform gespeichert wird. Daher genügt es bei der Hinzufügung eines neuen Shards, einen neuen Commit zu machen und das Template automatisch zu aktualisieren, wodurch das Prinzip "Infrastructure as Code" unterstützt wird.
Neue Architektur mit Patroni
Das Resultat ist folgendes Arbeitsdiagramm:

Alle Anwendungsserver wenden sich an den Lastenausgleich → dahinter stehen zwei PgBouncer-Instanzen → auf jeder Instanz läuft der Consul-Template, der den Zustand jedes Patroni-Clusters überwacht und sicherstellt, dass die PgBouncer-Konfiguration aktuell ist, die die Anfragen an den aktuellen Leader jedes Clusters weiterleitet.
Manuelles Testing
Diese Struktur haben wir vor dem Rollout in Produktion in einer kleinen Testumgebung getestet und die Funktionalität des automatischen Failovers überprüft. Wir haben das Board geöffnet, einen Sticker verschoben und in diesem Moment den Leader des Clusters "getötet". Bei AWS genügt es, die Instanz über die Konsole auszuschalten.

Der Sticker kam innerhalb von 10-20 Sekunden zurück, bevor er wieder normal zu bewegen begann. Das bedeutet, der Patroni-Cluster hat korrekt funktioniert: der Leader wurde gewechselt, die Informationen wurden an Consul gesendet, und das Consul-Template hat diese Informationen sofort aufgegriffen, die PgBouncer-Konfiguration ersetzt und den Reload-Befehl gesendet.
Wie überlebt man unter hoher Last und minimiert Ausfallzeiten?
Alles funktioniert hervorragend! Aber neue Fragen tauchen auf: Wie wird es sich unter hoher Last verhalten? Wie rollt man alles schnell und sicher in die Produktion aus?
Auf die erste Frage hilft uns eine Testumgebung, in der wir Lasttests durchführen. Sie ist in Architektur und Testdaten vollständig identisch mit der Produktion und hat generierte Testdaten, die in etwa der Produktionsgröße entsprechen. Wir entscheiden uns, während des Tests einen der PostgreSQL-Master absichtlich „abzuwürgen“ und zu beobachten, was passiert. Aber vorher ist es wichtig, das automatische Rollout zu überprüfen, denn in dieser Umgebung haben wir mehrere PostgreSQL-Shards, sodass wir großartige Tests der Konfigurationsskripte vor der Produktion erhalten.
Beide Aufgaben erscheinen ehrgeizig, aber wir haben PostgreSQL 9.6. Vielleicht aktualisieren wir gleich auf 11.2?
Wir haben beschlossen, dies in zwei Schritten durchzuführen: zuerst die Version auf 11.2 zu aktualisieren, dann Patroni zu starten.
PostgreSQL-Upgrade
Für ein schnelles Upgrade der PostgreSQL-Version sollte die Option -k, verwendet werden, bei der Hardlinks auf der Festplatte erstellt werden und kein Kopieren Ihrer Daten erforderlich ist. Bei Datenbanken mit 300-400 GB dauert das Upgrade nur 1 Sekunde.
Wir haben viele Shards, sodass das Upgrade automatisiert erfolgen muss. Dafür haben wir ein Ansible-Playbook geschrieben, das den gesamten Upgrade-Prozess für uns durchführt:
/usr/lib/postgresql/11/bin/pg_upgrade
<b>--link </b>
--old-datadir='' --new-datadir=''
--old-bindir='' --new-bindir=''
--old-options=' -c config_file='
--new-options=' -c config_file='Hier ist es wichtig zu erwähnen, dass vor dem Start der Aktualisierung der Parameter —checkverwendet werden muss, um sicherzustellen, dass das Upgrade möglich ist. Außerdem ersetzt unser Skript während des Upgrades die Konfigurationsdateien. Unser Skript hat in 30 Sekunden ausgeführt, was ein hervorragendes Ergebnis ist.
Start von Patroni
Um das zweite Problem zu lösen, reicht es, sich die Konfiguration von Patroni anzusehen. Im offiziellen Repository gibt es ein Beispiel für eine Konfiguration mit initdb, das für die Initialisierung einer neuen Datenbank beim ersten Start von Patroni verantwortlich ist. Da wir jedoch bereits eine vorhandene Datenbank haben, haben wir diesen Abschnitt einfach aus der Konfiguration entfernt.
Als wir begonnen haben, Patroni auf einen bestehenden PostgreSQL-Cluster zu installieren und zu starten, sind wir auf ein neues Problem gestoßen: Beide Server starteten als Leader. Patroni hat keine Ahnung vom vorherigen Zustand des Clusters und versucht, beide Server als zwei separate Cluster mit demselben Namen zu starten. Um dieses Problem zu lösen, muss das Datenverzeichnis auf dem Slave gelöscht werden:
rm -rf /var/lib/postgresql/Dies muss nur auf dem Slave durchgeführt werden!
Beim Verbinden eines neuen Replikats macht Patroni ein Basebackup des Leaders und stellt es auf der Replik wieder her, und holt anschließend den aktuellen Zustand über die WAL-Logs ein.
Eine weitere Schwierigkeit, die wir festgestellt haben, ist, dass alle PostgreSQL-Cluster standardmäßig "main" genannt werden. Wenn jeder Cluster nichts über den anderen weiß, ist das in Ordnung. Aber wenn Sie Patroni verwenden möchten, müssen alle Cluster einen einzigartigen Namen haben. Die Lösung besteht darin, den Clusternamen in der PostgreSQL-Konfiguration zu ändern.
Lasttest
Wir haben einen Test gestartet, der das Nutzerverhalten auf Boards simuliert. Als die Last unser durchschnittliches Tagesniveau erreichte, wiederholten wir denselben Test und schalteten eine Instanz mit dem führenden PostgreSQL aus. Der automatische Failover funktionierte wie erwartet: Patroni wechselte den Leader, Consul-Template aktualisierte die PgBouncer-Konfiguration und gab den Reload-Befehl. Unsere Grafiken in Grafana zeigten Verzögerungen von 20-30 Sekunden und eine geringe Anzahl von Fehlern bei der Verbindung mit der Datenbank. Das ist eine normale Situation, solche Werte sind für unseren Failover zulässig und eindeutig besser als Ausfallzeiten des Dienstes.
Patroni in der Produktion
Somit haben wir den folgenden Plan erstellt:
- Deployment von Consul-Template auf die PgBouncer-Server und Start;
- Aktualisierung von PostgreSQL auf Version 11.2;
- Änderung des Cluster-Namens;
- Start des Patroni-Clusters.
Unser Schema ermöglicht es uns, den ersten Punkt praktisch jederzeit durchzuführen. Wir können nacheinander jeden PgBouncer außer Betrieb nehmen und das Deployment sowie den Start von Consul-Template darauf ausführen. Genau das haben wir getan.
Für eine schnelle Bereitstellung haben wir Ansible verwendet, da wir alle Playbooks bereits in der Testumgebung geprüft hatten, und die Ausführungszeit des gesamten Szenarios betrug zwischen 1,5 und 2 Minuten für jeden Shard. Wir konnten alles nacheinander auf jeden Shard ausrollen, ohne unseren Dienst zu unterbrechen, müssten jedoch jeden PostgreSQL für mehrere Minuten abschalten. In diesem Fall könnten die Benutzer, deren Daten auf diesem Shard gespeichert sind, währenddessen nicht richtig arbeiten, was für uns inakzeptabel wäre.
Die Lösung für diese Situation war eine planmäßige Wartung, die alle 3 Monate bei uns stattfindet. Dies ist ein Fenster für geplante Arbeiten, in dem wir unseren Dienst vollständig abschalten und die Datenbankinstanzen aktualisieren. Eine Woche vor dem nächsten Wartungsfenster haben wir uns entschieden, einfach zu warten und uns zusätzlich vorzubereiten. Während der Wartezeit haben wir zusätzliche Vorkehrungen getroffen: Für jeden PostgreSQL-Shard haben wir eine Backup-Replikation erstellt, um die aktuellsten Daten im Falle eines Fehlers zu sichern, und für jeden Shard eine neue Instanz hinzugefügt, die zur neuen Replik im Patroni-Cluster werden sollte, um den Befehl zur Datenlöschung zu vermeiden. All dies half, das Risiko eines Fehlers zu minimieren.

Wir haben unseren Service neu gestartet, alles funktioniert wie gewünscht, die Benutzer konnten weiterarbeiten, jedoch haben wir an den Grafiken eine anomal hohe Last auf den Consul-Servern bemerkt.

Warum haben wir das in der Testumgebung nicht bemerkt? Dieses Problem verdeutlicht sehr gut, wie wichtig es ist, dem Prinzip "Infrastructure as Code" zu folgen und die gesamte Infrastruktur von den Testumgebungen bis hin zur Produktionsumgebung weiterzuentwickeln. Andernfalls kann man leicht auf das Problem stoßen, das uns passiert ist. Was geschah? Consul wurde zunächst in der Produktionsumgebung eingeführt, bevor es auf den Testumgebungen auftauchte. Schließlich war in den Testumgebungen die Version von Consul höher als in der Produktionsumgebung. In einer der Versionen wurde ein CPU-Leck bei der Arbeit mit consul-template behoben. Daher haben wir Consul einfach aktualisiert, um das Problem zu lösen.
Patroni-Cluster neu starten
Aber wir stießen auf ein neues Problem, von dem wir nicht einmal wussten. Beim Aktualisieren von Consul entfernen wir einfach den Consul-Knoten aus dem Cluster mit dem Befehl consul leave → Patroni verbindet sich mit einem anderen Consul-Server → alles funktioniert. Aber als wir zum letzten Instanz des Consul-Clusters kamen und ihm den Befehl consul leave gaben, starteten alle Patroni-Cluster einfach neu, und in den Logs sahen wir folgenden Fehler:
FEHLER: get_cluster
Verlauf (letzter Aufruf zuletzt):
...
RetryFailedError: 'Zeitüberschreitung der Wiederholungsfrist überschritten'
FEHLER: Fehler bei der Kommunikation mit DCS
<b>PROTOKOLL: Datenbanksystem wurde heruntergefahren</b>Der Patroni-Cluster konnte keine Informationen über seinen Cluster abrufen und wurde neu gestartet.
Um eine Lösung zu finden, haben wir die Autoren von Patroni über ein Issue auf GitHub kontaktiert. Sie haben uns Verbesserungen für unsere Konfigurationsdateien vorgeschlagen:
consul:
consul.checks: []
bootstrap:
dcs:
retry_timeout: 8Wir konnten das Problem in einer Testumgebung reproduzieren und diese Parameter dort testen, aber leider haben sie nicht funktioniert.
Das Problem bleibt weiterhin ungelöst. Wir planen, die folgenden Lösungsansätze auszuprobieren:
- Den Consul-Agenten auf jeder Instanz des Patroni-Clusters zu verwenden;
- Das Problem im Code zu beheben.
Wir verstehen, wo der Fehler auftritt: Wahrscheinlich liegt das Problem in der Verwendung des Standard-Timeouts, das nicht durch die Konfigurationsdatei überschrieben wird. Wenn der letzte Consul-Server aus dem Cluster entfernt wird, hängt der gesamte Consul-Cluster für mehr als eine Sekunde, weshalb Patroni den Clusterzustand nicht abrufen kann und den gesamten Cluster neu startet.
Glücklicherweise haben wir keine weiteren Fehler festgestellt.
Fazit zur Verwendung von Patroni
Nach dem erfolgreichen Start von Patroni haben wir in jedem Cluster eine zusätzliche Replik hinzugefügt. Jetzt gibt es in jedem Cluster eine Art Quorum: einen Führer und zwei Replikate, um im Falle eines Split-Brain bei einem Failover abzusichern.

In der Produktion läuft Patroni bereits seit über drei Monaten. In dieser Zeit hat er uns schon mehrfach geholfen. Kürzlich ist in AWS der Führer eines unserer Cluster ausgefallen, der automatische Failover hat funktioniert und die Benutzer konnten weiterarbeiten. Patroni hat seine Hauptaufgabe erfüllt.
Ein kleines Resümee zur Nutzung von Patroni:
- Die Konfigurationsänderung ist einfach. Es genügt, die Konfiguration auf einer Instanz zu ändern, und sie wird im gesamten Cluster übernommen. Wenn ein Neustart zur Anwendung der neuen Konfiguration erforderlich ist, informiert Patroni darüber. Patroni kann den gesamten Cluster mit einem Befehl neu starten, was ebenfalls sehr praktisch ist.
- Der automatische Failover funktioniert und hat uns bereits geholfen.
- PostgreSQL-Updates ohne Ausfallzeiten der Anwendung. Zuerst müssen die Replikate auf die neue Version aktualisiert werden, dann wird der Führer im Patroni-Cluster gewechselt und der alte Führer aktualisiert. Dabei erfolgt das notwendige Testen des automatischen Failovers.
Quelle: habr.com
