Soovin jagada oma esimest edukaid kogemusi Postgressi andmebaasi tÀisfunktsionaalsuse taastamisel. Olen PostgreSQL-iga tutvunud pool aastat tagasi, enne seda polnud mul mingit andmebaaside haldamise kogemust.

Töötan osaliselt DevOps insenerina suurtes IT-ettevĂ”tetes. Meie ettevĂ”te tegeleb tarkvara arendamisega suure koormusega teenuste jaoks, mina vastutan töökindluse, hoolduse ja juurutamise eest. Minu ĂŒlesanne oli ajakohastada rakendust ĂŒhel serveril. Rakendus on kirjutatud Django raamistikus; ajakohastamise kĂ€igus viiakse lĂ€bi migreerimised (andmebaasi struktuuri muutmine), ja enne seda protsessi teeme tĂ€ieliku andmebaasi varukoopia standardprogrammi pg_dump abil igaks juhuks.
Varukoopia tegemise ajal tekkis ettenĂ€gematu viga (Postgresi versioon â 9.5):
pg_dump: Tabeli "ws_log_smevlog" sisu vÀljund ebaÔnnestus: PQgetResult() ebaÔnnestus.
pg_dump: Serveri veateade: VIGA: vigane leht plokis 4123007 suheline baas/16490/21396989
pg_dump: Komandiks oli: COPY public.ws_log_smevlog [...]
pg_dump: [paralleelne arendamine] töötaja protsess lĂ”petas ootamatult. Viga "vigane leht plokis" kĂ”neleb failisĂŒsteemi taseme probleemidest, mis ei ole hea. Erinevates foorumites on soovitatud teha TĂIELIK VACUUM valikuga zero_damaged_pages selle probleemi lahendamiseks. Noh, proovime ...
Taastumise ettevalmistamine
TĂHELEPANU! Veenduge, et teete enne andmebaasi taastamise katset kindlasti Postgresi varukoopia. Kui teil on virtuaalmasin, peatage andmebaas ja tehke hetkekuvand. Kui hetkekuvandi tegemine pole vĂ”imalik, peatage andmebaas ja kopeerige Postgresi katalooge (sealhulgas wal-failid) usaldusvÀÀrsesse kohta. Peamine asi on mitte halvemaks teha. Loe .
Kuna minu andmebaas töötas ĂŒldiselt, piirdusin tavapĂ€rase andmebaasi dumpimisega, kuid jĂ€tsin vĂ€lja tabeli, kus olid kahjustatud andmed (valik -T, âexclude-table=TABLE pg_dump-is).
Server oli fĂŒĂŒsiline, hetkekuvandi tegemine oli vĂ”imatu. Varukoopia on tehtud, liigume edasi.
FailisĂŒsteemi kontrollimine
Enne andmebaasi taastamise katset tuleb veenduda, et meie failisĂŒsteem on korras. Vigu tuleb parandada, sest vastasel juhul vĂ”ib asjad veelgi hullemaks minna.
Minu puhul oli failisĂŒsteem andmebaasiga liidetud «/srv» ja tĂŒĂŒp oli ext4.
Peatame andmebaasi: systemctl stop postgresql@9.5-main.service ja kontrollime, et failisĂŒsteemi ei kasutata ja et selle saab lahti monteerida kĂ€suga lsof:
lsof +D /srv
Pidin veel peatama redis andmebaasi, kuna see kasutas samuti «/srv». Edasi monteerisin lahti /srv (umount).
FailisĂŒsteemi kontroll tehti utiliidiga e2fsck lippudega -f (Sundkontroll isegi kui failisĂŒsteem on mĂ€rgitud puhtaks):

Edasi utiliidiga dumpe2fs (sudo dumpe2fs /dev/mapper/gu2âsys-srv | grep checked) saab veenduda, et kontroll tĂ”epoolest viidi lĂ€bi:

