Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Heutzutage ist hohe Verfügbarkeit von Diensten überall und jederzeit erforderlich, nicht nur in großen, teuren Projekten. Vorübergehend nicht erreichbare Websites mit der Meldung „Entschuldigung, Wartungsarbeiten“ sind zwar noch anzutreffen, lösen jedoch meist nur ein mildes Lächeln aus. Fügen wir dem das Leben in der Cloud hinzu, wo für den Start eines zusätzlichen Servers lediglich ein API-Aufruf nötig ist, ohne dass man sich um die „Hardware“-Betriebsführung kümmern muss. Und es bleiben keine Ausreden übrig, warum ein kritisches System nicht zuverlässig unter Verwendung von Clustering-Technologien und Redundanz aufgebaut wurde.

Wir werden erläutern, welche Lösungen wir zur Gewährleistung der Zuverlässigkeit von Datenbanken in unseren Diensten in Betracht gezogen haben und zu welchen Ergebnissen wir gekommen sind. Zudem gibt es eine Demo mit weitreichenden Erkenntnissen.

Legacy in der Architektur von Hochverfügbarkeitslösungen

Das ist noch deutlicher bei der Entwicklung verschiedener Open-Source-Systeme zu sehen. Ältere Lösungen mussten Hochverfügbarkeitstechnologien hinzufügen, als die Nachfrage stieg. Die Qualität dieser Lösungen war unterschiedlich. Lösungen der neuen Generation legen Hochverfügbarkeit in den Mittelpunkt ihrer Architektur. Zum Beispiel positioniert MongoDB Cluster als die Hauptnutzungsoption. Der Cluster ist horizontal skalierbar, was einen starken Wettbewerbsvorteil dieser DBMS darstellt.

Kehren wir zu PostgreSQL zurück. Es gehört zu den ältesten und beliebtesten Open-Source-Projekten, dessen erster Release 1995 stattfand. Das Projektteam betrachtete lange Zeit Hochverfügbarkeit nicht als eine Aufgabe, die vom System gelöst werden sollte. Daher wurde die Replikationstechnologie zur Erstellung von Datensicherungen erst mit Version 8.2 im Jahr 2006 integriert, allerdings als Dateibasiert (log shipping). 2010 kam mit Version 9.0 die Streaming-Replikation hinzu, die die Grundlage für die Bildung unterschiedlichster Cluster ist. Dies überrascht tatsächlich viele Menschen, die mit PostgreSQL nach Enterprise SQL oder modernen NoSQL-Systemen vertraut werden – die Standardlösung aus der Community ist einfach ein Paar Master-Replica mit synchroner oder asymmetrischer Replikation. Dabei erfolgt im Standard die Umschaltung des Masters manuell, und die Frage der Kundenumschaltung wird ebenfalls selbstständig angeboten zu lösen.

Wie wir entschieden haben, ein zuverlässiges PostgreSQL zu entwickeln und was wir dafür ausgewählt haben

Dennoch wäre PostgreSQL nicht so beliebt geworden, wenn es nicht eine riesige Anzahl an Projekten und Werkzeugen gäbe, die helfen, eine ausfallsichere Lösung zu schaffen, die keine ständige Aufmerksamkeit erfordert. In der Cloud. Mail.ru Cloud Solutions (MCS) wurden seit dem Start von DBaaS Einzelserver PostgreSQL und Master-Replica-Paare mit asynchroner Replikation angeboten.

Natürlich wollten wir allen das Leben erleichtern und eine PostgreSQL-Installation zur Verfügung stellen, die als Grundlage für hochverfügbare Dienste dienen konnte, ohne dass man ständig darauf achten und nachts aufstehen musste, um einen Failover durchzuführen. In diesem Bereich gibt es sowohl alte bewährte Lösungen als auch eine Generation neuer Tools, die die neuesten Entwicklungen nutzen.

Heute besteht das Problem der hohen Verfügbarkeit nicht in der Redundanz (das ist selbstverständlich), sondern im Konsens — dem Algorithmus zur Wahl eines Führers (Leader election). Häufig treten große Ausfälle nicht aufgrund eines Mangels an Servern auf, sondern aufgrund von Konsensproblemen: kein neuer Führer wurde gewählt, es gibt zwei Führer in verschiedenen Rechenzentren usw. Ein Beispiel — der Ausfall im MySQL-Cluster von Github — sie schrieben. einen detaillierten Postmortem..

