Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

In diesem Artikel erkläre ich, wie wir das Thema Hochverfügbarkeit von PostgreSQL angegangen sind, warum es für uns wichtig wurde und was dabei herausgekommen ist.

Wir haben einen stark frequentierten Service: 2,5 Millionen Nutzer weltweit, über 50.000 aktive Nutzer jeden Tag. Die Server befinden sich in AWS in einer Region Irland: ständig sind über 100 verschiedene Server damit beschäftigt, von denen fast 50 Datenbanken hosten.

Der gesamte Backend-Bereich besteht aus einer großen monolithischen Stateful-Anwendung in Java, die eine dauerhafte WebSocket-Verbindung zum Client hält. Wenn mehrere Benutzer gleichzeitig an einem Board arbeiten, sehen sie alle die Änderungen in Echtzeit, da wir jede Änderung in der Datenbank festhalten. Wir haben etwa 10.000 Anfragen pro Sekunde an unsere Datenbanken. Bei Spitzenlast schreiben wir in Redis 80.000 bis 100.000 Anfragen pro Sekunde.
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Warum wir von Redis zu PostgreSQL gewechselt sind

Ursprünglich arbeitete unser Service mit Redis, einem Key-Value-Speicher, der alle Daten im Arbeitsspeicher speichert. Server.

Vorteile von Redis:

  1. Hohe Antwortgeschwindigkeit, da alles im Speicher gespeichert wird;
  2. Bequemlichkeit bei Backup und Replikation.

Nachteile von Redis für uns:

  1. Es gibt keine echten Transaktionen. Wir haben versucht, diese auf Anwendungsebene zu simulieren. Leider hat das nicht immer gut funktioniert und erforderte sehr komplexen Code.
  2. Das Datenvolumen ist durch den verfügbaren Speicher begrenzt. Wenn die Datenmenge ansteigt, wächst der Speicherbedarf, und letztendlich stoßen wir an die Grenzen des gewählten Instances, was bei AWS bedeutet, dass wir unseren Service für einen Typenwechsel anhalten müssen.
  3. Wir müssen ständig ein niedriges Latenzniveau aufrechterhalten, da wir eine sehr hohe Anzahl an Anfragen haben. Das optimale Latenzniveau für uns liegt bei 17-20 ms. Bei 30-40 ms erhalten wir lange Antwortzeiten auf die Anfragen unserer Anwendung und eine Verschlechterung des Dienstes. Leider kam es im September 2018 dazu, dass eine unserer Redis-Instanzen aus unbekannten Gründen eine doppelt so hohe Latenz wie gewohnt hatte. Um das Problem zu lösen, haben wir den Service mitten am Arbeitstag für Wartungsarbeiten gestoppt und die problematische Redis-Instanz ersetzt.
  4. Es ist leicht, Inkonsistenzen in den Daten zu erhalten, selbst bei kleinen Fehlern im Code, und dann viel Zeit mit dem Schreiben von Code zu verbringen, um diese Daten zu beheben.

Wir haben die Nachteile berücksichtigt und festgestellt, dass wir auf etwas Bequemeres umsteigen müssen, mit normalen Transaktionen und weniger Abhängigkeit von der Latenz. Wir haben eine Untersuchung durchgeführt, viele Optionen analysiert und uns für PostgreSQL entschieden.

Die Migration zur neuen Datenbank dauert bereits seit 1,5 Jahren und wir haben nur einen kleinen Teil der Daten übertragen, daher arbeiten wir momentan parallel mit Redis und PostgreSQL. Mehr zu den Phasen der Migration und dem Wechsel der Daten zwischen den Datenbanken steht in dem Artikel meines Kollegen.

Als wir mit der Migration begannen, arbeitete unsere Anwendung direkt mit der Datenbank und sprach mit dem Master von Redis und PostgreSQL. Der PostgreSQL-Cluster bestand aus einem Master und einer Replik mit asynchroner Replikation. So sah das Schema der Datenbankinteraktion aus:
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Implementierung von PgBouncer

