PostgreSQL - Warm-Standby Datenbank mit pg_standby Mini HowTo
Eine Möglichkeit eine relativ aktuelle Kopie einer Postgres-DB auf einen anderem Rechner vorzuhalten, ist die Warm-Standby-DB. Während der Master laufend seine Transaktionslogs archiviert, spielt der Slave die Transaktionslogs ein, sobald sie vorhanden sind. Dafür gibt es das kleine C-Programm pg_standby im contrib-Verzeichnis, wenn man die Postgres-Sourcen installiert hat. Ich empfehle grundsätzlich Postgres selber zu kompilieren. Die Pakete der Distributionen sind meist uralt und es hat sich zwischen 8.1 und 8.3 soviel getan, das man fast grob fahrlässig handelt, wenn man noch 8.1 einsetzt ;-)
Grundvoraussetzung bzw. Empfehlung (die man ernst nehmen sollte) ist, das Master und Slave mit der gleichen Postgres-Version arbeiten und am Besten mit Version 8.3 oder höher. Wenn man die Binaries und Libs vom Master kopiert und für den Slave verwendet, hat man da schon mal eine Sorge weniger (ausser man nimmt eh die Pakete der Distribution). Auch die Verzeichnisstruktur sollte auf beiden Rechnern gleich sein, da z.B. ein CREATE TABLESPACE auf dem Slave die gleichen Pfade vorfinden muss. Bei einem Update der Minor-Relase (also z.B. von 8.3.5 auf 8.3.6) sollte man den Slave zuerst updaten. Die neue Version ist sicherlich besser in der Lage, Logs einer älteren Version zu lesen als umgekehrt.
Zunächst bereitet man am besten Master und Slave mal vor - also Verzeichnisstruktur, Binaries usw. das man beide unabhängig voneinander starten könnte.
Die von mir in dem folgenden Beispiel verwendete Verzeichnisstruktur, mag vielleicht etwas eigenwillig erscheinen und findet sich auch auf keiner Distribution so, aber sie hat den großen Vorteil, das ich beliebig viele unterschiedliche Postgres-Instanzen in unterschiedlichen Versionen parallel auf einem Rechner betreiben kann. Damit sich das Ganze nicht ins Gehege kommt, verwende ich immer einen beliebigen Namesraum. Jede Postgres-Instanz heißt also z.B. so, wie eine Figur aus der Muppetshow oder ein Darsteller aus Star Trek (im Folgenden ist das die Variable $DBNAME)...
In der Master-DB kann man schon mal Folgendes eintragen ($DBNAME ist in den folgenden Abschnitten natürlich durch den jeweiligen Instanz-Namen zu ersetzen!):
# - Archiving -
archive_mode = on # allows archiving to be done
# (change requires restart)
archive_command = '/data/pgsql/bin/$DBNAME/pg_archive_xlog %p %f' # command to use to archive a logfile segment
archive_timeout = 60 # force a logfile segment switch after this
# time; 0 is off
Mit archive_mode=on schalten wir den Archivierungsmodus ein, d.h. Postgres hebt die Transaktionslogs erstmal auf. Nur wenn archive_command erfolgreich war und das Log an die finale Destination kopiert werden konnte, wird das Transaktionslog gelöscht. Es können also mal eine ganze Reihe Transaktionslogs auflaufen, wenn die Netzverbindung z.B. gestört ist und der Zielserver nicht erreicht werden kann. archive_timeout gibt an, wann spätestens ein Logswitch durchgeführt werden soll. Per Default ist ein Logfile 16 MB groß und bei einer Datenbank, in die nicht viel geschrieben wird, kann das schon mal eine ganze Weile dauern, bis die 16 MB voll sind. Man muss hier selbst entscheiden, wie weit man das Zeitfenster öffnen will. 60 Sek. sind ok, wenn man eine schnelle Leitung hat und wenn die Daten auf dem Standby-System einigermaßen aktuell sein sollen. Weniger als 60 Sek. könnten eventl. problematisch werden. Aber Obacht: Die Transaktionslogs sind immer 16 MB groß! Das häuft sich innerhalb von wenigen Stunden ziemlich an, wenn die Dinger nicht wegkommen! Man sollte das deshalb überwachen und per Skript guggen, ob sich die Logs nicht anhäufen und man eventl. eingreifen muss.
Die Master-DB jetzt aber erstmal noch nicht durchstarten.
Das in der postgresql.conf verwendete pg_archive_xlog-Skript sieht so aus:
#!/bin/bash
#
# This script ships the master transaction logs (WAL)
# to the standby database
#
PGINSTANCE=$DBNAME
# Redirect output
exec 1>/data/pgsql/logs/$PGINSTANCE/pg_archive_xlog.log 2>&1
# Some vars
RSYNC_RSH="/usr/bin/ssh -i /home/postgres/.ssh/id_dsa"
XLOG_FROM=$1 # This is the full path to xlog file incl. file name
XLOG_FILENAME=$2 # This is only the file name of the xlog
DEST_XLOG_DIR="/data/pgsql/dumps/$PGINSTANCE/pg_xlog_master"
DEST_USER="postgres"
DEST_HOST="standby.$PGINSTANCE.tauceti.net
# Does source xlog file exist?
if [ ! -f "$XLOG_FROM" ]
then
echo "No such file/path: $XLOG_FROM"
exit 1
fi
# Do we have a dest file name?
if [ -z "$XLOG_FILENAME" ]
then
echo "No destination file name! Please provide one!"
exit 1
fi
# Tranfer file
/usr/bin/rsync -av --rsh=ssh $XLOG_FROM $DEST_USER@$DEST_HOST:"$DEST_XLOG_DIR/$XLOG_FILENAME"
if [ $? -ne 0 ]
then
echo "rsync not successfull!"
exit 1
fi
# Always return exit status 0 if log transfer finished successfully
exit 0
Das Skript an sich ist nicht sonderlich schwierig. Sobald ein Transkationslog voll ist oder der archive_timeout zuschlägt, ruft Postgres dieses Skript auf und übergibt den vollen Pfad inkl. Dateinamen %p und nur den Dateinamen %f an das Skript. Dieses kopiert dann das Transaktionslog auf den Destination-Server. Das Skript verwendet rsync über ssh, um das Log auf den Zielhost zu kopieren. Dazu habe ich mit ssh-keygen -t dsa ein Schlüsselpaar ohne Passwort für den User postgres erstellt (unter dem die Postgres-Instanz i.d.R. läuft). Den Public-Key kopiert man in die authorized_keys vom User postgres (oder wie auch immer der User für die Postgres heißen mag) auf dem Slave-Rechner. Dann loggt man sich einmal als User postgres vom Master aus auf den Slave ein mit dem Key also z.B. /usr/bin/ssh -i /home/postgres/.ssh/id_dsa postgres@standby.$DBNAME.tauceti.net. Das ist nötig, damit man den Host-Key bestätigen kann. Das Ganze kann man auch über NFS machen. Aber NFS ist oft nicht so zuverlässig, wie man sich das wünschen würde. Muss jeder selber entscheiden...
Dann machen wir in der /etc/hosts vom Master einen Eintrag für den Slave. Man kann das natürlich auch per DNS machen, aber mit der hosts geht man auf Nummer sicher, wenn der DNS mal weg sein sollte (XXX natürlich durch die IP des Slave-Rechners ersetzen und tauceti.net natürlich auch durch die eigene Domain ersetzen):
# Database standby
XXX.XXX.XXX.XXX standby.$DBNAME.tauceti.net
Auf dem Slave legen wir jetzt ein Verzeichnis an, in dem das pg_archive_xlog-Skript die Transaktionslogs kopieren soll:
mkdir /data/pgsql/dumps/$DBNAME/pg_xlog_master
chown postgres.postgres /data/pgsql/dumps/$DBNAME/pg_xlog_master
chmod 700 /data/pgsql/dumps/$DBNAME/pg_xlog_master
Sollte sich die Master DB schon im Archive-Modus befinden (also die DB produziert Transaktionslogs), ist's ok ;-) Ansonsten sollte man die Master DB jetzt durchstarten, damit der Archive-Modus (archive_mode = on) aktiv wird (Gut... Durchstarten sollte man natürlich nur, wenn die Instanz nicht gerade benutzt wird und der Cheffe anschließend kommt, weil seine Reports hinüber sind, die gerade 8 Std. gelaufen sind ;-) ).
Nun loggt man sich in die Master-DB ein und führt einen manuellen Logswitch aus. Damit kann man testen, ob die Transaktionslogs dann auf dem Slave-Rechner landen unter /data/pgsql/dumps/$DBNAME/pg_xlog_master
psql -d template1
select pg_switch_xlog();
Sollte das nicht tun, muss man sich wohl oder übel auf Fehlersuche begeben... Als nächstes machen wir uns eine Kopie der Master-DB auf den Slave-Rechner. Dazu loggt man sich wieder auf der Master-DB ein und versetzt die Datenbank in den Backupmodus (Für label kann man irgendwas angeben. Das ist nur ein Marker.):
psql -d template1
SELECT pg_start_backup('label');
Dann kopiert man am Besten auf der Shell mit rsync alle Postgres-Daten auf den Slave-Rechner ($DBNAME und $ZIELRECHNER natürlich entsprechend ersetzen). Der folgende Befehl geht davon aus, das das Datenverzeichnis auf dem Slave noch leer ist (also keine pg_hba.conf, postgresql.conf, usw. vorhanden ist):
cd /data/pgsql/data
rsync -av --rsh=ssh $DBNAME root@$ZIELRECHNER:/data/pgsql/data/
Dann können wir auf der Master-DB den Backupmodus wieder beenden, wenn alle Daten kopiert sind:
SELECT pg_stop_backup();
Nun wechseln wir auf den Slave-Server. Dort haben wir ja unter /data/pgsql/data/$DBNAME jetzt auch die Transaktionslogs der Master-DB liegen (die haben wir oben beim Backup mitkopiert). Die müssen wir erstmal löschen:
cd /data/pgsql/data/$DBNAME/pg_xlog
find . -type f -exec rm {} \;
Mit dem rsync oben haben wir auch die postgresql.conf der Master-DB kopiert. Die sollte man jetzt etwas anpassen. Z.B. wird wahrscheinlich die IP (listen_addresses) nicht stimmen auf dem Slave und den Archive-Modus sollte man auch ausschalten. Also editieren wir die Datei und ändern Folgendes z.B. entsprechend ab:
listen_addresses = 'localhost'
archive_mode = off
Auch wenn Master- und Slave-Rechner eigentlich ungefähr gleich ausgestattet sein sollten, sollte man eventl. noch die Speicherparameter wie shared_buffers oder effective_cache_size runtersetzen, wenn der Slave vielleicht doch nicht soviel Speicher (RAM) hat wie der Master.
Als nächstes bereiten wir eine Datei namens recovery.conf auf dem Slave vor. Wenn diese Datei im Postgres-Datenverzeichnis liegt und Postgres findet diese Datei beim Starten vor, dann wechselt die DB in den Recoverymodus. Dort drin ist dann beim restore_command das oben erwähnte Programm pg_standby hinterlegt. Dieses liesst dann laufend die Transaktionslogs ein, die die Master-Instanz auf den Slave-Rechner unter /data/pgsql/dumps/$DBNAME/pg_xlog_master ablegt. Eine Vorlage liegt dem Postgres-Source bei z.B.:
cp /server/pgsql/8.3.6/share/recovery.conf.sample /data/pgsql/data/$DBNAME/recovery.conf
chown postgres.postgres /data/pgsql/data/$DBNAME/recovery.conf
Die recovery.conf editieren wir dann wie folgt und ändern nur restore_command:
restore_command = '/server/pgsql/8.3.6/bin/pg_standby -t /tmp/pgsql.fin.$DBNAME /data/pgsql/dumps/$DBNAME/pg_xlog_master %f %p %r 2>>/data/pgsql/logs/$DBNAME/standby.log'
-t wenn diese Datei existiert, wird der Recoveryprozess unterbrochen und die DB fährt hoch.
/data/pgsql/dumps/$DBNAME/pg_xlog_master hier sind die Transaktionslogs der Master-DB zu finden.
%f wird von Postgres ersetzt durch den Filenamen des Transaktionslogs, das es benötigt.
%p wird von Postgres ersetzt durch den Pfadnamen, wo die Transaktionslogs hinkopiert werden sollen.
%r wird ersetzt durch den Namen des Files, das den letzten Restartpunkt enthält. Das führt auch dazu, das ältere Transaktionslogs, die nicht mehr gebraucht werden, auch wieder gelöscht werden können, da mit dieser Information pg_standby ja weiß, welche Transaktionslogs nötig sind, um die DB sauber hochfahren zu können. Ansonsten würden die Logs dort bleiben, wo sie sind und man muss sie von Hand wegkopieren.
2>>/data/pgsql/logs/$DBNAME/standby.log hier leiten wir alle Fehlermeldungen in ein Logfile um.
Dann können wir die Standby-DB starten. Wenn man sich nun auf der Master-DB einloggt und wieder einen manuellen Logswitch ausführt, dann müsste man im Logfile der Slave-DB sehen, das dieses Transaktionslog eingespielt also recovered wird. Im Verzeichnis /data/pgsql/dumps/$DBNAME/pg_xlog_master auf dem Slave-Rechner müssten dann nach einer Weile die Logfiles verschwinden (hängt natürlich von verschiedenen Faktoren ab, wenn die Dinger da verschwinden und kann etwas dauern).
Zu beachten ist zum Schluss noch Folgendes: Wenn man in der Master-DB Änderungen in der pg_hba.conf oder postgresql.conf macht, dann muss man diese Änderungen natürlich auch auf der Slave-DB in den entsprechenden Dateien machen!
Die Wiederherstellungsprozedur dann demnächst in diesem Blog ;-)
Posted at 01:00vorm. März 05, 2009 by cetixx in Tipps | Kommentare [0]