Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)

Mig langar að deila með þér fyrstu farsælu reynslu minni af því að endurheimta Postgres gagnagrunn í fullan virkni. Ég kynntist Postgres DBMS fyrir hálfu ári, áður hafði ég enga reynslu af gagnagrunnsstjórnun.

Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)

Ég vinn sem hálfgerður DevOps verkfræðingur í stóru upplýsingatæknifyrirtæki. Fyrirtækið okkar þróar hugbúnað fyrir mikla þjónustu og ég ber ábyrgð á frammistöðu, viðhaldi og uppsetningu. Ég fékk staðlað verkefni: að uppfæra forrit á einum netþjóni. Forritið er skrifað í Django, meðan á uppfærslunni stendur eru flutningar framkvæmdar (breytingar á uppbyggingu gagnagrunnsins), og áður en þetta ferli fer fram tökum við fullan gagnagrunnsdump í gegnum venjulegt pg_dump forritið, bara ef til öryggis.

Óvænt villa kom upp við að taka dump (Postgres útgáfa 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

Villa "ógild síða í blokk" talar um vandamál á skráarkerfisstigi, sem er mjög slæmt. Á ýmsum vettvangi var lagt til að gera FULLT VAKUUM með valmöguleika núll_skemmdar_síður að leysa þetta vandamál. Jæja, við skulum reyna...

Undirbúningur fyrir bata

VIÐVÖRUN! Vertu viss um að taka Postgres öryggisafrit áður en þú reynir að endurheimta gagnagrunninn þinn. Ef þú ert með sýndarvél skaltu stöðva gagnagrunninn og taka skyndimynd. Ef það er ekki hægt að taka skyndimynd skaltu stöðva gagnagrunninn og afrita innihald Postgres möppunnar (þar á meðal wal skrár) á öruggan stað. Aðalatriðið í viðskiptum okkar er að gera ekki illt verra. Lestu это.

Þar sem gagnagrunnurinn virkaði almennt fyrir mig, takmarkaði ég mig við venjulegan gagnagrunnsdump, en útilokaði töfluna með skemmdum gögnum (valkostur -T, --exclude-table=TAFLA í pg_dump).

Miðlarinn var líkamlegur, það var ómögulegt að taka skyndimynd. Afritið hefur verið fjarlægt, við skulum halda áfram.

Athugun á skráarkerfi

Áður en reynt er að endurheimta gagnagrunninn þurfum við að ganga úr skugga um að allt sé í lagi með skráarkerfið sjálft. Og ef um mistök er að ræða, leiðréttu þær, því annars geturðu bara gert illt verra.

Í mínu tilviki var skráarkerfið með gagnagrunninum fest í "/srv" og tegundin var ext4.

Stöðvun gagnagrunnsins: systemctl hætta [netvarið] og athugaðu hvort skráarkerfið sé ekki í notkun af neinum og að hægt sé að aftengja það með skipuninni LSOF:
lsof +D /srv

Ég þurfti líka að stöðva Redis gagnagrunninn, þar sem hann var líka að nota "/srv". Næst losaði ég mig / SRV (umfjöllun).

Skráarkerfið var athugað með því að nota tólið e2fsck með rofanum -f (Þvingaðu eftirlit jafnvel þótt skráarkerfið sé merkt hreint):

Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)

Næst skaltu nota tólið dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep athugað) þú getur staðfest að athugunin hafi raunverulega verið framkvæmd:

Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)

e2fsck segir að engin vandamál hafi fundist á ext4 skráarkerfisstigi, sem þýðir að þú getur haldið áfram að reyna að endurheimta gagnagrunninn, eða öllu heldur aftur til tómarúm fullt (auðvitað þarftu að tengja skráarkerfið aftur og ræsa gagnagrunninn).

Ef þú ert með líkamlegan netþjón, vertu viss um að athuga stöðu diskanna (via smartctl -a /dev/XXX) eða RAID stjórnandi til að ganga úr skugga um að vandamálið sé ekki á vélbúnaðarstigi. Í mínu tilviki reyndist RAID vera „vélbúnaður“ svo ég bað staðbundinn stjórnanda að athuga stöðu RAID (þjónninn var nokkur hundruð kílómetra frá mér). Hann sagði að það væru engar villur, sem þýðir að við getum örugglega hafið endurreisn.

Tilraun 1: zero_damaged_pages

Við tengjumst gagnagrunninum í gegnum psql með reikningi sem hefur ofurnotendaréttindi. Okkur vantar ofurnotanda því... valmöguleika núll_skemmdar_síður aðeins hann getur breyst. Í mínu tilfelli er það postgres:

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

Valkostur núll_skemmdar_síður þarf til að hunsa lesvillur (af vef postgrespro):