Während unserer Migration entwickelte sich auch das Produkt weiter: Die Anzahl der Nutzer und der Server, die mit PostgreSQL arbeiteten, nahm zu, und wir hatten nicht genügend Verbindungen. 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, ansonsten besteht die Gefahr, dass die Datenbank nicht optimal arbeitet. In solch einer Situation wäre die Wahl eines Verbindungsmanagers ideal, der vor der Datenbank steht.

Wir hatten zwei Optionen für den Verbindungsmanager: Pgpool und PgBouncer. Aber der erste unterstützt den transaktionsbasierten Betrieb mit der Datenbank nicht, daher wählten wir PgBouncer.

Wir haben das folgende Arbeitsschema eingerichtet: Unsere Anwendung spricht mit einem PgBouncer, hinter dem sich die Master von PostgreSQL befinden, und hinter jedem Master gibt es eine Replik mit asynchroner Replikation.
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Dabei konnten wir nicht den gesamten Datenvolumen in PostgreSQL speichern und die Geschwindigkeit der Datenbank war für uns wichtig, daher begannen wir, PostgreSQL auf Anwendungsebene zu sharden. Das oben beschriebene Schema ist dafür relativ praktisch: Bei der Hinzufügung eines neuen Shards genügt es, die Konfiguration von PgBouncer zu aktualisieren, und die Anwendung kann sofort mit dem neuen Shard arbeiten.

Ausfallsicherheit von PgBouncer

Dieses Schema funktionierte, bis der einzige PgBouncer-Instanz ausfiel. Wir befinden uns in AWS, wo alle Instanzen auf Hardware laufen, die gelegentlich ausfällt. In solchen Fällen wird die Instanz einfach auf neue Hardware verschoben und funktioniert wieder. Das geschah auch mit PgBouncer, allerdings wurde es unzugänglich. Das Ergebnis dieses Ausfalls war die Nichterreichbarkeit unseres Dienstes für 25 Minuten. AWS empfiehlt in solchen Situationen eine Benutzerseitige Redundanz, die bei uns zu diesem Zeitpunkt nicht umgesetzt war.

Daraufhin machten wir uns ernsthaft Gedanken über die Fehlertoleranz von PgBouncer und PostgreSQL-Clustern, da eine ähnliche Situation mit jeder Instanz in unserem AWS-Konto auftreten könnte.

Wir haben das Schema zur Fehlertoleranz von PgBouncer wie folgt aufgebaut: Alle Anwendungsserver greifen auf einen Network Load Balancer zu, hinter dem sich zwei PgBouncer-Instanzen befinden. Jeder PgBouncer vergeht auf dieselben Master PostgreSQL jeder Shard. Im Falle eines erneuten Ausfalls einer AWS-Instanz wird der gesamte Datenverkehr über den anderen PgBouncer umgeleitet. Die Fehlertoleranz des Network Load Balancer wird von AWS bereitgestellt.

Dieses Schema ermöglicht es, problemlos neue PgBouncer-Server hinzuzufügen.
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Aufbau eines fehlertoleranten PostgreSQL-Clusters

Bei der Lösung dieser Aufgabe haben wir verschiedene Optionen in Betracht gezogen: Selbstgeschriebene Failover-Skripte, repmgr, AWS RDS, Patroni.

Selbstgeschriebene Skripte

Kann den Betrieb des Masters überwachen und im Falle eines Ausfalls die Replikation zum Master vorantreiben sowie die PgBouncer-Konfiguration aktualisieren.

Die Vorteile dieses Ansatzes liegen in der maximalen Einfachheit, da Sie die Skripte selbst schreiben und genau verstehen, wie sie funktionieren.

