Letztes Mal haben wir uns mit dem Gerät eines der wichtigsten Objekte des gemeinsamen Speichers getroffen, dem Puffercache. Die Möglichkeit, Informationen aus dem RAM zu verlieren, ist der Hauptgrund für die Notwendigkeit der Wiederherstellung nach einem Fehler. Heute werden wir über diese Tools sprechen.
Magazin
Leider geschehen keine Wunder: Um den Informationsverlust im RAM zu überstehen, muss alles Notwendige rechtzeitig auf eine Festplatte (oder ein anderes nichtflüchtiges Gerät) geschrieben werden.
Daher wurde dies getan. Neben Datenänderungen wird auch ein
Tagebuch dieser Änderungen geführt. Wenn wir etwas auf einer Seite im Puffercache ändern, erstellen wir im Protokoll einen Datensatz über diese Änderung. Der Datensatz enthält die Mindestinformationen, die ausreichen, damit die Änderung bei Bedarf wiederholt werden kann.
Damit dies funktioniert, muss der Journaleintrag unbedingt auf die Festplatte verschoben werden,
bevor die geänderte Seite dort ankommt. Daher der Name: Write-Ahead-Protokoll.
Wenn ein Fehler auftritt, befinden sich die Daten auf der Festplatte in einem inkonsistenten Zustand: Einige Seiten wurden früher geschrieben, andere später. Es bleibt jedoch ein Journal übrig, das von den Vorgängen gelesen und erneut ausgeführt werden kann, die bereits vor dem Fehler abgeschlossen wurden, deren Ergebnis jedoch die Festplatte nicht erreicht hat.
Warum nicht erzwingen, dass die Datenseiten selbst auf die Festplatte geschrieben werden, warum stattdessen Doppeljobs ausführen? Es stellt sich als so effektiv heraus.
Zuallererst ist ein Protokoll ein sequentieller Datenstrom, der geschrieben werden soll. Sogar Festplatten eignen sich sehr gut für sequentielle Aufnahmen. Die Aufzeichnung der Daten selbst ist jedoch zufällig, da die Seiten mehr oder weniger zufällig über die Festplatte verteilt sind.
Zweitens kann ein Journaleintrag viel kleiner als eine Seite sein.
Drittens müssen Sie sich bei der Aufnahme nicht darum kümmern, dass die Daten auf der Festplatte zu einem beliebigen Zeitpunkt konsistent bleiben (diese Anforderung verkürzt die Lebensdauer erheblich).
Und viertens kann das Journal (da es existiert), wie wir später sehen werden, nicht nur zur Wiederherstellung, sondern auch zur Sicherung und Replikation verwendet werden.
Sie müssen alle Vorgänge protokollieren, bei denen im Falle eines Fehlers die Gefahr von Inkonsistenzen auf der Festplatte besteht. Insbesondere werden folgende Aktionen protokolliert:
- Ändern von Seiten im Puffercache (in der Regel sind dies Tabellen und Indexseiten) - da die geänderte Seite nicht sofort auf die Festplatte verschoben wird;
- Festschreiben und Abbrechen von Transaktionen - Die Statusänderung erfolgt in den XACT-Puffern und erreicht die Festplatte auch nicht sofort.
- Dateivorgänge (Erstellen und Löschen von Dateien und Verzeichnissen, z. B. Erstellen von Dateien beim Erstellen einer Tabelle) - da diese Vorgänge gleichzeitig mit Datenänderungen ausgeführt werden müssen.
Nicht protokolliert:
- Operationen mit nicht journalisierten (nicht protokollierten) Tabellen - ihr Name spricht für sich selbst;
- Operationen mit temporären Tabellen - dies ist nicht sinnvoll, da die Lebensdauer solcher Tabellen die Lebensdauer der Sitzung, in der sie erstellt wurden, nicht überschreitet.
Vor PostgreSQL 10 wurden
Hash-Indizes nicht protokolliert (sie dienten nur dazu, Hash-Funktionen verschiedenen Datentypen zuzuordnen), aber jetzt wurde dies behoben.
Logisches Gerät