Þegar PostgreSQL finnur skemmdan síðuhaus tilkynnir hann venjulega um villu og hættir við núverandi viðskipti. Ef zero_damaged_pages er virkt gefur kerfið í staðinn út viðvörun, núllstillir skemmda síðu í minni og heldur áfram vinnslu. Þessi hegðun eyðir gögnum, nefnilega öllum línum á skemmdu síðunni.

Við virkum valkostinn og reynum að gera fullt tómarúm á borðunum:

VACUUM FULL VERBOSE

Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)
Því miður, óheppni.

Við fundum svipaða villu:

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_ristað brauð – vélbúnaður til að geyma „löng gögn“ í Poetgres ef þau passa ekki á eina síðu (8kb sjálfgefið).

Tilraun 2: endurskrá

Fyrstu ráðin frá Google hjálpuðu ekki. Eftir nokkurra mínútna leit fann ég seinni ábendinguna - til að gera endurtryggja skemmd borð. Ég sá þetta ráð á mörgum stöðum, en það vakti ekki traust. Endurskráum:

reindex table ws_log_smevlog

Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)

endurtryggja lokið án vandræða.

Þetta hjálpaði þó ekki, TÚMSÚG FULLT hrundi með svipaðri villu. Þar sem ég er vanur bilunum fór ég að leita lengra eftir ráðleggingum á netinu og rakst á frekar áhugavert grein.

Tilraun 3: SELECT, LIMIT, OFFSET

Greinin hér að ofan lagði til að skoða töfluna röð fyrir röð og fjarlægja erfið gögn. Fyrst þurftum við að skoða allar línurnar:

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

Í mínu tilviki innihélt taflan +1 628 991 XNUMX línur! Það var nauðsynlegt að hugsa vel um gagnaskiptingu, en þetta er efni til sérstakrar umræðu. Það var laugardagur, ég rak þessa skipun í tmux og fór að sofa:

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

Um morguninn ákvað ég að athuga hvernig gengi. Mér til undrunar komst ég að því að eftir 20 klukkustundir höfðu aðeins 2% af gögnunum verið skönnuð! Ég vildi ekki bíða í 50 daga. Enn ein algjör bilun.

En ég gafst ekki upp. Ég velti því fyrir mér hvers vegna skönnunin tók svona langan tíma. Af skjölunum (aftur á postgrespro) komst ég að:

OFFSET tilgreinir að sleppa tilgreindum fjölda raða áður en byrjað er að gefa út línur.
Ef bæði OFFSET og LIMIT eru tilgreind, sleppir kerfið fyrst OFFSET línunum og byrjar síðan að telja línurnar fyrir LIMIT þvingunina.

Þegar LIMIT er notað er mikilvægt að nota einnig ORDER BY-ákvæði þannig að niðurstöðuröðunum sé skilað í ákveðinni röð. Að öðrum kosti verður ófyrirsjáanlegum undirmengi raða skilað.

Augljóslega var ofangreind skipun röng: í fyrsta lagi var engin Raða eftir, niðurstaðan gæti verið röng. Í öðru lagi þurfti Postgres fyrst að skanna og sleppa OFFSET línum og með aukningu OFFSET framleiðni myndi minnka enn frekar.

Tilraun 4: taka sorphaugur í textaformi

Þá datt mér í hug að því er virðist snilldarhugmynd: Taktu dump í textaformi og greindu síðustu upptöku línu.

En fyrst skulum við líta á uppbyggingu töflunnar. ws_log_smevlog:

Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)

Í okkar tilviki höfum við dálk „Auðkenni“, sem innihélt einkvæmt auðkenni (teljara) línunnar. Planið var svona:

  1. Við byrjum að taka dump í textaformi (í formi sql skipana)
  2. Á ákveðnum tímapunkti myndi dumpið truflast vegna villu, en textaskráin yrði samt vistuð á diski
  3. Við skoðum lok textaskrárinnar, þar með finnum við auðkenni (id) síðustu línu sem var fjarlægt með góðum árangri

Ég byrjaði að taka dump í textaformi:

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

Sorpið, eins og búist var við, var rofið með sömu villu:

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

Lengra í gegn hali Ég horfði á enda sorphaugsins (hali -5 ./my_dump.dump) uppgötvaði að sorpið var truflað á línunni með id 186 525. „Þannig að vandamálið er í samræmi við auðkenni 186 526, það er bilað og þarf að eyða því! - Ég hélt. En að gera fyrirspurn í gagnagrunninn:
«veldu * úr ws_log_smevlog þar sem id=186529„Það kom í ljós að allt var í lagi með þessa línu... Raðir með vísitölur 186 - 530 virkuðu líka án vandræða. Önnur „snjöll hugmynd“ mistókst. Seinna skildi ég hvers vegna þetta gerðist: þegar gögnum er eytt og breytt úr töflu er þeim ekki eytt líkamlega, heldur merkt sem „dauðir tuples“, þá kemur sjálfvirkt tómarúm og merkir þessar línur sem eytt og leyfir að þessar línur séu endurnýttar. Til að skilja, ef gögnin í töflunni breytast og sjálfvirkt tómarúm er virkt, þá eru þau ekki geymd í röð.

