Schöner Replizieren mit PostgreSQL

replizieren-title

PostgreSQL verfügt per se über ein Hochverfügbarkeitsfeature, welches auf dem Prinzip der Datenreplikation über das Transaktionslog basiert. Beim Log Shipping Standby Server werden Daten, je nach gewählter Methode, über die WAL-Segmente bzw. die WAL-Buffer von einem Datenbankcluster auf einen anderen übertragen. Dabei ist zu beachten, dass immer der komplette Cluster repliziert wird, also mit allen Datenbanken, so wie man es schon beim Backup und Restore antrifft.

Die Umsetzung dieses Konzeptes ist, obwohl es das dokumentiere Standardverfahren zur Gewährleistung der Verfügbarkeit ist, eher „sperrig". Immer wieder müssen im Betrieb manuelle Eingriffe durchgeführt werden. Automatismen sind leider eher selten zu finden. So müssen beispielsweise beim Rollentausch in einem Cluster Konfigurationsdateien editiert werden, was umständlich und fehlerträchtig ist.

Hier empfehlen sich die Open Source-Tools repmgr und pgbouncer. Mit dem ersten lässt sich ein großer Teil der Replikationsadministration unter PostgreSQL übernehmen, während das zweite ein Connection Pooler für PostgreSQL ist, der Verbindungen umleiten, beenden, unterbinden und wieder zulassen kann. Beide Werkzeuge sind über das normale Linux-Repository erhältlich und auch nur unter Linux lauffähig.

Einige Voraussetzungen des repmgr (manche davon optional):

  • Passwortfreier Wechsel zwischen den Systemen als User postgres
  • Verwaltungsdatenbank und -user, oft ebenfalls repmgr genannt
  • Service repmgrd zum automatisierten Umschalten
  • Weiterer Host für Witness-Server

Und von pgbouncer:

  • separater Host; unabhängig von den Datenbankhosts
  • Separater User pgbouncer
  • Skript bzw. Konfiguration zum Weiterleiten auf den primären Host

Wir gehen von einem bereits existierenden und einem neu aufzusetzenden Datenbankcluster aus. Auf dem zukünftigen Standby-System bereiten wir das PGDATA-Verzeichnis sowie die Tablespace-Verzeichnisse vom Pfad identisch, aber leer, wie auf dem Primary vor.

Die Datei repmgr.conf wird auf beiden Seiten fast identisch eingerichtet. Über einen libpq-Connection-String wird eine Verbindung zum jeweiligen Replikations-Knoten angegeben, etwa:

conninfo='host=primary user=repmgr port=5432 dbname=repmgr'  

bzw.

conninfo='host=standby user=repmgr port=5432 dbname=repmgr'  

Weiterhin empfiehlt sich zum Überwachen des bald erfolgenden Klonungsprozesses der Parameter

pg_basebackup_options="--progress"  

Den bereits laufenden Cluster können wir zum Primary erklären

repmgr primary register  

Auf dem Standby-Server können wir nun den Klonprozess starten. (Hinweis: Zum Prüfen des voraussichtlichen Erfolges bietet sich vorab die Option --dry-run an)

repmgr -h primary -U repmgr -d repmgr standby clone -c  

Nach Abschluss des Klonens wird schließlich auch die Standby-Seite registriert:

repmgr standby register  

Um sich gegen den Ausfall eines kompletten Rechenzentrums abzusichern, sollten Primary- und Standby-Seite immer in verschiedenen Rechenzentren untergebracht sein. Aus Sicht des Standby-Systems kann ein Ausfall des Netzwerkes dazu führen, dass dieses System glaubt, selbst die führende Rolle übernehmen zu müssen, obwohl der Primary-Server weiter einwandfrei arbeitet. Dies kann zu einer fatalen Split-Brain-Situation führen, in der beide Seiten aktiv werden.

Hier kann ein zusätzlicher Witness-Server Abhilfe schaffen. Dabei handelt es sich um einen separaten Datenbankcluster, der noch Kontakt zum Primary-Server hat, aber nicht an der Replikation selbst teilnimmt, sondern dazu dient, mit beiden Replikationspartnern Kontakt zu halten.

Der Witness-Server wird über eine Verbindung zum Primary gestartet:

repmgr witness register -h primary -U repmgr 
repmgr daemon start  

Status und Typ der beteiligten Server können über das folgende Kommando abgefragt werden:

repmgr cluster show  

Es gibt nun für das Umschalten auf den Standby mehrere Möglichkeiten:

  • manuell, bei einem Ausfall des Primary, wenn kein Failover oder Witness-Server verwendet wird:
repmgr standby promote  
  • kontrolliert, etwa zum Test oder zu Wartungszwecken. Auch hier gibt es die Option --dry-run zum Prüfen des voraussichtlichen Erfolgs.
repmgr standby switchover  

Eine Wiedereingliederung des alten Primary als Standby kann auch erfolgen. Falls der Primary so unvermittelt beendet wurde, dass der Standby vor der Beförderung noch nicht aufholen konnte, muss der zukünftige Standby mit der Option --force-rewind "zurückgespult" werden, um beide wieder auf einen konsistenten Stand zu bringen.

repmgr -d 'host=standby user=repmgr dbname=repmgr port=5432' node rejoin --force-rewind  

Nun fehlt noch der Fall eines Failovers. Hierfür wird der systemd-Dienst repmgrd zwingend benötigt, da dieser den Clusterstatus überwacht. Weiterhin muss beidseitig in der repmgr.conf jeweils der folgende Parameter gesetzt sein:

failover='automatic'  
promote_command='<Pfad zu promote.sh>'  

Das erwähnte Skript promote.sh soll für eine Beförderung des Standbys, sowie für eine Neukonfiguration vom pgbouncer sorgen. Eine Grundkonfiguration vom pgbouncer existiert bereits in der Datei /etc/pgbouncer/pgbouncer.ini. U.a. sind diese Parameter zu prüfen:

listen_port = 6432              # Port für pgbouncer 
... 
%include /etc/pgbouncer/pgbouncer-other.ini  

In der letzten include-Datei legen wir die Verbindungsinformationen zu der momentanen DB auf dem Primary ab. Der Inhalt besteht wieder aus einem libpq-ähnlichen Connection-String, mit dem wir uns zu einer Datenbank ordix verbinden:

[databases] 
ordix= host=primary port=5432 dbname=ordix  

Das Skript promote12.sh kann folgenden Aufbau haben und muss auf beiden Seiten vorhanden sein:

#!/bin/bash 
# zum Debugging im Fehlerfall 
set -e -u -x 
exec 2>/tmp/promote12.txt 
PGBOUNCER_OTHER_NEW="/tmp/pgbouncer-other.ini" 
PGBOUNCER_HOST="pgbouncer" 
DATABASE="ordix" 
# Environment setzen 
. ~postgres/pg12_env.sh 
# pgbouncer anhalten 
psql -U postgres -h ${PGBOUNCER_HOST} -p 6432  pgbouncer -tc "pause ${DATABASE}" 
# Server befördern  
repmgr standby promote --log-to-file 
# Neue pgbouncer-other.ini generieren 
cat <<EOF >${PGBOUNCER_OTHER_NEW} 
[databases] 
${DATABASE}= host=$(hostname -f) port=5432 dbname=${DATABASE} 
EOF 
# Datei zum pgbouncer-Host kopieren (maximal 10 Versuche) 
for tries in {1..10} 
do 
 scp -v ${PGBOUNCER_OTHER_NEW} \  
${PGBOUNCER_HOST}:/etc/pgbouncer/pgbouncer-other.ini 
 if [ $? -eq 0 ] 
   then 
      echo $tries 
      break 
 fi 
done 
if [ $tries -ge 10 ] 
  then 
    echo "Fehlschlag" 
    exit 255 
fi 
# pgbouncer neu laden und wieder starten 
psql -U postgres -h ${PGBOUNCER_HOST} -p 6432 pgbouncer -tc "reload" 
psql -U postgres -h ${PGBOUNCER_HOST} -p 6432 pgbouncer -tc "resume ${DATABASE}" 
# Alte lokale Datei entfernen 
rm ${PGBOUNCER_OTHER_NEW}  

Öffnet man jetzt eine Verbindung zu pgbouncer auf Port 6432, so leitet dieser auf den Primary unter Port 5432 weiter. Eine Ausnahme stellt die explizite Angabe des Namens pgbouncer anstelle des Datenbanknamens (statt z.B. ordix) dar. In so einem Fall landet man auf einer Admin-Konsole, von der aus man administrative Tasks wie z.B. pausedatabase absetzen kann. Dort lässt sich prüfen, auf welchem Server man gelandet ist:

user1 > psql 'host=pgbouncer port=6432 user=ordix dbname=ordix' 

psql (ordix) >  SELECT inet_server_addr(); 
 inet_server_addr 
------------------ 
 192.168.78.139# <- IP-Adresse des Servers (Primary)  

Beim Switch- oder Failover wird nun das Skript promote12.sh aufgerufen, welches folgende Schritte ausführt:

  • pgbouncer-Sessions beenden
  • den Standby promoten
  • die include-Datei ersetzen und die Konfiguration neu einlesen