Nachteile:

  • Der Master könnte nicht ausgefallen sein; es könnte auch ein Netzwerkfehler aufgetreten sein. Das Failover würde, ohne dies zu wissen, die Replikation zum Master vorantreiben, während der alte Master weiterhin arbeitet. Infolgedessen hätten wir zwei Server in der Rolle des Masters und wüssten nicht, auf welchem die aktuellsten Daten vorliegen. Eine solche Situation wird auch als Split-Brain bezeichnet.
  • Wir verblieben ohne Replik. In unserer Konfiguration gibt es einen Master und eine Replik, nach dem Switch wird die Replik zum Master befördert, und wir haben keine Replik mehr, weshalb wir manuell eine neue Replik hinzufügen müssen.
  • Es ist zusätzliches Monitoring für das Failover erforderlich und wir haben 12 PostgreSQL-Shards, was bedeutet, dass wir 12 Cluster überwachen müssen. Bei einer Erhöhung der Anzahl der Shards darf man nicht vergessen, das Failover ebenfalls zu aktualisieren.

Ein selbstgeschriebener Failover sieht sehr kompliziert aus und erfordert nicht triviale Unterstützung. Bei einem PostgreSQL-Cluster ist dies die einfachste Variante, aber sie ist nicht skalierbar, daher kommt sie für uns nicht in Frage.

Repmgr

Replication Manager für PostgreSQL-Cluster, der die Arbeit eines PostgreSQL-Clusters verwalten kann. Dabei gibt es keinen automatischen Failover 'out of the box', weshalb man eine eigene 'Wrapper'-Lösung über die vorhandene Lösung schreiben muss. Das kann also sogar komplizierter sein als mit selbstgeschriebenen Skripten, weshalb wir Repmgr gar nicht ausprobiert haben.

AWS RDS

Unterstützt alles Notwendige für uns, kann Backups erstellen und unterstützt Connection Pools. Es gibt ein automatisches Umschalten: Wenn der Master ausfällt, wird die Replik zur neuen Master, und AWS ändert den DNS-Eintrag auf den neuen Master, wobei die Replikate in verschiedenen AZs sein können.

Zu den Nachteilen zählt das Fehlen feiner Einstellungen. Ein Beispiel für feine Einstellungen: Auf unseren Instanzen gibt es Einschränkungen für TCP-Verbindungen, die leider nicht in RDS 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

Außerdem ist der Preis für AWS RDS fast doppelt so hoch wie der normale Preis für Instanzen, was der Hauptgrund für die Ablehnung dieser Lösung war.

Patroni

Das ist ein Python-Template zur Verwaltung von PostgreSQL mit guter Dokumentation, automatischem Failover und Quellcode auf GitHub.

Vorteile von Patroni:

  • Jeder Konfigurationsparameter ist detailliert beschrieben, es ist klar, wie alles funktioniert;
  • Der automatische Failover funktioniert sofort;
  • Ist in Python geschrieben, und da wir selbst viel in Python programmieren, wird es uns leichter fallen, Probleme zu lösen und möglicherweise sogar zur Weiterentwicklung des Projekts beizutragen;
  • Verwaltet PostgreSQL vollständig, ermöglicht das gleichzeitige Ändern der Konfiguration auf allen Knoten des Clusters. Wenn ein Neustart des Clusters zur Anwendung der neuen Konfiguration erforderlich ist, kann dies ebenfalls mit Patroni erfolgen.

Nachteile:

  • Aus der Dokumentation ist nicht klar, wie man richtig mit PgBouncer arbeitet. Das ist zwar schwer als Nachteil zu bezeichnen, weil es die Aufgabe von Patroni ist, PostgreSQL zu verwalten, und wie die Verbindungen zu Patroni laufen – das ist unser Problem;
  • Es gibt nur wenige Beispiele für die Implementierung von Patroni bei großen Volumen, aber viele Beispiele für Implementierungen von Grund auf.

Letztlich haben wir uns für die Erstellung eines hochverfügbaren Clusters für Patroni entschieden.

Der Implementierungsprozess von Patroni

