Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)

Tahaksin teiega jagada oma esimest edukat kogemust Postgresi andmebaasi täielikuks taastamiseks. Postgresi DBMS-iga tutvusin pool aastat tagasi, enne seda puudus andmebaasihalduse kogemus üldse.

Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)

Töötan suures IT-ettevõttes pool-DevOpsi insenerina. Meie ettevõte arendab tarkvara suure koormusega teenuste jaoks ning mina vastutan jõudluse, hoolduse ja juurutamise eest. Mulle anti standardülesanne: uuendada rakendust ühes serveris. Rakendus on kirjutatud Django keeles, uuenduse käigus tehakse migratsioonid (muudatused andmebaasi struktuuris) ja enne seda protsessi teeme igaks juhuks läbi standardse pg_dump programmi täieliku andmebaasi dump.

Prügi tegemisel ilmnes ootamatu tõrge (Postgresi versioon 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

Viga "kehtetu leht plokis" räägib probleemidest failisüsteemi tasemel, mis on väga halb. Erinevates foorumites soovitati seda teha TÄISVAKUUM valikuga null_kahjustatud_lehekülge selle probleemi lahendamiseks. No proovime...

Ettevalmistus taastumiseks

ETTEVAATUST Enne andmebaasi taastamise katset tehke kindlasti Postgresi varukoopia. Kui teil on virtuaalmasin, peatage andmebaas ja tehke hetktõmmis. Kui hetktõmmist pole võimalik teha, peatage andmebaas ja kopeerige Postgresi kataloogi sisu (sh wali failid) kindlasse kohta. Meie äris on peamine asi mitte hullemaks teha. Lugege это.

Kuna andmebaas üldiselt töötas minu jaoks, piirdusin tavalise andmebaasi tõmmisega, kuid jätsin välja kahjustatud andmetega tabeli (valik -T, --välista-tabel=TABEL jaotises pg_dump).

Server oli füüsiline, hetkepilti ei saanud teha. Varukoopia on eemaldatud, liigume edasi.

Failisüsteemi kontroll

Enne andmebaasi taastamist peame veenduma, et failisüsteemi endaga on kõik korras. Ja vigade korral paranda need, sest muidu saad asja ainult hullemaks teha.

Minu puhul oli andmebaasiga failisüsteem ühendatud "/srv" ja tüüp oli ext4.

Andmebaasi peatamine: systemctl stop [meiliga kaitstud] ja kontrollige, et failisüsteemi ei kasutaks keegi ja et seda saaks käsu abil lahti ühendada lsof:
lsof +D /srv

Pidin ka redise andmebaasi peatama, kuna ka see kasutas "/srv". Järgmiseks võtsin lahti / srv (summa).

Failisüsteemi kontrolliti utiliidi abil e2fsck lülitiga -f (Sundkontroll isegi siis, kui failisüsteem on märgitud puhtaks):

Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)

Järgmiseks utiliidi kasutamine dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep kontrollitud) saate kontrollida, kas kontroll on tegelikult tehtud:

Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)

e2fsck ütleb, et ext4 failisüsteemi tasemel probleeme ei leitud, mis tähendab, et võite jätkata andmebaasi taastamist või pigem naasta vaakum täis (muidugi peate failisüsteemi tagasi ühendama ja andmebaasi käivitama).

Kui teil on füüsiline server, kontrollige kindlasti ketaste olekut (via smartctl -a /dev/XXX) või RAID-kontrolleri, et veenduda, et probleem pole riistvara tasemel. Minu puhul osutus RAID "riistvaraks", nii et palusin kohalikul administraatoril RAID-i olekut kontrollida (server asus minust mitmesaja kilomeetri kaugusel). Ta ütles, et vigu ei olnud, mis tähendab, et saame kindlasti alustada taastamist.

1. katse: null_kahjustatud_lehekülge

Ühendame andmebaasiga psql-i kaudu kontoga, millel on superkasutaja õigused. Meil on vaja superkasutajat, sest... valik null_kahjustatud_lehekülge ainult tema saab muutuda. Minu puhul on see postgres:

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

Variant null_kahjustatud_lehekülge vajalik lugemisvigade ignoreerimiseks (postgrespro veebisaidilt):

Kui PostgreSQL tuvastab rikutud lehe päise, teatab see tavaliselt veast ja katkestab praeguse tehingu. Kui zero_damaged_pages on lubatud, annab süsteem selle asemel hoiatuse, nullib kahjustatud lehe mälust välja ja jätkab töötlemist. See käitumine hävitab andmed, nimelt kõik kahjustatud lehe read.

