Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)

Ech wéilt mat Iech meng éischt erfollegräich Erfahrung deelen fir eng Postgres Datebank op voll Funktionalitéit ze restauréieren. Ech hunn den Postgres DBMS virun engem hallwe Joer kennegeléiert, virdrun hat ech guer keng Erfahrung an der Datebankverwaltung.

Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)

Ech schaffen als semi-DevOps Ingenieur an enger grousser IT Firma. Eis Firma entwéckelt Software fir High-Laast Servicer, an ech si verantwortlech fir Leeschtung, Ënnerhalt an Deployment. Ech krut eng Standardaufgab: eng Applikatioun op engem Server ze aktualiséieren. D'Applikatioun ass an Django geschriwwe ginn, während den Update gi Migratiounen duerchgefouert (Ännerungen an der Datebankstruktur), a virun dësem Prozess huelen mir e komplette Datebank Dump duerch de Standard pg_dump Programm, just am Fall.

En onerwaarte Feeler ass geschitt beim Ofhuelen vun engem Dump (Postgres Versioun 9.5):

pg_dump: Oumping the contents of table “ws_log_smevlog” failed: PQgetResult() failed.
pg_dump: Error message from server: ERROR: invalid page in block 4123007 of relatton base/16490/21396989
pg_dump: The command was: COPY public.ws_log_smevlog [...]
pg_dunp: [parallel archtver] a worker process dled unexpectedly

Feeler "ongëlteg Säit am Block" schwätzt vu Problemer um Dateisystemniveau, wat ganz schlecht ass. Op verschiddene Foren gouf proposéiert ze maachen VOLLZÄIT Vakuum mat Optioun null_beschiedegt_Säiten dëse Problem ze léisen. Gutt, loosst eis probéieren ...

Virbereedung fir Erhuelung

AENTWERT! Gitt sécher e Postgres Backup ze huelen ier all Versuch Är Datebank ze restauréieren. Wann Dir eng virtuell Maschinn hutt, stoppt d'Datebank an huelt e Snapshot. Wann et net méiglech ass e Snapshot ze maachen, stoppt d'Datebank a kopéiert den Inhalt vum Postgres Verzeichnis (inklusiv wal Dateien) op eng sécher Plaz. Den Haapt Saach an eisem Geschäft ass net d'Saache méi schlecht ze maachen. Liesen et.

Well d'Datebank allgemeng fir mech geschafft huet, hunn ech mech op e reguläre Datebankdump limitéiert, awer den Dësch mat beschiedegten Donnéeën ausgeschloss (Optioun -T, --exclude-table=TABEL an pg_dump).

De Server war kierperlech, et war onméiglech e Snapshot ze huelen. De Backup ass geläscht, loosst eis weidergoen.

Dateisystem kontrolléieren

Ier Dir probéiert d'Datebank ze restauréieren, musse mir sécher sinn datt alles an der Rei ass mam Dateiesystem selwer. An am Fall vu Feeler, korrigéiert se, well soss kënnt Dir d'Saachen nëmme méi schlëmm maachen.

A mengem Fall ass de Dateiesystem mat der Datebank montéiert "/srv" an den Typ war ext4.

D'Datebank stoppen: systemctl stoppen [Email geschützt] a kontrolléiert datt de Dateiesystem net vu jidderengem benotzt gëtt a ka mat dem Kommando demontéiert ginn lsof:
lsof +D /srv

Ech hunn och d'Redis Datebank ze stoppen, well se och benotzt "/srv". Als nächst hunn ech demontéiert / srv (umount).

De Dateiesystem gouf mam Utility iwwerpréift e2 fsch mam Schalter -f (Kraaft Iwwerpréiwung och wann Dateiesystem propper markéiert ass):

Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)

Als nächst, benotzt den Utility domm 2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep iwwerpréift) Dir kënnt verifizéieren datt d'Kontroll wierklech gemaach gouf:

Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)

e2 fsch seet datt keng Probleemer um ext4 Dateisystemniveau fonnt goufen, dat heescht datt Dir weider kënnt probéieren d'Datebank ze restauréieren, oder éischter zréck op Vakuum voll (natierlech musst Dir de Dateiesystem erëm montéieren an d'Datebank starten).

Wann Dir e kierperleche Server hutt, gitt sécher de Status vun den Disken ze kontrolléieren (via smartctl -a /dev/XXX) oder RAID Controller fir sécher ze stellen datt de Problem net um Hardwareniveau ass. A mengem Fall huet d'RAID als "Hardware" erausgestallt, also hunn ech de lokalen Admin gefrot fir de Status vun der RAID ze kontrolléieren (de Server war e puer honnert Kilometer vu mir ewech). Hien huet gesot datt et keng Feeler waren, dat heescht datt mir definitiv d'Restauratioun ufänken.