Vor Patroni hatten wir 12 PostgreSQL-Shards in einer Konfiguration mit einem Master und einem Replica mit asynchroner Replikation. Die Anwendungsserver kommunizierten über einen Network Load Balancer mit den Datenbanken, hinter dem zwei Instanzen mit PgBouncer standen, und dahinter befanden sich alle PostgreSQL-Server.
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Für die Einführung von Patroni mussten wir ein verteiltes Konfigurationsspeicher für den Cluster auswählen. Patroni funktioniert mit verteilten Konfigurationsspeichersystemen wie etcd, Zookeeper und Consul. Bei uns gibt es in der Produktion einen vollwertigen Consul-Cluster, der in Verbindung mit Vault betrieben wird, und wir nutzen ihn sonst nicht. Das ist eine gute Gelegenheit, Consul wie vorgesehen zu verwenden.

Wie Patroni mit Consul funktioniert

Wir haben einen Consul-Cluster, der aus drei Knoten besteht, und einen Patroni-Cluster, der aus einem Leader und einem Replica besteht (im Patroni heißt der Master Leader des Clusters und die Slaves – Replikate). Jede Instanz des Patroni-Clusters sendet ständig Informationen über den Zustand des Clusters an Consul. Daher kann man immer über Consul die aktuelle Konfiguration des Patroni-Clusters und den aktuellen Leader erfahren.

Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Um Patroni mit Consul zu verbinden, reicht es aus, die offizielle Dokumentation zu studieren, die besagt, dass man den Host im Format http oder https angeben muss, je nachdem, wie wir mit Consul arbeiten, sowie das Schema für die Verbindung, optional:

host: der Host:Port für den Consul-Endpunkt, im Format: http(s)://host:port
schema: (optional) http oder https, standardmäßig http

Das sieht einfach aus, aber hier beginnen die Fallstricke. Mit Consul arbeiten wir über eine sichere Verbindung über https, und unsere Verbindungskonfiguration sieht folgendermaßen aus:

consul:
  host: https://server.production.consul:8080 
  verify: true
  cacert: {{ consul_cacert }}
  cert: {{ consul_cert }}
  key: {{ consul_key }}

Aber so funktioniert es nicht. Beim Start kann sich Patroni nicht mit Consul verbinden, weil es trotzdem versucht, über http zu gehen.

Bei der Lösung des Problems half der Quellcode von Patroni. Gut, dass er in Python geschrieben ist. Es stellt sich heraus, dass der Host-Parameter nicht geparst wird, und das Protokoll muss im Schema angegeben werden. So sieht der funktionierende Konfigurationsblock für die Arbeit mit Consul bei uns aus:

consul:
  host: server.production.consul:8080
  scheme: https
  verify: true
  cacert: {{ consul_cacert }}
  cert: {{ consul_cert }}
  key: {{ consul_key }}

Consul-template

Wir haben also das Speicher-Repository 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 keine Antwort auf diese Frage, da die Zusammenarbeit mit PgBouncer dort grundsätzlich nicht beschrieben ist.

Auf der Suche nach einer Lösung fanden wir einen Artikel (leider erinnere ich mich nicht an den Titel), in dem geschrieben stand, dass Consul-template sehr hilfreich bei der Verbindung von PgBouncer und Patroni war. Das motivierte uns, die Funktionsweise von Consul-template zu untersuchen.

Es stellte sich heraus, dass Consul-template ständig die Konfiguration des PostgreSQL-Clusters in Consul überwacht. Beim Wechsel des Leaders aktualisiert es die PgBouncer-Konfiguration und sendet einen Befehl zum Neuladen.

Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Ein großer Vorteil des Templates ist, dass es in Form von Code gespeichert wird. Daher reicht es aus, einen neuen Commit vorzunehmen und das Template automatisch zu aktualisieren, um das Prinzip Infrastructure as Code zu unterstützen.

Die neue Architektur mit Patroni