Tilraun 5: SELECT, FROM, WHERE id=

Mistök gera okkur sterkari. Þú ættir aldrei að gefast upp, þú þarft að fara til enda og trúa á sjálfan þig og getu þína. Svo ég ákvað að prófa annan valmöguleika: flettu bara í gegnum allar skrárnar í gagnagrunninum eitt af öðru. Þegar við þekkjum uppbyggingu töflunnar minnar (sjá hér að ofan), höfum við auðkennisreit sem er einstakt (aðallykill). Við erum með 1 línur í töflunni og id eru í lagi, sem þýðir að við getum bara farið í gegnum þau eitt í einu:

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

Ef einhver skilur það ekki virkar skipunin sem hér segir: hún skannar töfluna röð fyrir röð og sendir stdout til / dev / null, en ef SELECT skipunin mistekst, þá er villutextinn prentaður (stderr er sendur á stjórnborðið) og lína sem inniheldur villuna er prentuð (þökk sé ||, sem þýðir að valið átti í vandræðum (skilakóði skipunarinnar) er ekki 0)).

Ég var heppinn, ég lét búa til vísitölur á vellinum id:

Fyrsta reynsla mín við að endurheimta Postgres gagnagrunn eftir bilun (ógild síða í blokk 4123007 af relatton base/16490)

Þetta þýðir að það ætti ekki að taka mikinn tíma að finna línu með viðkomandi auðkenni. Í orði ætti það að virka. Jæja, við skulum keyra skipunina inn tmux og við skulum fara að sofa.

Um morguninn komst ég að því að um 90 færslur höfðu verið skoðaðar, sem er rúmlega 000%. Frábær árangur miðað við fyrri aðferð (5%)! En ég vildi ekki bíða í 2 daga...

Tilraun 6: SELECT, FROM, WHERE id >= og id <

Viðskiptavinurinn var með frábæran netþjón sem var tileinkaður gagnagrunninum: tvískiptur örgjörvi Intel Xeon E5-2697 v2, það voru allt að 48 þræðir á staðnum okkar! Álagið á netþjóninn var í meðallagi; við gátum halað niður um 20 þráðum án vandræða. Það var líka nóg vinnsluminni: allt að 384 gígabæt!

Þess vegna þurfti skipunina að vera samhliða:

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

Hér var hægt að skrifa fallegt og glæsilegt handrit, en ég valdi hröðustu samhliða aðferðina: skiptu sviðinu 0-1628991 handvirkt í 100 færslur og keyrðu sérstaklega 000 skipanir af forminu:

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

En það er ekki allt. Fræðilega séð tekur tenging við gagnagrunn líka nokkurn tíma og kerfisauðlindir. Að tengja 1 var ekki mjög snjallt, þú munt sammála. Þess vegna skulum við sækja 628 línur í stað einnar tengingar. Fyrir vikið breyttist liðið í þetta:

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

Opnaðu 16 glugga í tmux lotu og keyrðu skipanirnar:

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

Degi síðar fékk ég fyrstu niðurstöðurnar! Nefnilega (gildin XXX og ZZZ eru ekki lengur varðveitt):

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

Þetta þýðir að þrjár línur innihalda villu. Auðkenni fyrstu og annarrar vandamálaskráningar voru á milli 829 og 000, auðkenni þeirrar þriðju voru á milli 830 og 000. Næst þurftum við einfaldlega að finna nákvæmlega auðkennisgildi vandamálaskráninganna. Til að gera þetta skoðum við úrvalið okkar með vandræðalegum færslum með skrefinu 146 og auðkennum auðkennið:

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

Hamingjusamur endir

Við fundum erfiðu línurnar. Við förum inn í gagnagrunninn í gegnum psql og reynum að eyða þeim:

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

Mér til undrunar var færslunum eytt án nokkurra vandræða, jafnvel án þess að hafa valkost núll_skemmdar_síður.

Svo tengdist ég gagnagrunninum, gerði það TÚMSÚG FULLT (Ég held að það hafi ekki verið nauðsynlegt að gera þetta), og að lokum tókst mér að fjarlægja öryggisafritið með því að nota pg_dump. Sorpið var tekið án nokkurra villna! Vandamálið var leyst á svo heimskulegan hátt. Gleðin átti sér engin takmörk, eftir svo mörg mistök tókst okkur að finna lausn!

Þakkir og ályktun

Svona reyndist mín fyrsta reynsla af því að endurheimta alvöru Postgres gagnagrunn. Ég mun lengi muna þessa reynslu.

Og að lokum vil ég þakka PostgresPro fyrir að þýða skjölin á rússnesku og fyrir algjörlega ókeypis námskeið á netinu, sem hjálpaði mikið við greiningu á vandamálinu.

Heimild: www.habr.com

Bæta við athugasemd