Lubame valiku ja proovime teha tabelite täieliku vaakumi:

VACUUM FULL VERBOSE

Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)
Kahjuks halb õnn.

Leidsime sarnase vea:

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_röstsai – mehhanism “pikkade andmete” salvestamiseks Poetgresesse, kui need ühele lehele ei mahu (vaikimisi 8kb).

2. katse: uuesti indekseerida

Google'i esimene nõuanne ei aidanud. Peale paariminutilist otsimist leidsin teise nipi – teha uuesti indekseerida kahjustatud laud. Nägin seda nõuannet paljudes kohtades, kuid see ei äratanud usaldust. Indekseerime uuesti:

reindex table ws_log_smevlog

Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)

uuesti indekseerida probleemideta lõpule viidud.

See aga ei aidanud, VAKUUM TÄIS kukkus sarnase veaga kokku. Kuna olen ebaõnnestumistega harjunud, hakkasin Internetist nõu otsima ja leidsin üsna huvitava artiklit.

3. katse: SELECT, LIMIT, OFFSET

Ülaltoodud artikkel soovitas tabelit ridade kaupa vaadata ja probleemsed andmed eemaldada. Kõigepealt pidime vaatama kõiki ridu:

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

Minu puhul sisaldas tabel +1 628 read! Selle eest oli vaja hästi hoolitseda andmete jaotamine, kuid see on eraldi arutelu teema. Oli laupäev, täitsin tmuxis selle käsu ja läksin magama:

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

Hommikuks otsustasin vaadata, kuidas läheb. Endalegi üllatuseks avastasin, et 20 tunni pärast oli skannitud vaid 2% andmetest! Ma ei tahtnud 50 päeva oodata. Järjekordne täielik ebaõnnestumine.

Aga ma ei andnud alla. Mõtlesin, miks skaneerimine nii kaua aega võttis. Dokumentatsioonist (taas postgrespros) sain teada:

OFFSET määrab määratud arvu ridade vahelejätmise enne ridade väljastamise alustamist.
Kui on määratud nii OFFSET kui ka LIMIT, jätab süsteem esmalt OFFSET read vahele ja seejärel hakkab piirangu LIMIT ridu loendama.

LIMIT kasutamisel on oluline kasutada ka ORDER BY klauslit, et tulemusread tagastataks kindlas järjekorras. Vastasel juhul tagastatakse ridade ettearvamatud alamhulgad.

Ilmselgelt oli ülaltoodud käsk vale: esiteks ei olnud tellida, võib tulemus olla ekslik. Teiseks pidi Postgres kõigepealt skannima ja vahele jätma OFFSET-i ridu ning seda suurendades OFFSET tootlikkus väheneks veelgi.

4. katse: tehke tekstikujuline prügimägi

Siis tuli mulle pähe pealtnäha geniaalne idee: võtta tekstivormis prügimägi ja analüüsida viimast salvestatud rida.

Kuid kõigepealt vaatame tabeli struktuuri. ws_log_smevlog:

Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)

Meie puhul on meil veerg "Id", mis sisaldas rea unikaalset identifikaatorit (loendurit). Plaan oli selline:

  1. Hakkame tekstivormingus tõmmist võtma (sql-käskude kujul)
  2. Teatud ajahetkel katkeb tõmmis vea tõttu, kuid tekstifail salvestatakse siiski kettale
  3. Vaatame tekstifaili lõppu ja leiame viimase edukalt eemaldatud rea identifikaatori (id).

Hakkasin tekstivormis prügimäge tegema:

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

Prügila, nagu oodatud, katkestati sama veaga:

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

Edasi läbi saba Vaatasin prügimäe lõppu (saba -5 ./my_dump.dump) avastas, et prügila katkestati liinil id 186 525. "Nii et probleem on real ID-ga 186 526, see on katki ja see tuleb kustutada!" - Ma mõtlesin. Kuid andmebaasi päringu tegemine:
«valige * ws_log_smevlogist, kus id=186529«Selgus, et selle joonega on kõik korras... Ka read indeksitega 186 530 - 186 540 töötasid probleemideta. Veel üks "hiilgav idee" ebaõnnestus. Hiljem sain aru, miks see juhtus: tabelist andmete kustutamisel ja muutmisel neid füüsiliselt ei kustutata, vaid märgitakse “surnud korteežideks”, siis tuleb autovaakum ja märgib need read kustutatuks ning lubab neid ridu uuesti kasutada. Et aru saada, kui tabelis olevad andmed muutuvad ja autovaakum on lubatud, siis neid järjestikku ei salvestata.