Ein Journal kann logischerweise als eine Folge von Datensätzen unterschiedlicher Länge betrachtet werden. Jeder Datensatz enthält
Daten zu einer bestimmten Operation, denen ein Standardheader vorangestellt ist. Der Titel gibt unter anderem an:
- Die Transaktionsnummer, zu der der Datensatz gehört.
- Ressourcenmanager - die Komponente des Systems, die für die Aufzeichnung verantwortlich ist;
- Prüfsumme (CRC) - Ermöglicht die Ermittlung von Datenbeschädigungen.
- Datensatzlänge und Link zum vorherigen Datensatz.
Die Daten selbst haben ein anderes Format und eine andere Bedeutung. Sie können beispielsweise ein Fragment einer Seite darstellen, das mit einem bestimmten Versatz über den Inhalt geschrieben werden muss. Der angegebene Ressourcenmanager „versteht“, wie die Daten in seinem Datensatz zu interpretieren sind. Es gibt separate Manager für Tabellen, für jeden Indextyp, für den Transaktionsstatus usw. Eine vollständige Liste davon kann auf Wunsch des Befehls abgerufen werden
pg_waldump -r list
Physisches Gerät
Auf der Festplatte wird das Protokoll als Dateien im Verzeichnis $ PGDATA / pg_wal gespeichert. Jede Datei ist standardmäßig 16 MB groß. Die Größe kann erhöht werden, um eine große Anzahl von Dateien in einem Verzeichnis zu vermeiden. Vor PostgreSQL 11 war dies nur beim Kompilieren des Quellcodes möglich. Jetzt können Sie die Größe beim Initialisieren des Clusters angeben (den
--wal-segsize
).
Protokolleinträge fallen in die aktuell verwendete Datei. Wenn es endet, wird das nächste verwendet.
Für das Protokoll im gemeinsam genutzten Speicher des Servers werden spezielle Puffer zugewiesen. Die Größe des
Journalcaches wird durch den Parameter
wal_buffers festgelegt (der Standardwert impliziert eine automatische Konfiguration: 1/32 des
Puffercaches wird zugewiesen).
Der Journal-Cache ist wie ein Puffer-Cache angeordnet, funktioniert jedoch hauptsächlich im Ringpuffer-Modus: Einträge werden dem "Kopf" hinzugefügt und vom "Ende" auf die Festplatte geschrieben.
Die Positionen Aufnahme ("Schwanz") und Einfügen ("Kopf") zeigen die Funktionen pg_current_wal_lsn bzw. pg_current_wal_insert lsn:
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331E4E64 | 0/331E4EA0 (1 row)
Um auf einen bestimmten Datensatz zu verweisen, wird der Datentyp pg_lsn (LSN = Protokollsequenznummer) verwendet. Dies ist eine 64-Bit-Nummer, die den Byte-Offset vor dem Datensatz relativ zum Protokollanfang darstellt. LSN wird als zwei 32-Bit-Zahlen in hexadezimaler Schreibweise ausgegeben.
Sie können herausfinden, in welcher Datei wir die gewünschte Position finden und mit welchem Versatz vom Anfang der Datei:
=> SELECT file_name, upper(to_hex(file_offset)) file_offset FROM pg_walfile_name_offset('0/331E4E64');
file_name | file_offset --------------------------+------------- 000000010000000000000033 | 1E4E64 \ /\ / 0/331E4E64
Der Dateiname besteht aus zwei Teilen. Die oberen 8 hexadezimalen Ziffern geben die Nummer des Zeitzweigs an (wird beim Wiederherstellen aus der Sicherung verwendet), der Rest entspricht den höchsten LSN-Ziffern (und die verbleibenden unteren LSN-Ziffern geben den Versatz an).
Protokolldateien können im Dateisystem im Verzeichnis $ PGDATA / pg_wal / angezeigt werden. Ab PostgreSQL 10 können sie jedoch auch mit einer speziellen Funktion angezeigt werden:
=> SELECT * FROM pg_ls_waldir() WHERE name = '000000010000000000000033';
name | size | modification --------------------------+----------+------------------------ 000000010000000000000033 | 16777216 | 2019-07-08 20:24:13+03 (1 row)
Weiterleiten schreiben
Lassen Sie uns sehen, wie das Journaling erfolgt und wie proaktive Aufzeichnungen bereitgestellt werden. Erstellen Sie eine Tabelle:
=> CREATE TABLE wal(id integer); => INSERT INTO wal VALUES (1);
Wir werden uns die Kopfzeile der Tabellenseite ansehen. Dazu benötigen wir eine bereits bekannte Erweiterung:
=> CREATE EXTENSION pageinspect;
Beginnen wir mit der Transaktion und merken uns die Einfügeposition im Protokoll:
=> BEGIN; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F377C (1 row)
Lassen Sie uns nun eine Operation ausführen, zum Beispiel die Zeile aktualisieren:
=> UPDATE wal set id = id + 1;
Diese Änderung wurde im Protokoll aufgezeichnet, die Einfügeposition hat sich geändert:
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F37C4 (1 row)
Um sicherzustellen, dass die geänderte Datenseite nicht vor dem Journaleintrag auf die Festplatte übertragen wird, wird die LSN des letzten Journaleintrags für diese Seite im Seitenkopf gespeichert:
=> SELECT lsn FROM page_header(get_raw_page('wal',0));
lsn ------------ 0/331F37C4 (1 row)
Beachten Sie, dass das Journal dem gesamten Cluster gemeinsam ist und ständig neue Einträge in das Journal fallen. Daher kann die LSN auf der Seite kleiner sein als der Wert, den die gerade zurückgegebene Funktion pg_current_wal_insert_lsn zurückgegeben hat. In unserem System passiert jedoch nichts, daher sind die Zahlen gleich.
Schließen Sie nun die Transaktion ab.
=> COMMIT;
Der Festschreibungsdatensatz wird ebenfalls in das Protokoll aufgenommen, und die Position ändert sich erneut:
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/331F37E8 (1 row)
Commit ändert den Status einer Transaktion in einer Struktur namens XACT (wir haben
bereits darüber gesprochen ). Status werden in Dateien gespeichert, sie verwenden jedoch auch einen eigenen Cache, der 128 Seiten im gemeinsam genutzten Speicher belegt. Daher muss für XACT-Seiten die LSN des letzten Journaleintrags verfolgt werden. Diese Informationen werden jedoch nicht auf der Seite selbst gespeichert, sondern im RAM.
Irgendwann werden die erstellten Journaleinträge auf die Festplatte geschrieben. In welchem - wir werden ein anderes Mal reden, aber in unserem Fall ist dies bereits geschehen:
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331F37E8 | 0/331F37E8 (1 row)
Nach diesem Zeitpunkt können die Daten- und XACT-Seiten aus dem Cache verschoben werden. Wenn sie jedoch früher erzwungen werden müssten, würde dies erkannt und die Journaleinträge müssten zuerst aufgezeichnet werden.
Wenn Sie die beiden LSN-Positionen kennen, können Sie die Größe der Journaleinträge zwischen ihnen (in Bytes) ermitteln, indem Sie einfach eine Position von der anderen subtrahieren. Sie müssen nur die Positionen in den Typ pg_lsn umwandeln:
=> SELECT '0/331F37E8'::pg_lsn - '0/331F377C'::pg_lsn;
?column? ---------- 108 (1 row)
In diesem Fall waren für die Zeilenaktualisierung und das Festschreiben 108 Byte im Protokoll erforderlich.
Auf die gleiche Weise können Sie abschätzen, wie viele Journaleinträge vom Server pro Zeiteinheit bei einer bestimmten Last generiert werden. Dies sind wichtige Informationen, die während des Setups benötigt werden (worüber wir beim nächsten Mal sprechen werden).
Jetzt verwenden wir das Dienstprogramm pg_waldump, um die erstellten Protokolleinträge anzuzeigen.
Das Dienstprogramm kann mit dem LSN-Bereich (wie in diesem Beispiel) arbeiten und Datensätze für die angegebene Transaktion auswählen. Es sollte im Auftrag des Postgres-Betriebssystembenutzers ausgeführt werden, da dieser Zugriff auf die Protokolldateien auf der Festplatte benötigt.
postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/331F377C -e 0/331F37E8 000000010000000000000033
rmgr: Heap len (rec/tot): 69/ 69, tx: 101085, lsn: 0/331F377C, prev 0/331F3014, desc: HOT_UPDATE off 1 xmax 101085 ; new off 2 xmax 0, blkref #0: rel 1663/16386/33081 blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 101085, lsn: 0/331F37C4, prev 0/331F377C, desc: COMMIT 2019-07-08 20:24:13.945435 MSK
Hier sehen wir die Überschriften der beiden Einträge.
Die erste ist die Operation
HOT_UPDATE , die sich auf den Heap-Ressourcenmanager bezieht. Der Dateiname und die Seitenzahl werden im Feld blkref angezeigt und stimmen mit der aktualisierten Tabellenseite überein:
=> SELECT pg_relation_filepath('wal');
pg_relation_filepath ---------------------- base/16386/33081 (1 row)
Der zweite Eintrag ist COMMIT und bezieht sich auf den Transaction Resource Manager.
Nicht das am besten lesbare Format, aber Sie können es bei Bedarf herausfinden.
Wiederherstellung
Wenn wir den Server starten, wird zuerst der Postmaster-Prozess gestartet, der wiederum den Startprozess startet, dessen Aufgabe es ist, die Wiederherstellung sicherzustellen, wenn ein Fehler auftritt.
Um festzustellen, ob eine Wiederherstellung erforderlich ist, überprüft der Start die spezielle Steuerdatei $ PGDATA / global / pg_control und den Clusterstatus. Wir können den Status selbst mit dem Dienstprogramm pg_controldata überprüfen:
postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state
Database cluster state: in production
Ein ordentlich gestoppter Server hat den Status "Herunterfahren". Wenn der Server nicht funktioniert und der Status "in Produktion" bleibt, bedeutet dies, dass das DBMS heruntergefallen ist und die Wiederherstellung automatisch durchgeführt wird.
Zur Wiederherstellung liest der Startvorgang das Protokoll nacheinander und wendet bei Bedarf Einträge auf die Seiten an. Sie können die Notwendigkeit überprüfen, indem Sie die LSN der Seite auf der Festplatte mit der LSN des Journaleintrags vergleichen. Wenn die LSN der Seite größer ist, ist der Datensatz nicht erforderlich. Tatsächlich ist dies jedoch nicht möglich, da die Aufzeichnungen für eine streng konsistente Anwendung ausgelegt sind.
Es gibt Ausnahmen. Einige Datensätze werden als Ganzseitenbild (FPI, Ganzseitenbild) erstellt, und es ist klar, dass ein solches Bild in jedem Zustand auf eine Seite angewendet werden kann - es wird immer noch alles gelöscht, was dort war. Eine weitere Änderung des Status einer Transaktion kann auf jede Version der XACT-Seite angewendet werden. Auf diesen Seiten muss daher keine LSN gespeichert werden.
Das Wechseln der Seiten während der Wiederherstellung erfolgt wie bei der normalen Arbeit im Puffercache - für diesen Postmaster werden die erforderlichen Hintergrundprozesse gestartet.
Ebenso gelten Journaleinträge für Dateien: Wenn beispielsweise ein Datensatz angibt, dass die Datei vorhanden sein muss, aber nicht vorhanden ist, wird die Datei erstellt.
Nun, ganz am Ende des Wiederherstellungsprozesses werden alle nicht journalisierten Tabellen mit "Dummies" aus ihren
Init-Ebenen überschrieben.
Dies ist eine sehr vereinfachte Darstellung des Algorithmus. Insbesondere haben wir nichts darüber gesagt, wo mit dem Lesen von Journaleinträgen begonnen werden soll (diese Konversation muss verschoben werden, bis der Kontrollpunkt berücksichtigt wird).
Und die letzte Klarstellung. Der "klassische" Wiederherstellungsprozess besteht aus zwei Phasen. In der ersten Phase (Rollforward) werden Journaleinträge gerollt, und der Server wiederholt alle während des Fehlers verlorenen Arbeiten. Beim zweiten (Rollback) werden Transaktionen zurückgesetzt, die zum Zeitpunkt des Fehlers nicht festgeschrieben wurden. PostgreSQL benötigt jedoch keine zweite Phase. Wie
bereits erwähnt , müssen Transaktionen aufgrund der Besonderheiten der Implementierung von Transaktionen mit mehreren Versionen nicht physisch zurückgesetzt werden. Es reicht aus, dass das Fixbit in XACT nicht gesetzt wird.
Fortsetzung folgt .