Hinweis: Vor PostgreSQL 12 konnte man das Skript noch unter dem Parameter service_promote_command eintragen und promote_command wie ursprünglich belassen. Da sich die Methode zur Beförderung mit Version 12 aber geändert hat, ist das nun nicht mehr so möglich.

Wenn die Session aus dem oberen Abschnitt die ganze Zeit verbunden war, so lässt sich nun beobachten, dass sich die IP-Adresse des Servers ändert, weil in der include-Datei der Hostname ausgetauscht wurde.

psql (ordix) > SELECT inet_server_addr(); 
 inet_server_addr 
------------------ 
 192.168.78.139# <- IP-Adresse alter Primary 
(1 Zeile) 
psql (ordix) > SELECT inet_server_addr(); 
FATAL:  Verbindung wird abgebrochen aufgrund von Anweisung des Administrators 
ERROR:  server conn crashed? 
Server beendete die Verbindung unerwartet 
Das heißt wahrscheinlich, dass der Server abnormal beendete 
bevor oder während die Anweisung bearbeitet wurde. 
Die Verbindung zum Server wurde verloren.  Versuche Reset: 
Erfolgreich. 
psql (ordix) > SELECT inet_server_addr(); 
 inet_server_addr 
------------------ 
 192.168.78.133# <- IP-Adresse neuer Primary 
(1 Zeile)  

Fazit

Das Repertoire vom repmgr umfasst Tools, mit denen das Einrichten, Überwachen und Umschalten zwischen verschiedenen Replikationskonten sehr komfortabel möglich ist: Die Nutzung der werkseitigen Tools ist wesentlich komplexer und aufwändiger

Die Replikation und die Anbindung an den pgbouncer funktioniert, und auch das Umschalten ist recht komfortabel. Die Anbindung erfordert jedoch mehr Aufwand bei der Verskriptung und kann dadurch anfälliger für Fehler sein.  

Glossar

RDBMS: Steht für Relationales Datenbankmanagementsystem.
Ein RDBMS verwaltet Datenbanken mitsamt ihren Objekten, sorgt u.a. für die Einhaltung von Constraints (Randbedingungen) und Isolation gleichzeitig stattfindender Datenabfragen.

(Datenbank-)Cluster: Stellt unter PostgreSQL das Äquivalent zu einer
Datenbankinstanz dar. In einem Cluster sind immer mehrere Datenbanken vorhanden, die voneinander unabhängige Daten besitzen, aber dieselben Tablespaces und User verwenden können.

WAL: "Write-Ahead Log" ist unter PostgreSQL die Entsprechung für Transaktionsprotokolle. Datenänderungen werden in WALs protokolliert, um sie im Fehlerfall wiederholbar zu machen und dienen bei der Replikation zur Datenübertragung zwischen einzelnen Datenbankclustern. Bei WAL-Segmenten handelt es sich um Dateien im Filesystem, bei WAL-Buffern um eine Memory-Struktur.

Primary: Der Clusterknoten, auf den schreibend zugegriffen werden kann. Nur ein solcher Knoten ist erlaubt.

Standby: Passiver Clusterknoten, der durch den Primary synchron gehalten wird und auch lesend zugreifbar sein kann. Es kann mehrere derartige Knoten geben.

Promoten / Demoten: Wenn ein Standby zum Primary erklärt wird, so wird er promotet („befördert"). Umgekehrt wird ein Primary, der zum Standby wird, demotet („degradiert").

Switchover: Ist der kontrollierte Tausch der Clusterrollen von Primary und Standby zum jeweils anderen Typ, also wechselseitiges Promoten und Demoten

Failover: Ist das automatische Promoten des Standbys, falls der Primary ausfällt. Ein automatisches Demoten des alten Primaries muss bzw. kann dabei nicht zwingend stattfinden.

libpq(-Connectionstring): Bei libpq handelt es ich um die C-Programmierschnittstelle für PostgreSQL. Die Datenbankverbindung wird dabei über eine Reihe von Schlüsselworten hergestellt, z.B. Datenbank-, Benutzername und Host.

Quellen

PostgreSQL Wiki (https://www.postgresql.org/docs/)

repmgr Dokumentation (https://repmgr.org/docs/current/)

pgbouncerDokumentation (https://www.pgbouncer.org/config.html)

Setup a PostgreSQL cluster with repmgr and pgbouncer (https://blog.raveland.org/post/postgresql_repmgr_pgbouncer_en/) 

By accepting you will be accessing a service provided by a third-party external to https://blog.ordix.de/