In der Folge erhielten wir ein solches Arbeitschema:
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Alle Anwendungsserver wenden sich an den Lastenausgleich → Hinter ihm stehen zwei PgBouncer-Instanzen → Auf jeder Instanz läuft Consul-template, das den Status jedes Patroni-Clusters überwacht und die Aktualität der PgBouncer-Konfiguration sicherstellt, die Anfragen an den aktuellen Leader jedes Clusters weiterleitet.

Manuelle Tests

Dieses Schema haben wir vor der Produktion in einer kleinen Testumgebung gestartet und das automatische Umschalten überprüft. Wir öffneten das Board, bewegten einen Sticker und in diesem Moment "töteten" wir den Cluster-Leader. In AWS reicht es aus, die Instanz über die Konsole auszuschalten.

Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Der Sticker kehrte innerhalb von 10-20 Sekunden zurück und begann dann wieder normal zu wandern. Das bedeutet, dass der Patroni-Cluster korrekt gearbeitet hat: Der Leader wurde gewechselt, die Informationen wurden an Consul gesendet und Consul-template erfasste diese Informationen sofort, ersetzte die PgBouncer-Konfiguration und sendete den Neuladungsbefehl.

Wie überlebt man unter hoher Belastung und hält die Ausfallzeiten minimal?

Alles funktioniert hervorragend! Aber neue Fragen tauchen auf: Wie funktioniert das unter hoher Last? Wie kann man alles schnell und sicher in der Produktion ausrollen?

Die Antwort auf die erste Frage erhalten wir mit Hilfe einer Testumgebung, auf der wir Lasttests durchführen. Sie ist in der Architektur identisch mit der Produktionsumgebung und hat generierte Testdaten, die in etwa dem Umfang der Produktion entsprechen. Wir versuchen einfach, einen der PostgreSQL-Master während des Tests "auszuschalten" und zu sehen, was passiert. Aber vorher ist es wichtig, das automatische Rollout zu überprüfen, denn in dieser Umgebung haben wir mehrere PostgreSQL-Shards, sodass wir ein hervorragendes Testen der Konfigurationsskripte vor der Produktion erhalten.

Beide Aufgaben erscheinen ehrgeizig, aber wir haben PostgreSQL 9.6. Vielleicht sollten wir gleich auf 11.2 upgraden?

Wir haben uns entschieden, dies in zwei Schritten zu tun: Zuerst die Version auf 11.2 zu aktualisieren, dann Patroni zu starten.

PostgreSQL-Update

Um das Version-Upgrade von PostgreSQL schnell durchzuführen, ist es notwendig, die Option zu verwenden -k, bei der Hardlinks auf der Festplatte erstellt werden und keine Notwendigkeit besteht, Ihre Daten zu kopieren. Bei Datenbanken mit 300-400 GB dauert das Upgrade 1 Sekunde.

Wir haben viele Shards, daher muss das Upgrade automatisch erfolgen. Zu diesem Zweck 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>
--altes-datadir='' --neues-datadir='' 
 --altes-bindir=''  --neues-bindir='' 
 --alte-optionen=' -c konfigurationsdatei=' 
 --neue-optionen=' -c konfigurationsdatei='

Dabei ist es wichtig zu beachten, dass vor dem Start des Upgrades das Upgrade mit dem Parameter —checkdurchgeführt werden muss, um sicherzustellen, dass das Upgrade möglich ist. Auch unser Skript ersetzt während des Upgrades die Konfigurationen. Unser Skript wurde in 30 Sekunden ausgeführt, das ist ein hervorragendes Ergebnis.

Start von Patroni

Um das zweite Problem zu lösen, reicht es aus, einen Blick auf die Patroni-Konfiguration zu werfen. Im offiziellen Repository gibt es ein Beispiel für eine Konfiguration mit initdb, die 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 begannen, Patroni auf einem bereits vorhandenen PostgreSQL-Cluster zu installieren und zu starten, stießen wir auf ein neues Problem: Beide Server starteten als Leader. Patroni weiß nichts über den früheren 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/