5. katse: SELECT, FROM, WHERE id=

Ebaõnnestumised teevad meid tugevamaks. Te ei tohiks kunagi alla anda, peate minema lõpuni ja uskuma endasse ja oma võimetesse. Seega otsustasin proovida teist võimalust: lihtsalt vaadata ükshaaval läbi kõik andmebaasis olevad kirjed. Teades minu tabeli struktuuri (vt ülal), on meil id väli, mis on unikaalne (esmane võti). Meil on tabelis 1 628 991 rida ja id on korras, mis tähendab, et saame need ükshaaval läbi vaadata:

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

Kui keegi aru ei saa, töötab käsk järgmiselt: skannib tabelit ridade kaupa ja saadab stdout / dev / null, aga kui käsk SELECT ebaõnnestub, siis prinditakse veatekst (stderr saadetakse konsooli) ja trükitakse viga sisaldav rida (tänu ||, mis tähendab, et valikul tekkisid probleemid (käsu tagastuskood). ei ole 0)).

Mul vedas, mul olid väljakul indeksid tehtud id:

Minu esimene kogemus Postgresi andmebaasi taastamisega pärast riket (kehtetu leht relatton base/4123007 plokis 16490)

See tähendab, et soovitud ID-ga rea ​​leidmine ei tohiks võtta palju aega. Teoreetiliselt peaks see toimima. Noh, käivitame käsu tmux ja lähme magama.

Hommikuks avastasin, et vaadatud on umbes 90 000 kirjet, mis on veidi üle 5%. Suurepärane tulemus võrreldes eelmise meetodiga (2%)! Aga ma ei tahtnud 20 päeva oodata...

6. katse: SELECT, FROM, WHERE id >= ja id <

Kliendil oli andmebaasile pühendatud suurepärane server: kahe protsessoriga Intel Xeon E5-2697 v2, meie asukohas oli koguni 48 lõime! Serveri koormus oli keskmine, saime probleemideta alla laadida umbes 20 lõime. RAM-i oli ka piisavalt: koguni 384 gigabaiti!

Seetõttu tuli käsk paralleelstada:

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

Siin oli võimalik kirjutada ilus ja elegantne skript, kuid valisin kiireima paralleelsusmeetodi: jagage vahemik 0-1628991 käsitsi 100 000 kirje pikkusteks intervallideks ja käivitage eraldi 16 vormi käsku:

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

Kuid see pole veel kõik. Teoreetiliselt võtab andmebaasiga ühenduse loomine ka omajagu aega ja süsteemiressursse. 1 628 991 ühendamine ei olnud väga tark, nõustute. Seetõttu toogem ühe ühenduse asemel 1000 rida. Selle tulemusel muutus meeskond järgmiseks:

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

Avage tmuxi seansis 16 akent ja käivitage käsud:

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

Päev hiljem sain esimesed tulemused! Nimelt (väärtusi XXX ja ZZZ enam ei säilitata):

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

See tähendab, et kolm rida sisaldavad viga. Esimese ja teise probleemkirje id jäid 829 000 ja 830 000 vahele, kolmanda id 146 000 ja 147 000 vahele. Edasi tuli lihtsalt leida probleemikirjete täpne id väärtus. Selleks vaatame läbi probleemsete kirjetega vahemiku sammuga 1 ja tuvastame 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

Õnnelik lõpp

Leidsime probleemsed read. Me läheme psql-i kaudu andmebaasi ja proovime need kustutada:

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

Minu üllatuseks kustutati kirjed probleemideta ka ilma võimaluseta null_kahjustatud_lehekülge.

Siis ühendasin andmebaasiga, tegin VAKUUM TÄIS (Ma arvan, et seda polnud vaja teha) ja lõpuks eemaldasin varukoopia edukalt kasutades pg_dump. Prügila võeti vigadeta! Probleem lahendati nii rumal viisil. Rõõmul polnud piire, peale nii palju ebaõnnestumisi õnnestus leida lahendus!

Tänuavaldused ja järeldused

Nii kujunes minu esimene kogemus tõelise Postgresi andmebaasi taastamisest. See kogemus jääb mulle kauaks meelde.

Ja lõpuks tahaksin tänada PostgresPro dokumentatsiooni vene keelde tõlkimise ja eest täiesti tasuta veebikursused, mis aitas probleemi analüüsimisel palju kaasa.

Allikas: www.habr.com

Lisa kommentaar