Die mathematische Basis für diese Frage ist sehr ernst. Auf der einen Seite gibt es. das CAP-Theorem,das theoretische Einschränkungen für den Bau von HA-Lösungen auferlegt, auf der anderen Seite mathematisch bewiesene Algorithmen zur Bestimmung des Konsenses, wie. Paxos und RaftAuf dieser Basis gibt es recht populäre DCS (dezentrale Konsenssysteme) — Zookeeper, etcd, Consul. Daher sollte man äußerst vorsichtig sein, wenn ein Entscheidungssystem einen eigenen Algorithmus verwendet, der selbst geschrieben wurde. Nach der Analyse einer Vielzahl von Systemen haben wir uns für Patroni entschieden — ein Open-Source-System, das hauptsächlich von der Firma Zalando entwickelt wird.

Als lyrische Einschub möchte ich sagen, dass wir auch Multi-Master-Lösungen in Betracht gezogen haben, also Cluster, die horizontal für Schreibvorgänge skalierbar sind. Wir haben uns jedoch aus zwei Hauptgründen entschieden, ein solches Cluster nicht zu erstellen. Erstens haben solche Lösungen eine hohe Komplexität und damit mehr verwundbare Stellen. Es wird schwierig sein, eine stabile Lösung für alle Fälle zu finden. Zweitens hört PostgreSQL in diesem Fall auf, rein (native) zu sein; einige Funktionen würden nicht verfügbar sein, und bei einigen Anwendungen könnten beim Betrieb versteckte Fehler auftreten.

Patroni

Also, wie funktioniert Patroni? Die Entwickler haben sich entschieden, das Rad nicht neu zu erfinden und schlugen vor, eines der bewährten DCS-Lösungen als Grundlage zu verwenden. Alle Fragen zur Synchronisierung der Konfigurationen, zur Wahl des Führers und zum Quorum werden ihm überlassen. Wir haben uns für etcd entschieden.

Anschließend kümmert sich Patroni um die korrekte Anwendung aller Einstellungen auf PostgreSQL und um die Konfiguration der Replikation sowie um die Ausführung der Befehle für Switchover und Failover (das heißt, für geplante und ungeplante Umschaltung des Masters). Im konkreten Fall in der MCS-Cloud kann ein Cluster aus einem Master, einer synchronen Replik und einer oder mehreren asynchronen Replikas erstellt werden. Das Vorhandensein einer synchronen Replik sichert die Datensicherheit auf mindestens 2 Servern, und genau diese Replik wird der Hauptkandidat für den Master sein.

Da etcd auf denselben Servern bereitgestellt wird, wird eine Anzahl von 3 oder 5 Servern empfohlen, um den optimalen Quorumwert zu erreichen. Ein solches Cluster ist horizontal für Lesevorgänge skalierbar (über das Schreiben habe ich zuvor geschrieben). Dennoch sollte man berücksichtigen, dass asynchrone Replikas dazu neigen, hinterherzuhinken, besonders bei hoher Last.

Die Verwendung solcher Replikas für Lesevorgänge (hot standby) ist gerechtfertigt für Berichts- oder Analyseaufgaben und entlastet den Master-Server.

Wenn Sie ein solches Cluster selbst aufbauen möchten, benötigen Sie:

  • 3 oder mehr Server vorbereiten, IP-Adressen und Firewall-Regeln zwischen ihnen konfigurieren;
  • Pakete für die Dienste etcd, Patroni, PostgreSQL installieren;
  • Das etcd-Cluster konfigurieren;
  • Den Dienst patroni für die Arbeit mit PostgreSQL konfigurieren.

Das heißt, insgesamt müssen etwa ein Dutzend Konfigurationsdateien richtig erstellt werden, ohne dass Fehler auftreten. Dazu ist die Nutzung eines Configuration Management Tools, wie zum Beispiel Ansible, auf jeden Fall sinnvoll. Dabei fehlt weiterhin ein hochverfügbarer TCP-Load-Balancer. Dessen Erstellung ist ein separates Projekt.

