Wir haben uns also mit dem Gerät des
Puffercaches vertraut gemacht und anhand seines Beispiels festgestellt, dass für die Wiederherstellung ein
Voraufzeichnungsprotokoll erforderlich
ist , wenn der Inhalt des RAM im Falle eines Fehlers verschwindet. Die Größe der erforderlichen Protokolldateien und die Wiederherstellungszeit sind aufgrund eines regelmäßig ausgeführten
Prüfpunkts begrenzt .
In früheren Artikeln haben wir bereits eine ziemlich große Anzahl wichtiger Einstellungen in Bezug auf das Journal auf die eine oder andere Weise betrachtet. In diesem Artikel (dem letzten in dieser Reihe) werden die noch nicht diskutierten Optimierungsprobleme behandelt: Protokollierungsstufen und deren Zweck sowie die Zuverlässigkeit und Leistung der Protokollierung.
Protokollebenen
Der Hauptzweck des Voraufzeichnungsprotokolls besteht darin, nach einem Fehler eine Wiederherstellung bereitzustellen. Wenn Sie jedoch noch ein Tagebuch führen müssen, kann es für andere Aufgaben angepasst werden, indem eine bestimmte Menge zusätzlicher Informationen hinzugefügt wird. Es gibt verschiedene Protokollierungsstufen. Sie werden durch den Parameter
wal_level festgelegt und so organisiert, dass das Protokoll jeder nächsten Ebene alles enthält, was in das Protokoll der vorherigen Ebene fällt, sowie etwas anderes Neues.
Minimal
Die minimal mögliche Stufe wird durch den Wert
wal_level = minimal festgelegt und garantiert nur die Wiederherstellung nach einem Fehler. Aus Platzgründen werden Vorgänge im Zusammenhang mit der Massendatenverarbeitung (z. B. CREATE TABLE AS SELECT oder CREATE INDEX) nicht protokolliert. Stattdessen werden die erforderlichen Daten sofort auf die Festplatte geschrieben, und ein neues Objekt wird dem Systemverzeichnis hinzugefügt und wird sichtbar, wenn die Transaktion festgeschrieben wird. Wenn während des Vorgangs ein Fehler auftritt, bleiben die bereits aufgezeichneten Daten unsichtbar und verletzen die Konsistenz nicht. Wenn der Fehler nach Abschluss des Vorgangs auftritt, ist alles Notwendige bereits auf der Festplatte gespeichert und muss nicht protokolliert werden.
Mal sehen.
Stellen Sie zunächst die gewünschte Stufe ein (dazu müssen Sie auch einen anderen Parameter ändern -
max_wal_senders ).
=> ALTER SYSTEM SET wal_level = minimal; => ALTER SYSTEM SET max_wal_senders = 0;
student$ sudo pg_ctlcluster 11 main restart
Beachten Sie, dass zum Ändern der Ebene ein Neustart des Servers erforderlich ist.
Merken Sie sich die aktuelle Position im Protokoll:
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/353927BC (1 row)
Erstellen wir nun die Tabelle (CREATE TABLE AS SELECT) und schreiben die Position erneut in das Protokoll. Die von der SELECT-Anweisung ausgewählte Datenmenge spielt in diesem Fall keine Rolle, daher beschränken wir uns auf eine Zeile.
=> CREATE TABLE wallevel AS SELECT 1 AS n; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/353A7DFC (1 row)
Schauen wir uns mit dem bekannten Dienstprogramm pg_waldump die Protokolleinträge an.
postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/353927BC -e 0/353A7DFC
Einige Details können natürlich von Start zu Start unterschiedlich sein, aber in diesem Fall ist dies geschehen. Der Heap2-Manager-Eintrag bezieht sich auf die Bereinigung. Hier handelt es sich um eine On-Page-Bereinigung einer der Tabellen im Systemkatalog (Systemobjekte können mit bloßem Auge leicht durch die „kurze“ Zahl in rel unterschieden werden):
rmgr: Heap2 len (rec/tot): 59/ 7587, tx: 0, lsn: 0/353927BC, prev 0/35392788, desc: CLEAN remxid 101126, blkref #0: rel 1663/16386/1247 blk 8 FPW
Dann gibt es einen Datensatz zum Abrufen der nächsten OID für die Tabelle, die wir erstellen werden:
rmgr: XLOG len (rec/tot): 30/ 30, tx: 0, lsn: 0/35394574, prev 0/353927BC, desc: NEXTOID 82295
Nun die eigentliche Erstellung der Tabelle:
rmgr: Storage len (rec/tot): 42/ 42, tx: 0, lsn: 0/35394594, prev 0/35394574, desc: CREATE base/16386/74103
Das Einfügen von Daten in eine Tabelle wird jedoch nicht protokolliert. Dann gibt es zahlreiche Einträge zum Einfügen von Zeilen in verschiedene Tabellen und Indizes - dieses PostgreSQL registriert die erstellte Tabelle im Systemverzeichnis (ich gebe es in Kurzform an):
rmgr: Heap len (rec/tot): 203/ 203, tx: 101127, lsn: 0/353945C0, prev 0/35394594, desc: INSERT off 71, blkref #0: rel 1663/16386/1247 blk 8 rmgr: Btree len (rec/tot): 53/ 685, tx: 101127, lsn: 0/3539468C, prev 0/353945C0, desc: INSERT_LEAF off 37, blkref #0: rel 1663/16386/2703 blk 2 FPW ... rmgr: Btree len (rec/tot): 53/ 2393, tx: 101127, lsn: 0/353A747C, prev 0/353A6788, desc: INSERT_LEAF off 10, blkref #0: rel 1664/0/1233 blk 1 FPW
Und schließlich die Transaktionsfixierung:
rmgr: Transaction len (rec/tot): 34/ 34, tx: 101127, lsn: 0/353A7DD8, prev 0/353A747C, desc: COMMIT 2019-07-23 18:59:34.923124 MSK
Replik
Wenn wir das System aus der Sicherung wiederherstellen, beginnen wir mit einem bestimmten Status des Dateisystems und bringen die Daten schrittweise zum Wiederherstellungspunkt, wobei die archivierten Journaleinträge wiedergegeben werden. Die Anzahl solcher Datensätze kann sehr groß sein (z. B. mehrere Tage), dh der Wiederherstellungszeitraum deckt nicht einen Kontrollpunkt ab, sondern viele. Daher ist klar, dass die Mindeststufe des Protokolls nicht ausreicht. Wenn ein Vorgang nicht protokolliert wird, wissen wir einfach nicht, dass er wiederholt werden muss. Um aus einer Sicherung wiederherzustellen, müssen
alle Vorgänge protokolliert werden.
Gleiches gilt für die Replikation. Alles, was nicht protokolliert wird, wird nicht auf das Replikat übertragen und nicht reproduziert. Wenn wir jedoch Anforderungen auf einem Replikat ausführen möchten, ist dies immer noch kompliziert.
Zunächst benötigen wir Informationen zu exklusiven Sperren, die auf dem Primärserver auftreten, da diese möglicherweise mit Anforderungen auf dem Replikat in Konflikt stehen. Solche Sperren werden protokolliert und auf das Replikat angewendet (im Auftrag des Startvorgangs).
Zweitens müssen Sie in der Lage sein,
Datenschnappschüsse zu erstellen, und dafür werden, wie wir uns erinnern, Informationen über laufende Transaktionen benötigt. Bei einem Replikat handelt es sich nicht nur um lokale Transaktionen, sondern auch um Transaktionen auf dem Hauptserver. Die einzige Möglichkeit, diese Informationen zu übertragen, besteht darin, sie regelmäßig in das Protokoll zu schreiben (dies geschieht alle 15 Sekunden).
Die Protokollebene, die sowohl die Fähigkeit zur Wiederherstellung nach einer Sicherung als auch die Möglichkeit der physischen Replikation garantiert, wird durch den Wert wal_level = replica festgelegt. (Vor Version 9.6 gab es zwei separate Ebenen Archiv und hot_standby, aber dann wurden sie zu einer gemeinsamen Ebene kombiniert.)
Ab PostgreSQL 10 ist diese Stufe standardmäßig festgelegt (und davor war sie minimal). Setzen Sie daher die Parameter einfach auf die Standardwerte zurück:
=> ALTER SYSTEM RESET wal_level; => ALTER SYSTEM RESET max_wal_senders;
student$ sudo pg_ctlcluster 11 main restart
Wir löschen die Tabelle und wiederholen genau die gleiche Abfolge von Aktionen wie beim letzten Mal:
=> DROP TABLE wallevel; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/353AF21C (1 row)
=> CREATE TABLE wallevel AS SELECT 1 AS n; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/353BE51C (1 row)
Überprüfen Sie nun die Journaleinträge.
postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/353AF21C -e 0/353BE51C
Bereinigen, OID abrufen, Tabelle erstellen und im Systemverzeichnis registrieren - im Moment ist alles so, wie es war:
rmgr: Heap2 len (rec/tot): 58/ 58, tx: 0, lsn: 0/353AF21C, prev 0/353AF044, desc: CLEAN remxid 101128, blkref #0: rel 1663/16386/1247 blk 8 rmgr: XLOG len (rec/tot): 30/ 30, tx: 0, lsn: 0/353AF258, prev 0/353AF21C, desc: NEXTOID 82298 rmgr: Storage len (rec/tot): 42/ 42, tx: 0, lsn: 0/353AF278, prev 0/353AF258, desc: CREATE base/16386/74106 rmgr: Heap len (rec/tot): 203/ 203, tx: 101129, lsn: 0/353AF2A4, prev 0/353AF278, desc: INSERT off 73, blkref #0: rel 1663/16386/1247 blk 8 rmgr: Btree len (rec/tot): 53/ 717, tx: 101129, lsn: 0/353AF370, prev 0/353AF2A4, … rmgr: Btree len (rec/tot): 53/ 2413, tx: 101129, lsn: 0/353BD954, prev 0/353BCC44, desc: INSERT_LEAF off 10, blkref #0: rel 1664/0/1233 blk 1 FPW
Aber etwas Neues. Die Aufzeichnung einer exklusiven Sperre in Bezug auf den Standby-Manager - in diesem Fall blockiert sie die Transaktionsnummer (warum wird sie benötigt, werden wir in der nächsten Artikelserie ausführlich besprechen):
rmgr: Standby len (rec/tot): 42/ 42, tx: 101129, lsn: 0/353BE2D8, prev 0/353BD954, desc: LOCK xid 101129 db 16386 rel 74106
Und dies ist ein Datensatz zum Einfügen von Zeilen in unsere Tabelle (vergleichen Sie die Dateinummer rel mit der oben im CREATE-Datensatz angegebenen):
rmgr: Heap len (rec/tot): 59/ 59, tx: 101129, lsn: 0/353BE304, prev 0/353BE2D8, desc: INSERT+INIT off 1, blkref #0: rel 1663/16386/74106 blk 0
Commit-Datensatz:
rmgr: Transaction len (rec/tot): 421/ 421, tx: 101129, lsn: 0/353BE340, prev 0/353BE304, desc: COMMIT 2019-07-23 18:59:37.870333 MSK; inval msgs: catcache 74 catcache 73 catcache 74 catcache 73 catcache 50 catcache 49 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 snapshot 2608 relcache 74106 snapshot 1214
Ein weiterer Datensatz, der regelmäßig auftritt und nicht an die abgeschlossene Transaktion gebunden ist, bezieht sich auf den Standby-Manager und berichtet über die derzeit laufenden Transaktionen:
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 0/353BE4E8, prev 0/353BE340, desc: RUNNING_XACTS nextXid 101130 latestCompletedXid 101129 oldestRunningXid 101130
Logisch
Schließlich wird die letzte Ebene durch den Wert des Parameters
wal_level = logisch festgelegt und bietet die Möglichkeit der logischen Dekodierung und logischen Replikation. Es muss auf dem Publishing-Server aktiviert sein.
Aus Sicht von Journaleinträgen unterscheidet sich diese Ebene praktisch nicht von Replikatdatensätzen - Datensätze, die sich auf Replikationsursprünge beziehen, und beliebige logische Einträge, die dem Anwendungsprotokoll hinzugefügt werden können, werden hinzugefügt. Grundsätzlich hängt die logische Dekodierung von Informationen über laufende Transaktionen ab, da Sie eine Momentaufnahme der Daten erstellen müssen, um Änderungen im Systemkatalog zu verfolgen.
Jetzt werden wir nicht näher auf die Funktionsweise von Sicherung und Replikation eingehen - dies ist ein großes Thema für eine separate Artikelserie.
Zuverlässigkeit aufzeichnen
Es ist klar, dass der Journalmechanismus zuverlässig sein und Garantien für die Möglichkeit der Wiederherstellung in allen Situationen bieten muss (natürlich nicht im Zusammenhang mit einer Beschädigung des Datenträgers). Die Zuverlässigkeit wird von vielen Faktoren beeinflusst, von denen wir Caching, Datenkorruption und Atomizität von Aufzeichnungen berücksichtigen werden.
Caching
Auf dem Datenpfad zum nichtflüchtigen Speicher (z. B. einem Festplattenlaufwerk) befinden sich zahlreiche Caches.
Wenn ein Programm (ein beliebiges, aber in unserem Fall PostgreSQL) das Betriebssystem auffordert, etwas auf die Festplatte zu schreiben, überträgt das Betriebssystem die Daten in seinen Cache im RAM. Die tatsächliche Aufzeichnung erfolgt asynchron, abhängig von den Einstellungen des E / A-Schedulers des Betriebssystems.
Wenn das Betriebssystem beschließt, Daten zu schreiben, fallen diese in den Cache des Laufwerks (Festplatte). Die Antriebselektronik kann auch die Aufzeichnung verzögern, z. B. das Sammeln von Daten in Gruppen, deren gleichzeitige Aufzeichnung rentabler ist. Wenn ein RAID-Controller verwendet wird, wird zwischen dem Betriebssystem und dem Laufwerk eine andere Caching-Ebene angezeigt.
Wenn Sie also keine besonderen Maßnahmen ergreifen, ist völlig unklar, wann die Daten wirklich sicher gespeichert werden. Dies ist normalerweise nicht wichtig, aber es gibt kritische Stellen, an denen PostgreSQL sicherstellen muss, dass die Daten sicher geschrieben sind. Dies ist zunächst ein Journal (wenn der Journaleintrag die Festplatte nicht erreicht hat, verschwindet er zusammen mit dem Rest des RAM-Inhalts) und ein Prüfpunkt (es muss sichergestellt sein, dass die schmutzigen Seiten tatsächlich auf die Festplatte geschrieben wurden). Es gibt jedoch auch andere Situationen, z. B. die Ausführung nicht journalisierter Vorgänge auf der Mindeststufe usw.
Das Betriebssystem bietet Tools, die das sofortige Schreiben von Daten in den nichtflüchtigen Speicher gewährleisten müssen. Es gibt mehrere Optionen, aber es gibt zwei Hauptoptionen: Entweder wird nach der Aufzeichnung ein Synchronisierungsbefehl gegeben (fsync, fdatasync), oder beim Öffnen (oder Schreiben) einer Datei wird ein spezielles Flag für die Synchronisierung oder sogar die direkte Aufzeichnung angezeigt, wobei der Betriebssystem-Cache umgangen wird.
Für das Protokoll können Sie mit dem Dienstprogramm pg_test_fsync die Methode auswählen, die für ein bestimmtes Betriebssystem und ein bestimmtes Dateisystem am besten geeignet ist. Sie wird im Konfigurationsparameter
wal_sync_method installiert. Normale Dateien werden immer mit fsync synchronisiert.
Der subtile Punkt ist, dass bei der Auswahl einer Methode die Eigenschaften der Ausrüstung berücksichtigt werden müssen. Wenn Sie beispielsweise einen Controller verwenden, der von einer Backup-Batterie unterstützt wird, gibt es keinen Grund, den Cache nicht zu verwenden, da die Batterie bei einem Stromausfall Daten speichert.
Die Dokumentation enthält viele Details zu diesem Thema.
In jedem Fall ist die Synchronisation teuer und findet nicht öfter als unbedingt erforderlich statt (wir werden etwas weniger auf dieses Problem zurückkommen, wenn wir über die Leistung sprechen).
Im Allgemeinen kann die Synchronisation
deaktiviert werden (der Parameter
fsync ist dafür verantwortlich). In diesem Fall sollten Sie jedoch die Speicherzuverlässigkeit vergessen. Durch Deaktivieren von
fsync stimmen Sie zu, dass Daten jederzeit unwiederbringlich verloren gehen können. Wahrscheinlich ist die einzig sinnvolle Option, diese Option zu verwenden, die vorübergehende Steigerung der Produktivität, wenn Daten problemlos aus einer anderen Quelle wiederhergestellt werden können (z. B. während der ersten Migration).
Datenkorruption
Das Gerät ist fehlerhaft und die Daten können auf dem Medium beschädigt werden, wenn Daten über Schnittstellenkabel usw. übertragen werden. Einige dieser Fehler werden auf Hardwareebene verarbeitet, andere jedoch nicht.
Um das Problem rechtzeitig zu erkennen, werden die Journaleinträge immer mit Prüfsummen versehen.
Datenseiten können auch mit Prüfsummen geschützt werden. Derzeit ist dies nur möglich, wenn der Cluster initialisiert wurde. In PostgreSQL 12 können sie jedoch mit dem Dienstprogramm pg_checksums ein- und ausgeschaltet werden (allerdings noch nicht im laufenden Betrieb, sondern nur, wenn der Server gestoppt ist).
In einer Produktionsumgebung müssen Prüfsummen trotz des Aufwands für ihre Berechnung und Kontrolle enthalten sein. Dies verringert die Wahrscheinlichkeit, dass ein Fehler nicht rechtzeitig erkannt wird.
Reduziert, eliminiert aber nicht.
Erstens werden Prüfsummen nur beim Zugriff auf die Seite überprüft. Daher können Schäden unbemerkt bleiben, bis sie in alle Sicherungen eingehen. Aus diesem Grund sucht pg_probackup während der Sicherung nach Prüfsummen aller Seiten des Clusters.
Zweitens wird eine mit Nullen gefüllte Seite als korrekt angesehen. Wenn das Dateisystem die Datei versehentlich "aufhebt", bleibt dies möglicherweise unbemerkt.
Drittens schützen Prüfsummen nur die Hauptschicht der Datendateien. Die verbleibenden Ebenen und anderen Dateien (z. B. XACT-Transaktionsstatus) sind durch nichts geschützt.
Leider.
Mal sehen, wie es funktioniert. Stellen Sie zunächst sicher, dass Prüfsummen aktiviert sind (beachten Sie, dass dies bei der Installation eines Pakets auf Debian-ähnlichen Systemen nicht der Fall ist):
=> SHOW data_checksums;
data_checksums ---------------- on (1 row)
Der Parameter
data_checksums ist schreibgeschützt.
Hier ist die Datei, in der sich unsere Tabelle befindet:
=> SELECT pg_relation_filepath('wallevel');
pg_relation_filepath ---------------------- base/16386/24890 (1 row)
Stoppen Sie den Server und ändern Sie einige Bytes auf der Nullseite. Löschen Sie beispielsweise den letzten Protokolleintrag aus dem LSN-Header.
student$ sudo pg_ctlcluster 11 main stop
postgres$ dd if=/dev/zero of=/var/lib/postgresql/11/main/base/16386/24890 oflag=dsync conv=notrunc bs=1 count=8
8+0 records in 8+0 records out 8 bytes copied, 0,0083022 s, 1,0 kB/s
Grundsätzlich konnte der Server nicht gestoppt werden. Es reicht aus, dass die Seite auf die Festplatte geschrieben und aus dem Cache gezwungen wurde (andernfalls arbeitet der Server mit der Seite aus dem Cache). Ein solches Szenario ist jedoch schwieriger zu reproduzieren.
Jetzt starten wir den Server und versuchen die Tabelle zu lesen.
student$ sudo pg_ctlcluster 11 main start
=> SELECT * FROM wallevel;
WARNING: page verification failed, calculated checksum 23222 but expected 50884 ERROR: invalid page in block 0 of relation base/16386/24890
Was aber, wenn die Daten nicht aus dem Backup wiederhergestellt werden können? Mit
dem Parameter
ignore_checksum_failure können
Sie versuchen, die Tabelle zu lesen, natürlich mit dem Risiko, dass Daten verzerrt werden.
=> SET ignore_checksum_failure = on; => SELECT * FROM wallevel;
WARNING: page verification failed, calculated checksum 23222 but expected 50884 n --- 1 (1 row)
In diesem Fall läuft natürlich alles gut, da wir nur den Seitentitel und nicht die Daten selbst durcheinander gebracht haben.
Und noch etwas. Wenn die Prüfsummen aktiviert sind, werden Eingabeaufforderungsbits in das Protokoll geschrieben (wir haben sie zuvor
untersucht ), da eine Änderung eines beliebigen, auch nicht wesentlichen Bits auch zu einer Änderung der Prüfsumme führt. Wenn Prüfsummen
deaktiviert sind, ist der Parameter
wal_log_hints für das Schreiben von Hinweisbits in das
Protokoll verantwortlich .
Änderungen an Tooltip-Bits werden immer als
Ganzseitenbild (FPI, Ganzseitenbild) protokolliert, wodurch die Größe des Protokolls in der angegebenen Reihenfolge erhöht wird. In diesem Fall ist es sinnvoll, die Komprimierung von Vollbildern mit dem Parameter
wal_compression zu aktivieren (dieser Parameter wurde in Version 9.5
angezeigt ). Nachfolgend sehen wir uns bestimmte Zahlen an.
Atomaritätsrekord
Und schließlich gibt es das Problem der Atomizität der Aufzeichnung. Eine Datenbankseite benötigt mindestens 8 KB (es können 16 oder 32 KB sein), und auf einer niedrigen Ebene erfolgt die Aufzeichnung in Blöcken, die normalerweise kleiner sind (normalerweise 512 Byte oder 4 KB). Daher kann im Falle eines Stromausfalls die Datenseite teilweise aufgezeichnet werden. Es ist klar, dass es während der Wiederherstellung keinen Sinn macht, normale Journaleinträge auf eine solche Seite anzuwenden.
Zum Schutz können Sie mit PostgreSQL das
vollständige Bild der Seite in das Protokoll schreiben, wenn es nach dem Start des Prüfpunkts zum ersten Mal geändert wird (dasselbe Bild wird aufgezeichnet, wenn sich die Tooltip-Bits ändern). Der Parameter
full_page_writes steuert dies und ist standardmäßig aktiviert.
Wenn ein Seitenabbild während der Wiederherstellung in einem Protokoll auftritt, wird es bedingungslos (ohne LSN-Prüfung) auf die Festplatte geschrieben: Es besteht mehr Vertrauen, da es wie jeder Protokolldatensatz durch eine Prüfsumme geschützt ist. Und bereits regelmäßige Journaleinträge werden auf dieses garantiert korrekte Bild angewendet.
Obwohl PostgreSQL nicht zugewiesenen Speicherplatz aus dem Ganzseitenbild ausschließt (wir haben uns zuvor die Blockstruktur angesehen), nimmt das Volumen der generierten Journaleinträge erheblich zu. Wie bereits erwähnt, kann die Situation durch Komprimieren der vollständigen Bilder (Parameter
wal_compression ) verbessert werden.
Um die Änderung der Größe des Protokolls irgendwie zu spüren, führen wir ein einfaches Experiment mit dem Dienstprogramm pgbench durch. Initialisieren wir:
student$ pgbench -i test
dropping old tables... creating tables... generating data... 100000 of 100000 tuples (100%) done (elapsed 0.15 s, remaining 0.00 s) vacuuming... creating primary keys... done.
Die
Option full_page_writes ist aktiviert:
=> SHOW full_page_writes;
full_page_writes ------------------ on (1 row)
Führen Sie den Haltepunkt aus und führen Sie den Test sofort 30 Sekunden lang aus.
=> CHECKPOINT; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/38E04A08 (1 row)
student$ pgbench -T 30 test
starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 26851 latency average = 1.117 ms tps = 895.006720 (including connections establishing) tps = 895.095229 (excluding connections establishing)
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/3A69C478 (1 row)
Protokollgröße:
=> SELECT pg_size_pretty('0/3A69C478'::pg_lsn - '0/38E04A08'::pg_lsn);
pg_size_pretty ---------------- 25 MB (1 row)
Deaktivieren Sie nun den Parameter full_page_writes:
=> ALTER SYSTEM SET full_page_writes = off; => SELECT pg_reload_conf();
Und wiederhole das Experiment.
=> CHECKPOINT; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/3A69C530 (1 row)
student$ pgbench -T 30 test
starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 27234 latency average = 1.102 ms tps = 907.783080 (including connections establishing) tps = 907.895326 (excluding connections establishing)
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/3BE87658 (1 row)
Protokollgröße:
=> SELECT pg_size_pretty('0/3BE87658'::pg_lsn - '0/3A69C530'::pg_lsn);
pg_size_pretty ---------------- 24 MB (1 row)
Ja, die Größe hat abgenommen, aber überhaupt nicht so bedeutend, wie man es erwarten könnte.
Der Grund dafür ist, dass der Cluster mit Prüfsummen auf den Datenseiten initialisiert wird und Sie daher beim Ändern der QuickInfo-Bits immer noch ganzseitige Bilder in das Protokoll schreiben müssen. Diese Daten (in unserem Fall) machen etwa die Hälfte des Gesamtvolumens aus, was anhand der Statistiken ersichtlich ist:
postgres$ /usr/lib/postgresql/11/bin/pg_waldump --stats -p /var/lib/postgresql/11/main/pg_wal -s 0/3A69C530 -e 0/3BE87658
Type N (%) Record size (%) FPI size (%) ---- - --- ----------- --- -------- --- XLOG 1721 ( 1,03) 84329 ( 0,77) 13916104 (100,00) Transaction 27235 ( 16,32) 926070 ( 8,46) 0 ( 0,00) Storage 1 ( 0,00) 42 ( 0,00) 0 ( 0,00) CLOG 1 ( 0,00) 30 ( 0,00) 0 ( 0,00) Standby 4 ( 0,00) 240 ( 0,00) 0 ( 0,00) Heap2 27522 ( 16,49) 1726352 ( 15,76) 0 ( 0,00) Heap 109691 ( 65,71) 8169121 ( 74,59) 0 ( 0,00) Btree 756 ( 0,45) 45380 ( 0,41) 0 ( 0,00) -------- -------- -------- Total 166931 10951564 [44,04%] 13916104 [55,96%]
Aus Gründen der Kompaktheit habe ich die Nullzeilen aus der Tabelle entfernt. Achten Sie auf die Gesamtzeile (Gesamt) und vergleichen Sie die Größe der Vollbilder (FPI-Größe) mit der Größe normaler Datensätze (Datensatzgröße).
Der Parameter
full_page_writes kann nur deaktiviert werden, wenn das Dateisystem und die Hardware, die für sich verwendet werden, eine atomare Aufzeichnung gewährleisten. Wie wir jedoch sehen können, gibt es dafür keinen guten Grund (vorausgesetzt, dass Prüfsummen enthalten sind).
Nun wollen wir sehen, wie die Komprimierung hilft.
=> ALTER SYSTEM SET full_page_writes = on; => ALTER SYSTEM SET wal_compression = on; => SELECT pg_reload_conf();
Wiederholen Sie das gleiche Experiment.
=> CHECKPOINT; => SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/3BE87710 (1 row)
student$ pgbench -T 30 test
starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 26833 latency average = 1.118 ms tps = 894.405027 (including connections establishing) tps = 894.516845 (excluding connections establishing)
=> SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 0/3CBD3EA8 (1 row)
Protokollgröße:
=> SELECT pg_size_pretty('0/3CBD3EA8'::pg_lsn - '0/3BE87710'::pg_lsn);
pg_size_pretty ---------------- 13 MB (1 row)
Schlussfolgerung: Bei einer großen Anzahl von Ganzseitenbildern (aufgrund von Prüfsummen oder
Vollseitenschreibungen ,
dh fast immer) ist es höchstwahrscheinlich sinnvoll, die Komprimierung zu verwenden, obwohl dadurch der Prozessor
geladen wird.
Leistung
Während des normalen Serverbetriebs erfolgt eine kontinuierliche sequentielle Aufzeichnung von Protokolldateien. Da es keinen wahlfreien Zugriff gibt, erfüllen auch normale Festplatten diese Aufgabe. Diese Art des Ladens unterscheidet sich jedoch erheblich von der Art und Weise, wie auf die Datendateien zugegriffen wird.
Daher ist es normalerweise vorteilhaft, das Protokoll auf einer separaten physischen Festplatte (oder einem Festplattenarray) abzulegen, die auf dem Server-Dateisystem bereitgestellt ist. Anstelle des Verzeichnisses $ PGDATA / pg_wal müssen Sie einen symbolischen Link zum entsprechenden Verzeichnis erstellen.
Es gibt einige Situationen, in denen Protokolldateien nicht nur geschrieben, sondern auch gelesen werden müssen. Der erste ist ein verständlicher Fall der Wiederherstellung nach einem Fehler. Der zweite ist weniger trivial. Dies tritt auf, wenn die Streaming-Replikation verwendet wird und das Replikat keine Journaleinträge empfängt, während sie sich noch in den RAM-Puffern des Hauptservers befinden. Dann muss der Walsender-Prozess die erforderlichen Daten von der Festplatte lesen. Wir werden darüber ausführlicher sprechen, wenn wir zur Replikation kommen.
Die Protokollierung erfolgt in einem von zwei Modi:
- synchron - Wenn eine Transaktion festgeschrieben wird, ist die Fortsetzung der Arbeit erst möglich, wenn sich alle Journaleinträge zu dieser Transaktion auf der Festplatte befinden.
- asynchron - Die Transaktion wird sofort abgeschlossen und das Protokoll wird im Hintergrund geschrieben.
Der Synchronmodus wird durch den Parameter
synchronous_commit bestimmt und ist standardmäßig aktiviert.
Da die Synchronisation mit einer realen (d. H. Langsamen) E / A verbunden ist, ist es vorteilhaft, sie so wenig wie möglich durchzuführen. Zu diesem
Zweck macht der Wartungsprozess, der die Transaktion abschließt und das Protokoll schreibt, eine kurze Pause, die durch den Parameter
commit_delay festgelegt wird. ,
commit_siblings . , . , , - .
commit_siblings = 5,
commit_delay = 0, .
commit_delay , OLTP-.
LSN ( , ). .
( D ACID) — , . , ( COMMIT ) .
,
synchronous_commit = off ( local).
wal writer, (
wal_writer_delay = 200ms ).
, , WAL. , , , , . (, : , , .)
, ( ) — ?
, , .
— . : , 3 ×
wal_writer_delay ( ).
— — .
: (
fsync = off), . , , , .
synchronous_commit . , . , , .
. , WAL. , , .
- , , pgbench.
=> ALTER SYSTEM SET synchronous_commit = off; => SELECT pg_reload_conf();
student$ pgbench -T 30 test
starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 45439 latency average = 0.660 ms tps = 1514.561710 (including connections establishing) tps = 1514.710558 (excluding connections establishing)
900 (tps), — 1500. , , , .
. - , . !
, .