e2fsck ĂŒtleb, et ext4 failisĂŒsteemi tasemel probleeme ei leitud, mis tĂ€hendab, et saab jĂ€tkata katseid andmebaasi taastada, tĂ€psemalt naasta vacuum full (loomulikult tuleb failisĂŒsteem tagasi monteerida ja andmebaasi kĂ€ivitada).
Kui teil on fĂŒĂŒsiline server, siis kontrollige kindlasti ketaste seisukorda (kasutades smartctl -a /dev/XXX) vĂ”i RAID-kontroller, et veenduda, et probleem ei ole riistvaras. Minu puhul osutus RAID ânĂ€htavaksâ, seega palusin kohalikul administraatoril kontrollida RAID-i seisukorda (server oli minust mitmekĂŒmne kilomeetri kaugusel). Ta ĂŒtles, et vigu pole, mis tĂ€hendab, et saame kindlasti alustada taastamist.
Katse 1: zero_damaged_pages
Ăhendame andmebaasiga psql kaudu, kasutades superkasutaja Ă”igusi. Meile on vajalik just superkasutaja, kuna valikut zero_damaged_pages saab muuta vaid tema. Minu puhul on see postgres:
psql -h 127.0.0.1 -U postgres -s [database_name]
Valik zero_damaged_pages on vajalik lugemisvigade ignoreerimiseks (PostgresPro veebisaidilt):
Kui leitud on kahjustatud lehepealkiri, teatab Postgres Pro tavaliselt veast ja lĂ”petab praeguse tehingu. Kui zero_damaged_pages valik on lubatud, vĂ€ljastab sĂŒsteem selle asemel hoiatuse, nullib kahjustatud lehe mĂ€lu ja jĂ€tkab töötlemist. See kĂ€itumine rikub andmeid, nimelt kĂ”iki ridu kahjustatud lehel.
LĂŒlitame valiku sisse ja proovime tabeli ĂŒle tĂ€ielikult vakuumeerida:
VACUUM FULL VERBOSE 
Kahjuks, ebaÔnnestumine.
Kohtasime sarnast viga:
INFO: vakuumine "public.ws_log_smevlog"
HOIATUS: vigane leht plokis 4123007 seoses suhetega base/16400/21396989; nullid leht
VIGA: ootamatu tĂŒkil number 573 (oodatud 565) toster vÀÀrtuse 21648541 jaoks pg_toast_106070â mehhanism "pika andmete" salvestamiseks Postgres'is, kui need ei mahdu ĂŒhele lehte (vaikimisi 8KB).
Katse 2: reindekseerimine
Esimene soovitus Google'ist ei aidanud. PĂ€rast mitut minutit otsimist leidsin teise soovituse â teha reindekseerimine kahjustatud tabelist. Seda soovitust olin nĂ€inud paljudes kohtades, kuid see ei inspireerinud usaldust. Teeme reindekseerimise:
reindekseerimine tabelit ws_log_smevlog 
reindekseerimine lÔppes probleemideta.
Kuid see ei aidanud, VACUUM FULL kuk knowledge ebaĂ”nnestus sarnase veaga. Kuna olin harjunud lĂ€bikukkumistega, otsisin edasi Internetist nĂ”u ja sattusin ĂŒsna huvitavale .
Katse 3: SELECT, LIMIT, OFFSET
Eelpooltoodud artiklis soovitati vaadata tabelit rida-realt ja eemaldada probleemsed andmed. Esiteks oli vaja vaadata lÀbi kÔik read:
for ((i=0; i/dev/null || echo $i; doneMinu puhul sisaldas tabel 1 628 991 rida! Tegelikult oleks pidanud hoolitsema , kuid see on eraldi arutlemise teema. Oli laupÀev, kÀivitusin selle kÀsu tmuxis ja lÀksin magama:
for ((i=0; i/dev/null || echo $i; doneHommikuks otsustasin vaadata, kuidas asjalood olid. Olin ĂŒllatunud, et 20 tunni jooksul oli skaneeritud vaid 2% andmetest! Ma ei tahtnud 50 pĂ€eva oodata. JĂ€lle tĂ€ielik ebaĂ”nnestumine.
Aga ma ei andnud alla. Mind hakkas huvitama, miks skaneerimine nii kaua aega vÔttis. Dokumentatsioonist (taas postgrespro-lt) Ôppisin, et:
OFFSET nÀitab, kui palju ridu tuleb vahele jÀtta, enne kui hakatakse ridu tagasi andma.
Kui nii OFFSET kui LIMIT on mÀÀratud, siis kĂ”igepealt jĂ€tab sĂŒsteem vahele OFFSET read ja alustab seejĂ€rel ridade arvestamist LIMIT-i jaoks.LIMIT-i rakendamisel on oluline kasutada ka ORDER BY-lause, et tulemuste read esitataks kindlas jĂ€rjekorras. Vastasel juhul tagastatakse ettearvamatud read.
On ilmne, et ĂŒlaltoodud kĂ€sk oli vale: kĂ”igepealt puudus order by, seetĂ”ttu vĂ”is tulemus olla vale. Teiseks pidi Postgres kĂ”igepealt skaneerima ja vahele jĂ€tma OFFSET-read ning kui see suureneb OFFSET tĂ”husus oleks veelgi madalam.
Katse 4: tÔmmake dump tekstivormingus
Siis tuli mulle pĂ€he nĂ€iliselt geniaalne mĂ”te: vĂ”tta dump tekstivormingus ja analĂŒĂŒsida viimast salvestatud rida.
Aga kÔigepealt tutvume tabeli struktuuriga ws_log_smevlog:

Meie korral on meil veerg «id», mis sisaldas unikaalset identifikaatorit (loendur) rida. Plaan oli jÀrgmine:
- Alustame dumpi tegemist tekstivormingus (sql-kÀskude kujul)
- MÔnel hetkel katkestataks dumpi vÔtmine vea tÔttu, kuid tekstifail salvestataks silti siiski kettale.
- Vaata tekstifaili lÔppu, niinimetatud leidme identifikaatori (id) viimase rea, mis Ônnestus.
Alustasin dumpi vÔtmist tekstivormingus:
pg_dump -U my_user -d my_database -F p -t ws_log_smevlog -f ./my_dump.dumpDumpi tegemine, nagu oodatud, katkestati sama vea tÔttu:
pg_dump: Serveri veateade: ERROR: invalid page in block 4123007 of relation base/16490/21396989 SeejĂ€rel lĂ€bi tail vaatasin dumpi lĂ”ppu (tail -5 ./my_dump.dump) leidsin, et dump katkestati id real 186 525. âSeega on probleem id 186 526 real, see on rikutud, see tuleb eemaldada!â â mĂ”tlesin ma. Kuid tehes pĂ€ring andmebaasi:
«valige * from ws_log_smevlog where id=186529» selgus, et selle reaga on kĂ”ik korras⊠Ridadel, mille indeksid on 186 530 â 186 540, töötasid ka probleemideta. JĂ€lle ĂŒks "geniaalne idee" ebaĂ”nnestus. Hiljem sain aru, miks see juhtus: tabelist andmete kustutamisel vĂ”i muutmisel ei kustutata neid fĂŒĂŒsiliselt, vaid need mĂ€rgitakse kui "surmatud ridadeks"; seejĂ€rel tuleb autovacuum ja mĂ€rgib need read kustutatuks ning lubab neid ridasid uuesti kasutada. Arvestades, et kui tabelis andmed muutuvad ja autovacuum on sisse lĂŒlitatud, siis ei hoita neid jĂ€rjestikustes asukohtades.
Katse 5: SELECT, FROM, WHERE id=
EbaĂ”nnestumised teevad meid tugevamaks. Kunagi ei tohi alla anda, tuleb minna lĂ”puni ja uskuda endasse ja oma vĂ”imetesse. Seega otsustasin proovida veel ĂŒht varianti: lihtsalt vaadata kĂ”ik andmed andmebaasis ĂŒkshaaval. Teades minu tabeli struktuuri (vt ĂŒlal), on meil id vĂ€li, mis on ainulaadne (esmane vĂ”ti). Tabelis on meil 1 628 991 rida ja id need on jĂ€rjestikuses jĂ€rjekorras, mis tĂ€hendab, et saame need lihtsalt ĂŒkshaaval lĂ€bi kĂ€ia:
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; doneKui keegi ei saa aru, siis kÀib kÀsklus nii: ta vaatab rida-realt tabelit ja saadab stdout-i /dev/null, kuid kui SELECT kÀsk kukub lÀbi, siis kuvatakse veateksti (stderr saadetakse konsooli) ja vÀlja prinditakse rida, mis sisaldab viga (tÀnu ||, mis tÀhendab, et select kÀsku esines probleem, tagastuskood ei ole 0).
Mul oli Ônne, mul oli vÀlja töötatud indeksid vÀlja suhtes id:

See tÀhendab, et vajaliku id-ga rea leidmine ei tohiks vÔtta palju aega. Teoorias peaks see toimima. Noh, kÀivitage kÀsk tmux ja lÀheme magama.
Hommikuks avastasin, et oli vaadatud umbes 90 000 kirjet, mis teeb natuke ĂŒle 5%. SuurepĂ€rane tulemus, kui vĂ”rrelda varasema meetodiga (2%)! Kuid oodata 20 pĂ€eva ei soovinudâŠ
Katse 6: SELECT, FROM, WHERE id >= and id <
Kliendi andmebaasi jaoks oli eraldatud suurepÀrane server: kahetuumaline Intel Xeon E5-2697 v2, meie asukohas oli koguni 48 lÔime! Serveri koormus oli keskmine, me saime probleemideta vÔtta umbes 20 lÔime. Ka RAM-i oli piisavalt: koguni 384 gigabaiti!
SeetÔttu tuli kÀsklus paralléliseerida:
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; doneSiin oleks vÔinud kirjutada kauni ja elegantse skripti, kuid ma valisin kÔige kiiremalt samaaegselt töötamise viisi: jagasin vahemiku 0-1628991 kÀsitsi 100 000 kirje kaupa intervallideks ja kÀivitasin eraldi 16 kÀsku:
for ((i=N; i/dev/null || echo $i; doneAga see ei ole kĂ”ik. Tegelikult vĂ”tab andmebaasi ĂŒhendamine samuti aega ja sĂŒsteemi resursse. Ăhendamine 1 628 991-ga ei olnud just vĂ€ga mĂ”istlik, nĂ”ustute? SeetĂ”ttu tĂ”mmake ĂŒhe ĂŒhenduse pĂ”hjal 1000 rida, mitte ĂŒhte. LĂ”pptulemusena muutus kĂ€sk selliseks:
for ((i=N; i=$i and id/dev/null || echo $i; doneAvame tmux sessioonis 16 akent ja kÀivitame kÀsud:
1) for ((i=0; i=$i and id/dev/null || echo $i; done 2) for ((i=100000; i=$i and id/dev/null || echo $i; done ⊠15) for ((i=1400000; i=$i and id/dev/null || echo $i; done 16) for ((i=1500000; i=$i and id/dev/null || echo $i; done
PÀeva pÀrast sain esimesed tulemused! Nimelt (vÀÀrtused XXX ja ZZZ ei ole enam salvestatud):
ERROR: puuduv tĂŒkk number 0 toast vÀÀrtuse 37837571 puhul pg_toast_106070
829000
ERROR: puuduv tĂŒkk number 0 toast vÀÀrtuse XXX puhul pg_toast_106070
829000
ERROR: puuduv tĂŒkk number 0 toast vÀÀrtuse ZZZ puhul pg_toast_106070
146000See tÀhendab, et meil on kolm rida, mis sisaldavad viga. Esimese ja teise probleemi kirje id asus vahemikus 829 000 ja 830 000, kolmanda id aga vahemikus 146 000 ja 147 000. Edasi pidime lihtsalt leidma probleemsete kirje id tÀpsed vÀÀrtused. Selleks vaatame meie probleemsete kirje vahemikku samm-sammult ja tuvastame id:
for ((i=829000; 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/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
Me leidsime probleemsed read. Logime 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 1Minu ĂŒllatuseks, kanded kanti probleemideta vĂ€lja isegi ilma valikuta zero_damaged_pages.
Siis ĂŒhendasin end andmebaasi, tegin VACUUM FULL (arvan, et tegin seda ei olnud vaja), ja lĂ”puks Ă”nnestus mul luua varukoopia pg_dump. Varukoopia loodi ilma vigadeta! Probleem Ă”nnestus lahendada sedasi. RÔÔmu ei olnud piire pĂ€rast nii palju ebaĂ”nnestumisi, lĂ”puks leiti lahendus!
TÀnusÔnad ja kokkuvÔte
Nii et see oli minu esimene kogemus PostgreSQL andmebaasi taastamisel. Selle kogemuse mÀletan pikalt.
Ja lĂ”petuseks tahaksin tĂ€nada PostgresPro't tĂ”lgitud dokumentatsiooni eest vene keeles ja , mis aitasid vĂ€ga palju probleemi analĂŒĂŒsimisel.
Allikas: habr.com