Für diejenigen, die einen fertigen Cluster benötigen, aber nicht selbst damit herumbasteln möchten, haben wir versucht, das Leben zu erleichtern und einen fertigen Cluster auf Patroni in unserer Cloud bereitgestellt, den man kostenlos testen kann. Neben dem Cluster selbst haben wir Folgendes erstellt:

  • Einen TCP-Load-Balancer; dieser verweist immer auf den aktuellen Master, entweder synchron oder asynchron, je nach Port;
  • Eine API zum Wechseln des aktiven Masters von Patroni.

Diese kann sowohl über die MCS-API als auch über die Web-Konsole angesteuert werden.

Demo

Um die Möglichkeiten des PostgreSQL-Clusters in der MCS-Cloud zu testen, wollen wir sehen, wie sich eine Live-Anwendung bei Problemen mit der Datenbank verhalten wird.

Im Folgenden finden Sie den Code für eine Anwendung, die künstliche Ereignisse protokollieren und diese auf dem Bildschirm anzeigen wird. Im Falle von Fehlern wird sie diese melden und ihre Arbeit in einer Schleife fortsetzen, bis wir sie mit der Tastenkombination Strg + C anhalten.

from __future__ import print_function

from datetime import datetime
from random import randint
from time import sleep
import psycopg2


def main():
    try:
        connection = psycopg2.connect(user = "admin",
                                      password = "P@ssw0rd",
                                      host = "89.208.87.38",
                                      port = "5432",
                                      database = "myproddb")

        cursor = connection.cursor()
        cursor.execute("SELECT version();")
        record = cursor.fetchone()
        print("Verbindung geöffnet zu", record[0])

        cursor.execute(
            "INSERT INTO log VALUES ({});".format(randint(1, 10000)))
        connection.commit()
        cursor.execute("SELECT COUNT(event_id) from log;")
        record = cursor.fetchone()
        print("Wert protokolliert, Gesamtanzahl: {}".format(record[0]))
    except Exception as error:
        print ("Fehler beim Verbinden mit PostgreSQL", error)
    finally:
        if connection:
            cursor.close()
            connection.close()
            print("Verbindung geschlossen")


if __name__ == '__main__':
    try:
        while True:
            try:
                print(datetime.now())
                main()
                sleep(3)
            except Exception as e:
                print("Fehler erfasst:n", e)
                sleep(1)
    except KeyboardInterrupt:
        print("Beenden")

Die Anwendung benötigt PostgreSQL zum Funktionieren. Lassen Sie uns einen Cluster in der MCS-Cloud unter Verwendung der API erstellen. In einem normalen Terminal, in dem das Token für den Zugriff auf die API in der Variable OS_TOKEN gespeichert ist (dies kann mit dem Befehl openstack token issue erhalten werden), geben wir die folgenden Befehle ein:

Wir erstellen einen Cluster:

cat < pgc10.json
{"cluster":{"name":"postgres10","allow_remote_access":true,"datastore":{"type":"postgresql","version":"10"},"databases":[{"name":"myproddb"}],"users":[{"databases":[{"name":"myproddb"}],"name":"admin","password":"P@ssw0rd"}],"instances":[{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}},{"key_name":"shared","availability_zone":"DP1","flavorRef":"d659fa16-c7fb-42cf-8a5e-9bcbe80a7538","nics":[{"net-id":"b91eafed-12b1-4a46-b000-3984c7e01599"}],"volume":{"size":50,"type":"DP1"}}]}}
EOF

curl -s -H "X-Auth-Token: $OS_TOKEN" 
-H 'Accept: application/json' 
-H 'Content-Type: application/json' 
-d @pgc10.json https://infra.mail.ru:8779/v1.0/ce2a41bbd1434013b85bdf0ba07c770f/clusters

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Wenn der Cluster den STATUS ACTIVE erreicht, erhält jedes Feld die aktuellen Werte — der Cluster ist bereit.

Im GUI:

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Versuchen wir, uns zu verbinden und eine Tabelle zu erstellen:

psql -h 89.208.87.38 -U admin -d myproddb
Passwort für Benutzer admin:
psql (11.1, Server 10.7)
Tippen Sie "help" für Hilfe.

myproddb=> CREATE TABLE log (event_id integer NOT NULL);
CREATE TABLE
myproddb=> INSERT INTO log VALUES (1),(2),(3);
INSERT 0 3
myproddb=> SELECT * FROM log;
 event_id