Das muss nur auf dem Slave gemacht werden!

Beim Anschließen einer sauberen Replikat macht Patroni ein Basis-Backup des Leaders und stellt es auf der Replikation wieder her, und gleicht dann den aktuellen Zustand über die WAL-Logs an.

Eine weitere Herausforderung, mit der wir konfrontiert waren, 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 eindeutigen Namen haben. Die Lösung besteht darin, den Namen des Clusters in der PostgreSQL-Konfiguration zu ändern.

Lasttest

Wir haben einen Test gestartet, der die Aktivitäten der Benutzer auf den Boards simuliert. Als die Last unseren Durchschnittswert für den Tag erreichte, haben wir denselben Test wiederholt und einen Instance mit dem Leader PostgreSQL abgeschaltet. Der automatische Failover funktionierte wie erwartet: Patroni wechselte den Leader, der Consul-Template aktualisierte die PgBouncer-Konfiguration und sendete den Reload-Befehl. In unseren Grafana-Diagrammen war zu sehen, dass es Verzögerungen von 20-30 Sekunden gab und eine geringe Anzahl von Fehlern von den Servern, die mit der Verbindung zur Datenbank zusammenhingen. Das ist eine normale Situation, solche Werte sind für unseren Failover akzeptabel und auf jeden Fall besser als ein Ausfall des Dienstes.

Patroni in der Produktion

Am Ende hatten wir folgenden Plan:

  • Deployment des Consul-Templates auf die PgBouncer-Server und dessen Start;
  • Aktualisierung von PostgreSQL auf Version 11.2;
  • Änderung des Cluster-Namens;
  • Start des Patroni-Clusters.

Dabei erlaubt unser Schema, den ersten Punkt praktisch jederzeit umzusetzen; wir können jeden PgBouncer nacheinander außer Betrieb nehmen und das Deployment sowie den Start des Consul-Templates durchführen. So haben wir es auch gemacht.

Für ein schnelles Rollout haben wir Ansible verwendet, da wir alle Playbooks bereits in der Testumgebung geprüft hatten, und die Ausführungszeit des vollständigen Szenarios lag zwischen 1,5 und 2 Minuten pro Shard. Wir konnten alles nacheinander auf jeden Shard ausrollen, ohne unseren Dienst anzuhalten, aber wir müssten jeweils für einige Minuten jeden PostgreSQL abschalten. In diesem Fall könnten die Benutzer, deren Daten sich auf diesem Shard befinden, währenddessen nicht vollumfänglich arbeiten, was für uns inakzeptabel ist.

Die Lösung für diese Situation war eine geplante Wartung, die alle 3 Monate bei uns durchgeführt wird. Dieses Zeitfenster ist für geplante Arbeiten, in dem wir unseren Dienst vollständig ausschalten und die Datenbankinstanzen aktualisieren. Eine Woche vor dem nächsten Wartungszeitraum entschieden wir uns, einfach abzuwarten und uns zusätzlich vorzubereiten. Während der Wartezeit haben wir uns zusätzlich absichert: Für jedes PostgreSQL-Shard haben wir eine Backup-Replikation hochgezogen, um die neuesten Daten im Falle eines Fehlers zu sichern, und haben für jedes Shard eine neue Instanz hinzugefügt, die als neue Replik im Patroni-Cluster fungieren sollte, um den Befehl zum Löschen der Daten zu vermeiden. All dies half, das Risiko von Fehlern zu minimieren.
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Wir haben unseren Dienst neu gestartet, alles funktionierte wie vorgesehen, die Benutzer konnten weiterarbeiten, aber auf den Grafiken bemerkten wir eine anomal hohe Last auf den Consul-Servern.
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