Versuch 1: zero_damaged_pages

Mir verbannen mat der Datebank iwwer psql mat engem Kont deen Superuser Rechter huet. Mir brauchen e Superuser, well ... Optioun null_beschiedegt_Säiten nëmmen hie kann änneren. A mengem Fall ass et postgres:

psql -h 127.0.0.1 -U postgres -s [database_name]

Optioun null_beschiedegt_Säiten néideg fir Liesfehler ze ignoréieren (vun der Postgrespro Websäit):

Wann PostgreSQL e korrupte Säit Header erkennt, mellt et normalerweis e Feeler an hält déi aktuell Transaktioun of. Wann zero_damaged_pages ageschalt ass, gëtt de System amplaz eng Warnung eraus, nulléiert déi beschiedegt Säit an der Erënnerung a geet weider mat der Veraarbechtung. Dëst Verhalen zerstéiert Daten, nämlech all Reihen op der beschiedegter Säit.

Mir aktivéieren d'Optioun a probéieren e vollen Vakuum vun den Dëscher ze maachen:

VACUUM FULL VERBOSE

Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)
Leider Pech.

Mir hunn en ähnleche Feeler begéint:

INFO: vacuuming "“public.ws_log_smevlog”
WARNING: invalid page in block 4123007 of relation base/16400/21396989; zeroing out page
ERROR: unexpected chunk number 573 (expected 565) for toast value 21648541 in pg_toast_106070

pg_toast - e Mechanismus fir "laang Daten" am Poetgres ze späicheren wann se net op enger Säit passen (8kb par défaut).

Versuch 2: reindexéieren

Déi éischt Berodung vu Google huet net gehollef. No e puer Minutten Sich hunn ech den zweeten Tipp fonnt - ze maachen reindexéieren beschiedegt Dësch. Ech hunn dëse Rot op ville Plazen gesinn, awer et huet kee Vertrauen inspiréiert. Loosst eis nei indexéieren:

reindex table ws_log_smevlog

Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)

reindexéieren ouni Problemer ofgeschloss.

Dat huet awer net gehollef, VAKUUM VOLLZÄIT mat engem ähnleche Feeler erofgefall. Well ech un Feeler gewinnt sinn, hunn ech ugefaang fir Berodung um Internet weider ze sichen an hunn eng zimlech interessant fonnt Artikel.

Versuch 3: SELECT, LIMIT, OFFSET

Den Artikel hei uewen huet virgeschloen d'Tabell Rei fir Zeil ze kucken an problematesch Donnéeën ze läschen. Als éischt musse mir all d'Linnen kucken:

for ((i=0; i<"Number_of_rows_in_nodes"; i++ )); do psql -U "Username" "Database Name" -c "SELECT * FROM nodes LIMIT 1 offset $i" >/dev/null || echo $i; done

A mengem Fall, den Dësch enthält 1 628 991 Linnen! Et war néideg gutt ze këmmeren Datepartitionéierung, awer dëst ass en Thema fir eng separat Diskussioun. Et war e Samschdeg, ech hunn dëse Kommando an tmux gemaach an sinn an d'Bett gaang:

for ((i=0; i<1628991; i++ )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog LIMIT 1 offset $i" >/dev/null || echo $i; done

Bis de Moien hunn ech beschloss ze kucken wéi et leeft. Zu menger Iwwerraschung hunn ech entdeckt datt no 20 Stonnen nëmmen 2% vun den Donnéeën gescannt goufen! Ech wollt net 50 Deeg waarden. En anere komplette Feeler.

Mee ech hunn net opginn. Ech hu mech gefrot firwat d'Scannen esou laang gedauert hunn. Aus der Dokumentatioun (erëm op postgrespro) hunn ech erausfonnt:

OFFSET spezifizéiert fir déi spezifizéiert Zuel vu Reihen ze iwwersprangen ier Dir ufänkt d'Zeilen auszeginn.
Wa béid OFFSET a LIMIT spezifizéiert sinn, spréngt de System als éischt d'OFFSET Reihen iwwer an fänkt dann un d'Reihen fir d'LIMIT Constraint ze zielen.

Wann Dir LIMIT benotzt, ass et wichteg och eng ORDER BY Klausel ze benotzen fir datt d'Resultatreihen an enger spezifescher Uerdnung zréckginn. Soss ginn onberechenbar Ënnersätz vu Reihen zréck.

Natierlech war dat uewe genannte Kommando falsch: éischtens gouf et nee Uerdnung duerch, d'Resultat kéint falsch sinn. Zweetens, Postgres huet als éischt OFFSET Reihen ze scannen an iwwersprangen, a mat der Erhéijung OFFSET d'Produktivitéit géif nach weider erofgoen.

Versuch 4: Huelt en Dump an Textform

Dunn koum eng scheinbar genial Iddi a mengem Kapp: Huelt en Dump an Textform an analyséiert déi lescht opgeholl Zeil.

Awer als éischt, loosst eis d'Struktur vum Dësch kucken. ws_log_smevlog:

Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)