----------
        1
        2
        3
(3 Zeilen)

myproddb=>

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Im Anwendungsbereich geben wir die aktuellen Einstellungen für die Verbindung zu PostgreSQL an. Wir werden die Adresse des TCP-Loadbalancers angeben, wodurch ein manuelles Umschalten auf die Adresse des Masters entfällt. Lassen Sie uns es starten. Wie zu sehen ist, werden die Ereignisse erfolgreich in der Datenbank protokolliert.

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Geplantes Umschalten des Masters

Jetzt testen wir die Funktionalität unserer Anwendung beim geplanten Umschalten des Masters:

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Wir beobachten die Anwendung. Wir sehen, dass die Arbeit der Anwendung tatsächlich unterbrochen wird, aber dies dauert nur wenige Sekunden, in diesem speziellen Fall maximal 9.

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

Maschineausfall

Jetzt versuchen wir, einen Ausfall der virtuellen Maschine des aktuellen Masters zu simulieren. Man könnte die virtuelle Maschine einfach über die Horizon-Oberfläche herunterfahren, aber das wäre ein reguläres Herunterfahren. Ein solcher Wechsel würde von allen Diensten, einschließlich Patroni, verarbeitet werden.

Wir benötigen jedoch ein unvorhersehbares Herunterfahren. Daher habe ich unsere Administratoren gebeten, die virtuelle Maschine — den aktuellen Master — testweise unerwartet abzuschalten.

Wie wir einen zuverlässigen PostgreSQL-Cluster mit Patroni aufgebaut haben

In der Zwischenzeit arbeitete unsere Anwendung weiter. Natürlich kann ein solches Notumschalten des Masters nicht unbemerkt bleiben.

2019-03-29 10:45:56.071234
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu hergestellt, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 453
Verbindung geschlossen
2019-03-29 10:45:59.205463
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu hergestellt, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 454

Verbindung geschlossen
2019-03-29 10:46:02.661440
Fehler beim Verbinden zum PostgreSQL-Server, Verbindung unerwartet geschlossen
        Dies bedeutet wahrscheinlich, dass der Server abnormal beendet wurde
        bevor oder während die Anfrage bearbeitet wurde.

Gefangener Fehler:
 lokale Variable 'connection' vor Zuweisung referenziert
……………………………………………………….. - hier sind einige Fehler aufgetreten
2019-03-29 10:46:30.930445
Fehler beim Verbinden zum PostgreSQL-Server, Verbindung unerwartet geschlossen
        Dies bedeutet wahrscheinlich, dass der Server abnormal beendet wurde
        bevor oder während die Anfrage bearbeitet wurde.

Gefangener Fehler:
 lokale Variable 'connection' vor Zuweisung referenziert
2019-03-29 10:46:31.954399
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu hergestellt, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 455
Verbindung geschlossen
2019-03-29 10:46:35.409800
Verbindung zu PostgreSQL 10.7 auf x86_64-pc-linux-gnu hergestellt, kompiliert von gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-36), 64-Bit
Wert protokolliert, Gesamtsumme: 456
Verbindung geschlossen
^Cexit

Wie man sieht, konnte die Anwendung ihre Arbeit in weniger als 30 Sekunden fortsetzen. Ja, einige Benutzer des Dienstes werden die Probleme bemerken. Allerdings handelt es sich um einen schwerwiegenden Serverausfall, der nicht häufig vorkommt. Zudem hätte eine Person (Administrator) nicht so schnell reagieren können, es sei denn, sie hat bereitwillig mit einem Umschalt-Script an der Konsole gesessen.

Ausgabe

Meiner Meinung nach bietet ein solcher Cluster enormes Potenzial für Administratoren. Im Grunde werden schwerwiegende Ausfälle und Ausfälle von Datenbankservern für die Anwendung und damit für den Benutzer nicht erkennbar sein. Es wird nicht nötig sein, hastig etwas zu reparieren und vorübergehende Konfigurationen, Server usw. zu verwenden. Und wenn man eine solche Lösung als fertigen Cloud-Service nutzt, wird man keine Zeit mit der Vorbereitung aufwenden müssen. Man kann interessante Dinge tun.

Quelle: habr.com

60GB SSD 8Gb DDR4