Tahaksin jagada teiega oma esimest edukaid kogemusi Postgresi andmebaasi tĂ€ieliku töövĂ”ime taastamisel. Olen tutvunud Postgresi andmebaasihalduriga pool aastat tagasi; enne seda ei olnud mul andmebaaside haldamise kogemust ĂŒldse.

Töötan pool-DevOps insenerina suures IT-ettevĂ”ttes. Meie ettevĂ”te tegeleb tarkvaraarendusega suure koormusega teenustele, mina olen vastutav töövĂ”ime, hoolduse ja juurutamise eest. Minu ĂŒlesandeks oli tavaline ĂŒlesanne: uuendada rakendust ĂŒhel serveril. Rakendus on kirjutatud Django's ja uuendamise kĂ€igus toimuvad migratsioonid (andmebaasi struktuuri muutmine), ning enne seda protsessi teeme, igaks juhuks, tĂ€isdumpi andmebaasist standardse pg_dump programmiga.
Dumpi tegemise kĂ€igus tekkis ettenĂ€gematu viga (Postgresi versioon â 9.5):
pg_dump: Tabeli "ws_log_smevlog" sisu vÀljavÔttmine ebaÔnnestus: PQgetResult() ebaÔnnestus.
pg_dump: Serveri veateade: VIGA: kehtetu leht plokis 4123007 suhtes andmebaasis /16490/21396989
pg_dump: KĂ€sk oli: COPY public.ws_log_smevlog [...]
pg_dunp: [paralleelne arhitĂŒĂŒr] töötaja protsess lĂ”petas ootamatult. Viga "kehtetu leht plokis" nĂ€itab failisĂŒsteemi tasandi probleeme, mis ei ole sugugi hea. Erinevates foorumites soovitati teha FULL VACUUM valikuga zero_damaged_pages selle probleemi lahendamiseks. Mis siis, proovimeâŠ
Taastumise ettevalmistamine
TĂHELEPANU! Veenduge, et teete enne andmebaasi taastamise katset kindlasti varukoopia Postgresest. Kui teil on virtuaalne masin, peatage andmebaas ja tehke snapshot. Kui snapshot'i tegemine pole vĂ”imalik, peatage andmebaas ja kopeerige Postgrese katalooge (sealhulgas wal-failid) usaldusvÀÀrsesse kohta. Meie töös on kĂ”ige olulisem mitte halvemaks teha. Loe .
Kuna overall andmebaas töötas, piirdusin tavapÀrase andmebaasi dumpimisega, kuid jÀtsin vÀlja tabeli, kus olid rikutud andmed (valik -T, --exclude-table=TABLE pg_dump-i jaoks).
Server oli fĂŒĂŒsiline, snapshot'i tegemine polnud vĂ”imalik. Varukoopia on tehtud, liikuge edasi.
FailisĂŒsteemi kontrollimine
Enne andmebaasi taastamise katset on oluline veenduda, et meie failisĂŒsteem on kĂ”ik korras. Ja juhul, kui esineb vigu, tuleb need parandada, kuna vastasel juhul saab teha ainult hullemaks.
Minu puhul oli andmebaasiga failisĂŒsteem manustatud "/srv" ja tĂŒĂŒp oli ext4.
Peatame andmebaasi: systemctl stop postgresql@9.5-main.service ja kontrollime, et failisĂŒsteemi ei kasutata ning seda saab kĂŒlmutada kĂ€su abil lsof:
lsof +D /srv
Pidin peatama ka Redis andmebaasi, kuna see kasutab samuti "/srv". Edasi kĂŒlmutasin /srv (umount).
FailisĂŒsteemi kontroll tehti tööriistaga e2fsck keermega -f (Sundkontroll, isegi kui failisĂŒsteem on puhtaks mĂ€rgitud):

Edasi kasutades tööriista dumpe2fs (sudo dumpe2fs /dev/mapper/gu2âsys-srv | grep checked) saame veenduda, et kontroll on tĂ”epoolest teostatud:

e2fsck ĂŒtleb, et probleeme ext4 failisĂŒsteemi tasemel ei leitud, mis tĂ€hendab, et saame jĂ€tkata andmebaasi taastamise katseid, viidates tĂ€psemalt vacuum full (loomulikult on vajalik failisĂŒsteemi tagasi mountida ja andmebaasi kĂ€ivitada).
Kui sul on fĂŒĂŒsiline server, siis kontrolli kindlasti ketaste seisundit (kasutades smartctl -a /dev/XXX) vĂ”i RAID-kontrollerit, et veenduda, et probleem ei ole riistvaraline. Minu puhul oli RAID "raud", seetĂ”ttu palusin kohalikul adminil kontrollida RAIDi seisundit (server oli minust mitusada kilomeetrit eemal). Ta ĂŒtles, et vigu pole, seega saame kindlasti taastamisega alustada.
Katse 1: zero_damaged_pages
Ăhendame andmebaasiga psql kaudu kasutajaga, kellel on superkasutaja Ă”igused. Meil on tĂ”esti vaja superkasutajat, kuna valikut zero_damaged_pages suudab muuta ainult tema. Minu juhul on see postgres:
psql -h 127.0.0.1 -U postgres -s [database_name]
Valik zero_damaged_pages on vajalik lugemisvigade ignoreerimiseks (PostgresPro veebilehelt):
Kui on tuvastatud kahjustatud lehe pealkiri, teatab Postgres Pro tavaliselt veast ja katkestab antud tehingu. Kui parameeter zero_damaged_pages on lubatud, siis kuvab sĂŒsteem selle asemel hoiatused, nullib mĂ€lus kahjustatud lehe ja jĂ€tkab töötlemist. See kĂ€itumine hĂ€vitab andmed, nimelt kĂ”ik read kahjustatud lehes.
LĂŒlitame valiku sisse ja proovime teha tabeli tĂ€ielikku vacuumit:
VACUUM FULL VERBOSE 
Kahjuks ebaÔnnestus.
Seisime silmitsi sarnase veaga:
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â mehhanism "piklike andmete" salvestamiseks Poetgress, kui need ei mahutu ĂŒhte lehte (vaikimisi 8 kb).
Katse 2: reindex
Google'i esimene soovitus ei aidanud. PĂ€rast mĂ”ne minuti otsimist leidsin teise soovituse â teha reindex defektse tabeli. Seda nĂ”uannet olen kohanud paljudes kohtades, kuid see ei andnud usaldusvÀÀrsust. Teeme reindex:
reindex table ws_log_smevlog 
reindex lÔppes probleemideta.
Kuid see ei aidanud, VACUUM FULL krahhis lĂ”ppes analoogse veaga. Kuna olin harjunud ebaĂ”nnestumistega, hakkasin internetis edasisi nĂ”uandeid otsima ja sattusin ĂŒsna huvitavale .
Katse 3: SELECT, LIMIT, OFFSET
Ălaltoodud artiklis soovitati vaadata tabelit rida-realt ja eemaldada probleemsed andmed. Alustuseks pidi kĂ”iki ridu vaatama:
for ((i=0; i/dev/null || echo $i; doneMinu puhul sisaldas tabel 1 628 991 ridu! Headuses oleks olnud vajalik hoolitseda , kuid see on eraldi arutelu teema. Oli laupÀev, kÀivitasin selle kÀsu tmuxis ja lÀksin magama:
for ((i=0; i/dev/null || echo $i; doneHommikuks otsustasin kontrollida, kuidas asjad edenevad. Minu ĂŒllatuseks avastasin, et 20 tunni jooksul oli skaneeritud vaid 2% andmetest! Ma ei tahtnud oodata 50 pĂ€eva. JĂ€rjekordne tĂ€ielik ebaĂ”nnestumine.
Aga ma ei lasknud alla. Mind hakkas huvitama, miks skaneerimine kestis nii kaua. Dokumentatsioonist (taas postgrespro) sain teada:
OFFSET nÀitab, et tuleb vahele jÀtta mÀÀratud arv ridu, enne kui hakatakse ridu vÀljundisse andma.
Kui on mÀÀratud nii OFFSET kui ka LIMIT, siis sĂŒsteem kĂ”igepealt jĂ€tab vahele OFFSET read ja seejĂ€rel hakkab loendama ridu LIMIT piirmÀÀra jaoks.LIMITi rakendamisel on oluline kasutada ka ORDER BY klauslit, et tulemuste read vĂ€ljastataks kindlas jĂ€rjekorras. Vastasel juhul tagastatakse ettearvamatud alamkogud ridu.
Ilmselgelt oli ĂŒlaltoodud kĂ€sk vale: kĂ”igepealt ei olnud order by, tulemus vĂ”is olla vale. Teiseks pidi Postgres esmalt skaneerima ja vahele jĂ€tma OFFSET-read, ning koos kasvu CHECKSUM_AGG vĂ€heneks tootlikkus veelgi.
Katse 4: vÔtta dump tekstivormingus
SeejĂ€rel tuli mulle pĂ€he, nĂ€iliselt geniaalne idee: vĂ”tta dump tekstivormingus ja analĂŒĂŒsida viimast salvestatud rida.
Aga kÔigepealt tutvume tabeli struktuuriga ws_log_smevlog:

Meie puhul on meil veerg âidâ, mis sisaldas ridu unikaalset identifikaatorit (loenduri). Plaan oli jĂ€rgmine:
- Alustame dumpi tegemist tekstivormingus (sql-kÀskudena)
- Teatud hetkel katkeb andme salvestamine vea tÔttu, kuid tekstifail oleks ikkagi kettale salvestatud.
- Vaadates tekstifaili lÔppu, leiame viimase rida id, mis salvestati edukalt.
Alustasin andmete salvestamist tekstivormingus:
pg_dump -U my_user -d my_database -F p -t ws_log_smevlog -f ./my_dump.dumpAndmete salvestamine katkeb, nagu oodata oli, sama vea tÔttu:
pg_dump: Serveri veateates: ERROR: invalid page in block 4123007 of relation base/16490/21396989 SeejÀrel vaatasin tail ma dumpi lÔppu (tail -5 ./my_dump.dump) ja avastasin, et dump katkeb real, mille id on 186 525. "TÀhendab, probleem on real, mille id on 186 526, see on vigane, see tuleb kustutada!" - mÔtlesin ma. Kuid kui tegin pÀringu andmebaasi:
«select * from ws_log_smevlog where id=186529» selgus, et selle reaga on kĂ”ik korras... Rida, mille indeksid on 186 530 â 186 540 töötasid samuti probleemideta. JĂ€rjekordne "geniaalne idee" ebaĂ”nnestus. Hiljem sain aru, miks see juhtus: andmete kustutamisel vĂ”i muutmisel tabelist ei kustutata neid fĂŒĂŒsiliselt, vaid need mĂ€rgitakse âsurnud tupplehtedeks", seejĂ€rel tuleb autovacuum ja mĂ€rgib need read kustutatutena ja lubab neid kasutada uuesti. Ăheks selgituseks, kui andmed tabelis muutuvad ja autovacuum on sisse lĂŒlitatud, siis need ei sĂ€ili jĂ€rjestikku.
Katse 5: SELECT, FROM, WHERE id=
EbaĂ”nnestumised muudavad meid tugevamaks. Ei tasu kunagi alla anda, tuleb minna lĂ”puni ja uskuda endasse ja oma vĂ”imalustesse. SeetĂ”ttu otsustasin proovida veel ĂŒhte varianti: vaadata lihtsalt kĂ”ik kirjed andmebaasis ĂŒkshaaval. Teades oma tabeli struktuuri (vt ĂŒlal), on meil id, mis on ainulaadne (peamine vĂ”ti). Tabelis on 1 628 991 rida ja id need on jĂ€rjestatud, mis tĂ€hendab, et saame neid lihtsalt ĂŒkshaaval lĂ€bi vaadata:
for ((i=1; i/dev/null || echo $i; doneKui keegi ei mĂ”ista, töötab kĂ€sk jĂ€rgmiselt: vaatab tabelit realt real ja saadab stdout-i /dev/null, kuid kui SELECT-kĂ€sk ebaĂ”nnestub, kuvatakse veateksti (stderr saadetakse konsooli) ja vĂ€ljastatakse ĂŒksus, mis sisaldab viga (tĂ€nu ||, mis tĂ€hendab, et SELECT-l tekkisid probleemid (kĂ€skluse vĂ€ljumiskood ei ole 0)).
Mul oli vedanud, mul oli vÀlja töötatud indekseid alusel id:

See tÀhendab, et Ôige id-ga rea leidmine ei tohiks vÔtta palju aega. Teoorias peaks see toimima. Noh, kÀivitan kÀsu. tmux ja lÀhen magama.
Hommikuks avastasin, et olin vaadanud umbes 90 000 kirjet, mis moodustab veidi ĂŒle 5%. SuurepĂ€rane tulemus, kui vĂ”rrelda eelneva meetodiga (2%)! Kuid 20 pĂ€eva ootamine ei olnud just see, mida ma tahtsin...
Katse 6: SELECT, FROM, WHERE id >= ja id <
Kliendi andmebaasiks oli saadaval suurepÀrane server: kaheprotsessoriline Intel Xeon E5-2697 v2, meie kÀsutuses oli lausa 48 lÔime! Serveri koormus oli keskmine, saime probleemideta vÔtta umbes 20 lÔime. Ka RAM-i oli piisavalt: koguni 384 gigabaiti!
SeetÔttu tuli kÀsk anda paralleelselt:
for ((i=1; i/dev/null || echo $i; doneSiin oleks vĂ”inud kirjutada kauni ja elegantse skripti, kuid ma valisin kiireima paralleelimise viisi: jagasin vahemiku 0-1628991 kĂ€sitsi intervallideks 100 000 kirjet ja kĂ€ivitasin eraldi 16 kĂ€su jĂ€rgmist tĂŒĂŒpi:
for ((i=N; i/dev/null || echo $i; doneAga see ei ole kĂ”ik. Tegelikult vĂ”tab andmebaasi ĂŒhendamine ka mingil ajal ja sĂŒsteemi ressursse. Ăhendamine 1 628 991 puhul ei olnud just vĂ€ga mĂ”istlik, nĂ”ustuge. SeetĂ”ttu tĂ”mbleme ĂŒhe ĂŒhenduse all 1000 rida ĂŒhe asemel. Ehk kĂ€sk muutus selliseks:
for ((i=N; i=$i and id/dev/null || echo $i; doneAvame 16 akent tmux sessioonis 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 salvestunud):
ERROR: puudu olev lÔhik nummer 0 toast vÀÀrtusele 37837571 pg_toast_106070
829000
ERROR: puudu olev lÔhik nummer 0 toast vÀÀrtusele XXX pg_toast_106070
829000
ERROR: puudu olev lÔhik nummer 0 toast vÀÀrtusele ZZZ pg_toast_106070
146000See tÀhendab, et meil on kolm rida, mis sisaldavad viga. Esimese ja teise probleemse kirje id asusid vahemikus 829 000 ja 830 000, kolmanda id vahel oli vahemikus 146 000 ja 147 000. Edasi pidime lihtsalt leidma probleemsete kirjete tÀpsed id vÀÀrtused. Selleks vaatame meie probleemsete kirjetega 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Ă”pplahendus
Me leidsime probleemsed read. Siseneme andmebaasi lÀbi psql ja proovime need eemaldada:
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 kustutati kirjed probleemideta ka ilma valikuta zero_damaged_pages.
SeejĂ€rel ĂŒhendasin andmebaasiga, tegin VACUUM FULL (arvan, et see polnud vajalik), ja lĂ”puks sain edukalt varukoopia lĂ€bi pg_dump. Dump tehti ilma igasuguste vigadeta! Probleem sai lahendatud nii primitiivsel viisil. Ănne polnud piire, pĂ€rast nii paljusid ebaĂ”nnestumisi leidsin lĂ”puks lahenduse!
TÀnud ja jÀreldused
Selline oli minu esimene kogemus Postgresi andmebaasi taastamisel. Selle kogemuse mÀletan pikalt.
Ja lĂ”petuseks, tahaksin tĂ€nada ettevĂ”tet PostgresPro vene keelde tĂ”lgitud dokumentatsiooni eest ja , mis aitasid mul probleemi analĂŒĂŒsimisel vĂ€ga palju.
Allikas: habr.com