An eisem Fall hu mir eng Kolonn "Id", déi den eenzegaartegen Identifizéierer (Zähler) vun der Zeil enthält. De Plang war esou:

  1. Mir fänken un en Dump an Textform ze huelen (a Form vu sql Kommandoen)
  2. Zu engem gewëssen Zäitpunkt wier den Dump wéinst engem Feeler ënnerbrach ginn, awer d'Textdatei wier nach ëmmer op Disk gespäichert
  3. Mir kucken um Enn vun der Textdatei, doduerch fanne mir den Identifizéierer (ID) vun der leschter Zeil déi erfollegräich geläscht gouf

Ech hunn ugefaang en Dump an Textform ze huelen:

pg_dump -U my_user -d my_database -F p -t ws_log_smevlog -f ./my_dump.dump

Den Dump, wéi erwaart, gouf mam selwechte Feeler ënnerbrach:

pg_dump: Error message from server: ERROR: invalid page in block 4123007 of relatton base/16490/21396989

Weider duerch Schwäif Ech hunn um Enn vum Dump gekuckt (Schwäif -5 ./my_dump.dump) entdeckt datt den Dump op der Linn mat id ënnerbrach gouf 186 525. "Also ass de Problem an der Linn mat ID 186 526, et ass gebrach, a muss geläscht ginn!" - Ech duecht. Awer eng Ufro un d'Datebank maachen:
«wielt * aus ws_log_smevlog wou ID = 186529"Et huet sech erausgestallt datt alles gutt war mat dëser Linn ... Reihen mat Indizes 186 - 530 hunn och ouni Probleemer geschafft. Eng aner "genial Iddi" huet gescheitert. Méi spéit hunn ech verstan firwat dat geschitt ass: wann Dir Daten aus engem Dësch läschen an änneren, ginn se net kierperlech geläscht, awer als "dout Tuples" markéiert, da kënnt autovakuum a markéiert dës Linnen als geläscht an erlaabt datt dës Linnen erëmbenotzt ginn. Fir ze verstoen, wann d'Donnéeën an der Tabell ännert an den Autovakuum aktivéiert ass, da gëtt se net sequenziell gespäichert.

Versuch 5: SELECT, FROM, WHERE id=

Feeler maachen eis méi staark. Dir sollt ni opginn, Dir musst bis zum Schluss goen an un Iech selwer an Är Fäegkeeten gleewen. Also hunn ech beschloss eng aner Optioun ze probéieren: kuckt just duerch all d'Records an der Datebank een nom aneren. Wann Dir d'Struktur vu mengem Dësch kennt (kuckt hei uewen), hu mir en ID Feld dat eenzegaarteg ass (primär Schlëssel). Mir hunn 1 Zeile an der Tabell an id sinn an der Rei, dat heescht datt mir se einfach een nom aneren duerchgoe kënnen:

for ((i=1; i<1628991; i=$((i+1)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done

Wann iergendeen et net versteet, funktionnéiert de Kommando wéi follegt: et scannt den Dësch Rei fir Zeil a schéckt stdout op / dev / null, awer wann de SELECT Kommando feelt, da gëtt de Feelertext gedréckt (stderr gëtt op d'Konsole geschéckt) an eng Zeil mat de Fehler gëtt gedréckt (dank ||, dat heescht datt d'Auswiel Problemer hat (de Retourcode vum Kommando). ass net 0)).

Ech hat Gléck, ech hat Indexen um Terrain erstallt id:

Meng éischt Erfarung fir eng Postgres-Datebank no engem Feeler ze recuperéieren (ongëlteg Säit am Block 4123007 vun der Relatton Base/16490)

Dëst bedeit datt eng Zeil mat der gewënschter ID net vill Zäit brauch. Theorie soll et funktionéieren. Gutt, loosst eis de Kommando lafen tmux a loosst eis schlofen.

Bis de Moien hunn ech festgestallt, datt ongeféier 90 Entréen gekuckt goufen, dat ass just iwwer 000%. En exzellent Resultat am Verglach mat der viregter Method (5%)! Awer ech wollt net 2 Deeg waarden ...

Versuch 6: SELECT, FROM, WHERE id >= an id <

De Client hat en exzellente Server fir d'Datebank gewidmet: Dual-Prozessor Intel Xeon E5-2697 v2, et waren esou vill wéi 48 Threads op eiser Plaz! D'Laascht op de Server war duerchschnëttlech; mir konnten ongeféier 20 Threads ouni Probleemer eroflueden. Et war och genuch RAM: sou vill wéi 384 Gigabyte!

Dofir muss de Kommando paralleliséiert ginn:

for ((i=1; i<1628991; i=$((i+1)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done

Hei war et méiglech e schéinen an elegante Skript ze schreiwen, awer ech hunn déi séierst Paralleliséierungsmethod gewielt: manuell d'Bereich 0-1628991 an Intervalle vun 100 Opzeechnungen opgedeelt a separat 000 Kommandoe vun der Form lafen:

for ((i=N; i<M; i=$((i+1)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done

Mä dat ass net alles. An der Theorie hëlt d'Verbindung mat enger Datebank och e puer Zäit a Systemressourcen. Verbindung 1 war net ganz schlau, Dir wäert averstanen. Dofir, loosst eis 628 Zeile recuperéieren amplaz vun enger Verbindung. Als Resultat huet d'Team an dëst transforméiert:

for ((i=N; i<M; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done

Öffnen 16 Fënsteren an enger tmux Sessioun a lafen d'Befehle:

1) for ((i=0; i<100000; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done
2) for ((i=100000; i<200000; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done
…
15) for ((i=1400000; i<1500000; i=$((i+1000)) )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done
16) for ((i=1500000; i<1628991; i=$((i+1000)) )); do psql -U my_user -d my_database  -c "SELECT * FROM ws_log_smevlog where id>=$i and id<$((i+1000))" >/dev/null || echo $i; done

Een Dag méi spéit krut ech déi éischt Resultater! Nämlech (d'Wäerter XXX an ZZZ sinn net méi erhale bleiwen):

ERROR:  missing chunk number 0 for toast value 37837571 in pg_toast_106070
829000
ERROR:  missing chunk number 0 for toast value XXX in pg_toast_106070
829000
ERROR:  missing chunk number 0 for toast value ZZZ in pg_toast_106070
146000

Dëst bedeit datt dräi Zeilen e Feeler enthalen. D'Ids vun den éischten an zweete Problemrecords waren tëscht 829 an 000, d'IDs vun der Drëtter waren tëscht 830 an 000. Als nächst musse mir einfach de genauen Id-Wäert vun de Problemrecords fannen. Fir dëst ze maachen, kucke mir eis Gamme mat problematesche Rekorder mat engem Schrëtt vun 146 duerch an identifizéieren d'ID:

for ((i=829000; i<830000; i=$((i+1)) )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done
829417
ERROR:  unexpected chunk number 2 (expected 0) for toast value 37837843 in pg_toast_106070
829449
for ((i=146000; i<147000; i=$((i+1)) )); do psql -U my_user -d my_database -c "SELECT * FROM ws_log_smevlog where id=$i" >/dev/null || echo $i; done
829417
ERROR:  unexpected chunk number ZZZ (expected 0) for toast value XXX in pg_toast_106070
146911

Glécklecht Enn

Mir hunn déi problematesch Linnen fonnt. Mir ginn an d'Datebank iwwer psql a probéieren se ze läschen:

my_database=# delete from ws_log_smevlog where id=829417;
DELETE 1
my_database=# delete from ws_log_smevlog where id=829449;
DELETE 1
my_database=# delete from ws_log_smevlog where id=146911;
DELETE 1

Zu menger Iwwerraschung goufen d'Entréeën ouni Problemer och ouni Optioun geläscht null_beschiedegt_Säiten.

Duerno hunn ech mat der Datebank verbonnen, gemaach VAKUUM VOLLZÄIT (Ech mengen et war net néideg dëst ze maachen), a schlussendlech hunn ech de Backup mat Erfolleg geläscht pg_dump. Den Dump gouf ouni Feeler geholl! De Problem gouf op esou eng domm Manéier geléist. D'Freed wousst keng Grenzen, no sou vill Feeler hu mir et fäerdeg bruecht eng Léisung ze fannen!

Acknowledgements a Conclusioun

Dëst ass wéi meng éischt Erfarung fir eng richteg Postgres Datebank ze restauréieren huet sech erausgestallt. Ech wäert dës Erfahrung fir eng laang Zäit erënneren.

A schliisslech wëll ech dem PostgresPro Merci soen fir d'Dokumentatioun op Russesch ze iwwersetzen a fir komplett gratis online Coursen, wat bei der Analyse vum Problem vill gehollef huet.

Source: will.com

Setzt e Commentaire