Warum haben wir das in der Testumgebung nicht gesehen? Dieses Problem verdeutlicht sehr gut, dass man dem Prinzip Infrastructure as Code folgen und die gesamte Infrastruktur von der Testumgebung bis zur Produktion weiterentwickeln sollte. Andernfalls ist es sehr einfach, auf ein solches Problem zu stoßen, wie wir es erlebt haben. Was ist passiert? Consul erschien zunächst in der Produktion und dann in den Testumgebungen, und letztendlich war in den Testumgebungen die Version von Consul höher als in der Produktion. In einem der Releases wurde ein CPU-Leck beim Arbeiten mit consul-template behoben. Daher haben wir einfach Consul aktualisiert und dieses Problem somit gelöst.

Patroni-Cluster neu starten

Doch wir haben ein neues Problem festgestellt, von dem wir nicht einmal ahnten. Bei der Aktualisierung 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 Knoten im Consul-Cluster gelangten und ihm den Befehl consul leave gaben, starteten alle Patroni-Cluster neu, und in den Logs sahen wir den folgenden Fehler:

FEHLER: get_cluster
Rückverfolgung (letzter Aufruf letzte):
...
RetryFailedError: 'Frist für den erneuten Versuch überschritten'
FEHLER: Fehler bei der Kommunikation mit DCS
<b>PROTOKOLL: Datenbanksystem wurde heruntergefahren</b>

Der Patroni-Cluster konnte keine Informationen über seinen Cluster erhalten und startete neu.

Um eine Lösung zu finden, wandten wir uns über ein Issue auf GitHub an die Autoren von Patroni. Sie schlugen Verbesserungen für unsere Konfigurationsdateien vor:

consul:
 consul.checks: []
bootstrap:
 dcs:
   retry_timeout: 8

Wir konnten das Problem in der Testumgebung reproduzieren und testeten diese Parameter dort, aber leider funktionierten sie nicht.

Das Problem ist nach wie vor ungelöst. Wir planen, die folgenden Lösungsansätze auszuprobieren:

  • Verwendung des Consul-Agents auf jeder Instanz des Patroni-Clusters;
  • Fehler im Code beheben.

Uns ist der Ort des Fehlers bekannt: Wahrscheinlich liegt das Problem in der Verwendung des Standard-Timeouts, das nicht über 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, wodurch Patroni den Status des Clusters nicht abrufen kann und das gesamte Cluster neu startet.

Glücklicherweise haben wir keine weiteren Fehler festgestellt.

Ergebnisse der 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 Leader und zwei Replikate, um für den Fall eines Split-Brains bei einem Failover abzusichern.
Hochverfügbarer PostgreSQL Cluster + Patroni. Implementierungserfahrung

In der Produktion läuft Patroni seit über drei Monaten. In dieser Zeit hat er uns bereits gerettet. Kürzlich ist in AWS der Leader eines der Cluster ausgefallen, der automatische Failover hat funktioniert und die Benutzer konnten weiterarbeiten. Patroni hat seine Hauptaufgabe erfüllt.

Ein kurzes Fazit zur Verwendung von Patroni:

  • Die Bequemlichkeit der Konfigurationsänderung. Es reicht aus, die Konfiguration auf einer Instanz zu ändern, und sie wird in den gesamten Cluster übertragen. Falls ein Neustart zur Anwendung der neuen Konfiguration erforderlich ist, wird Patroni dies mitteilen. Patroni kann das gesamte Cluster mit einem einzigen Befehl neu starten, was ebenfalls sehr praktisch ist.
  • Der automatische Failover funktioniert und hat uns bereits gerettet.
  • Aktualisierung von PostgreSQL ohne Ausfallzeiten der Anwendung. Zuerst müssen die Replikate auf die neue Version aktualisiert werden, dann den Leader im Patroni-Cluster wechseln und den alten Leader aktualisieren. Dabei findet das notwendige Testen des automatischen Failover statt.

Quelle: habr.com

60GB SSD 8Gb DDR4