Johannes Truschnigg - SRE & DevOps


Zur Suche ▼Zum Archiv ▼

Eintrag von 2014-09-15

Archiv-Helferprogramm für PostgreSQL WAL-Segmente


Wer einen PostgreSQL-Cluster betreibt und Hochverfügbarkeit (oder zumindest kurze Recovery-Zeiten im Falle eines Crashes der Datenbank...) anstrebt, dem ist das gewissenhafte Archivieren der Segmente des WAL (Write Ahead Log) des Clusters ans Herz gelegt. Das WAL ist eine theoretisch endlose Folge aufsteigend nummerierter Datenblöcke, deren fixe Größe beim Kompilieren von PostgreSQL festgelegt wird - in der Standardkonfiguration beträgt diese 16 MByte. Diese Blöcke beinhalten eine Art Liste geplanter Änderungen in einer Form, die lückenlose Anwendbarkeit auf den Ist-Stand der Daten des Clusters garantiert. Sollte der Cluster vor dem erfolgreichen Abarbeiten eines ganzen WAL-Segments abstuerzen oder sonstwie ungeplant unterbrochen worden sein, so ist sichergestellt, dass beim nächsten Start des Clusters vom Beginn des letzten erfolgreich abgearbeiteten WAL-Segments aus fortgesetzt werden kann. In der PostgreSQL-Dokumentation findet man einen detaillierteren Abriss über das WAL.

Das WAL ermöglicht es aber darüber hinaus auch, die Änderungen, die ein Postgres-Cluster auf sich selbst anzuwenden plant, auf einem anderen Cluster (mit demselben Datenbestand bis zu diesem WAL-Segment) ebenfalls durchzuführen. Dazu bedient man sich einer Methode namens "Log Shipping", wobei komplette WAL-Segmente (in der Praxis 16 MByte große Binärdateien) vom Master-Cluster zu (mindestens) einem Slave-Cluster geschickt, d. h. irgendwie in dessen Dateisystem kopiert oder verschoben, werden. Dies ist auch dann noch relevant, wenn man sich der moderneren Replikationsmethode "Streaming Replication", wobei Änderungen am WAL-Strom in feinerer Abstufung als in ganzen WAL-Segmenten über eine persistente TCP-Verbindung zwischen Master und Slave(s) mehr oder weniger in Echtzeit repliziert werden, bedient. Hier benötigt man ein lückenloses WAL-Archiv, aus dem der Slave Segmente lesen kann, falls die replizierende TCP-Verbindung aus irgendwelchen Gründen über eine längere Zeit abreißt - konkret: wenn das erste benötigte WAL-Segment am Slave nicht mehr unter den durch den Master auch ohne explizites WAL-Archiv vorgehaltenen, durch wal_keep_segments festgelegten Segmenten ist.

Als Zielort für ein WAL-Archiv empfiehlt sich bspw. ein (Netzwerk-)Dateisystem, das sowohl von Master als auch Slave gelesen und geschrieben werden kann - der Master muss darauf WAL-Segmente archivieren können, und der Slave sollte applizierte und somit definitiv nicht mehr gebrauchte Segmente aus dem Dateisystem löschen können. Wenn dieses vollläuft, führt das zu sehr unschönen Konsequenzen für den Master-Cluster, sobald das Dateisystem seine Kapazitäten erschöpft, das das pg_xlog-Verzeichnis des Clusters beinhaltet. Ein NFS-Export auf dem Client oder dem Master ist für das Archiv also durchaus tauglich. Wie aber kommen nun die WAL-Segmente vom Master auf den Slave, bzw. in das geteilte Archiv-Verzeichnis? Hierzu dient das archive_command am Master - ein Aufruf via system(3), der das Kopieren des WAL-Segments an den Zielort übernehmen soll.

Die PostgreSQL-Dokumentation beschreibt ausführlich, welche Anforderungen dieses Kommando erfüllen muss:

  1. Wenn das Segment erfolgreich kopiert wurde, muss es mit Status 0 enden.
  2. In allen anderen Fällen darf es nicht mit Status 0 enden.
  3. Es darf keine schon existierenden Dateien (WAL-Segmente) überschreiben.

In der Dokumentation wird hierfür ein Shellscript vorgeschlagen, das um eine Limitation von GNU cp herumarbeitet - dieses ermöglicht es nämlich nicht, den Descriptor der Ziel-Datei mit den Flags O_CREAT und O_EXCL zu öffnen, was die garantierte Erfüllung der dritten Bedingung streng genommen unmöglich macht. Man behilft sich in der offiziellen Dokumentation mit einem zweiten Schritt vor dem tatsächlichen Kopieren: durch einen Aufruf des Programms (bzw. Builtins) `test` soll sichergestellt werden, dass die Zieldatei noch nicht existiert:

archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'

Hierbei entsteht eine klassische Race Condition, die effektiv nur durch die zuvor erwähnten Flags für den open(2)-Syscall verhindert werden kann. Auch aus diesem Grund habe ich mich dazu entschlossen, ein möglichst minimales C-Programm zu schreiben, das diese Aufgabe (besser) erfüllen kann: pg_archive_wal_segment.

Hat man es kompiliert und an einem beliebigen Pfad im Dateisystem (das tunlichst nicht mit "noexec" gemountet sein sollte :-)) abgelegt - ich nehme /usr/local/bin/ an -, konfiguriert man den PostgreSQL-Master-Cluster einfach fortan so:

archive_command = '/usr/local/bin/pg_archive_wal_segment %p /mnt/server/archivedir/%f'

Das Programm beschränkt sich ganz absichtlich darauf, die Quelldatei (das erste Argument) an den angegebenen Pfadnamen (das zweite Argument) zu kopieren. Das gelingt durch read(2)- und write(2)-syscalls mit einem 512 KByte großen Buffer dazwischen. Im Fehlerfall, oder wenn der Vorgang "verdächtig" lange dauert (Default: 30 Sekunden - dieses Limit kann durch eine Compile-Time-Option festegelegt werden) bzw. wenn das Programm durch ein Signal unterbrochen wird, wird die Zieldatei wieder unlink(2)t und ein Exit-Status ungleich 0 zurückgegeben, worüber pg_archive_wal_segment auf stderr Bescheid gibt. In so einem Fall versucht der Master-Cluster nach kurzer Zeit einen erneuten Aufruf des archive_commands - so lange, bis der Kopiervorgang einmal erfolgreich verläuft. Hat das Kopieren geklappt, vermeldet das Programm den Erfolg der Operation auf stdout und retourniert 0.

direkter Link ▲

© 2007-2019 Johannes Truschnigg | valid xhmtl & css