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ð ykkur fyrstu farsælu reynslu minni af því að endurheimta Postgres gagnagrunn í fulla virkni. Ég kynntist Postgres fyrst fyrir sex mánuðum; áður en það gerðist hafði ég enga reynslu af stjórnun gagnagrunna.

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

Ég vinn sem hálf-DevOps verkfræðingur hjá stóru upplýsingatæknifyrirtæki. Fyrirtækið okkar þróar hugbúnað fyrir þjónustu með mikla álagi og ég ber ábyrgð á rekstraröryggi, viðhaldi og uppsetningu. Ég fékk staðlað verkefni: að uppfæra forrit á einum netþjóni. Forritið er skrifað í Django og flutningar (breytingar á gagnagrunnsbyggingu) eru framkvæmdir meðan á uppfærslunni stendur. Áður en þetta ferli fer fram framkvæmum við fulla gagnagrunnsdump með stöðluðu pg_dump forritinu, til öryggis.

Óvænt villa kom upp við úrvinnslu (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“ gefur til kynna vandamál á skráarkerfisstigi, sem er mjög slæmt. Ýmsar umræður hafa lagt til að gera það Fullt tómarúm með valmöguleika núll_skemmdar_síður til að leysa þetta vandamál. Jæja, poppróbeum...

Undirbúningur fyrir bata

VIÐVÖRUN! Gakktu úr skugga um að taka afrit af Postgres uppsetningunni þinni áður en þú reynir að endurheimta gagnagrunninn. Ef þú ert að nota sýndarvél skaltu stöðva gagnagrunninn og taka skyndimynd. Ef þú getur ekki tekið skyndimynd skaltu stöðva gagnagrunninn og afrita innihald Postgres möppunnar (þar á meðal .wal skrár) á öruggan stað. Það mikilvægasta er að forðast að gera illt verra. Lestu áfram. это.

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

Þjónninn var líkamlegur, svo það var ómögulegt að taka skyndimynd. Afritið er tilbúið, við skulum halda áfram.

Að athuga skráarkerfið

Áður en við reynum að endurheimta gagnagrunninn þurfum við að ganga úr skugga um að skráarkerfið sjálft sé óskemmd. Og ef einhverjar villur eru þurfum við að laga þær, því annars gætum við aðeins gert illt verra.

Í mínu tilfelli var skráarkerfið með gagnagrunninum sett upp í "/srv" og gerðin var ext4.

Stöðva gagnagrunninn: kerfisstýring stöðva postgresql@9.5-main.service og við athugum hvort skráarkerfið sé ekki í notkun af neinum og hægt sé að aftengja það með skipuninni LSOF:
lsof +D /srv

Ég þurfti líka að stöðva redis gagnagrunninn því hann var líka að nota "/srv"Svo tók ég það úr sambandi. /srv (óupphæð).

Skráarkerfisathugunin var framkvæmd með gagnforritinu e2fsck með -f takkanum (Þvingaða athugun jafnvel þótt skráarkerfið sé merkt sem hreint):

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

Næst, með því að nota gagnsemina dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep athugað) þú getur staðfest að athugunin hafi í raun 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 nákvæmara sagt, farið aftur í tómarúm fullt (auðvitað þarftu að tengja skráakerfið aftur og ræsa gagnagrunninn).

Ef þú ert með líkamlegan netþjón, vertu viss um að athuga stöðu diskanna (í gegnum smartctl -a /dev/XXX) eða RAID stjórnandann til að ganga úr skugga um að vandamálið væri ekki vélbúnaðartengt. Í mínu tilfelli reyndist RAID-ið vera vélbúnaðartengt, svo ég bað kerfisstjórann á staðnum að athuga stöðu RAID-kerfisins (þjónninn var nokkur hundruð kílómetra í burtu). Hann sagði að engar villur væru til staðar, sem þýddi að við gætum örugglega hafið endurheimt.

Tilraun 1: núll_skemmdar_síður

Tengjast gagnagrunninum í gegnum psql með aðgangi með ofurnotandaréttindum. Við þurfum ofurnotanda vegna þess að valmöguleikinn núll_skemmdar_síður Aðeins það getur breytt því. Í mínu tilfelli er það postgres:

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

Valkostur núll_skemmdar_síður þurfti að hunsa lesvillur (af vefsíðu postgrespro):

Þegar skemmd síðuhaus greinist tilkynnir PostgreSQL venjulega villu og hættir við núverandi færslu. Ef breytan zero_damaged_pages er virk, gefur kerfið í staðinn út viðvörun, núllstillir skemmdu síðuna og heldur áfram vinnslu. Þessi hegðun spillir gögnunum, sérstaklega öllum röðum á skemmdu síðunni.

Við virkjum þennan möguleika og reynum að ryksuga borðið að fullu:

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, bilun.

Við lentum í svipuðum villum:

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

Tilraun 2: endurvísitölu

Fyrsta ráðið frá Google hjálpaði ekki. Eftir nokkurra mínútna leit fann ég annað ráðið: endurvísitölu Skemmd tafla. Ég hef séð þessi ráð víða en þau vekja ekki traust. Við skulum endurskoða vísitöluna:

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)

endurvísitölu lokið án vandræða.

Það hjálpaði þó ekki, TÚMSÚG FULLT hrundi með svipaðri villu. Þar sem ég er vanur mistökum hélt ég áfram að leita ráða á netinu og rakst á frekar áhugaverða grein.

Tilraun 3: VELJA, TAKMARKA, FRÁSKIPTA

Í greininni hér að ofan var lagt til að fara yfir töfluna röð fyrir röð og eyða vandræðalegum gögnum. Fyrst var nauðsynlegt að fara yfir allar raðir:

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 tilfelli innihélt taflan +1 628 991 XNUMX línur! Það var nauðsynlegt að gæta að gagnaskipting, en það er umræðuefni í aðra umræðu. Það var laugardagur, ég keyrði þ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 uppgötvaði ég að eftir 20 klukkustundir höfðu aðeins 2% gagnanna verið skönnuð! Ég vildi ekki bíða í 50 daga. Annað algjört mistök.

En ég gafst ekki upp. Ég velti fyrir mér hvers vegna skönnunin tæki svona langan tíma. Úr skjöluninni (aftur á postgrespro) lærði ég:

OFFSET segir því að sleppa tilgreindum fjölda raða áður en byrjað er að skrifa raðir.
Ef bæði OFFSET og LIMIT eru tilgreind, sleppir kerfið fyrst OFFSET röðum og byrjar síðan að telja raðir fyrir LIMIT.

Þegar LIMIT er notað er mikilvægt að nota einnig ORDER BY klausuna til að tryggja að niðurstöðuröðin sé skilað í ákveðinni röð. Annars verða ófyrirsjáanleg undirmengi raða skilað.

Það er augljóst að skipunin hér að ofan var röng: í fyrsta lagi var enginn panta eftir, gæti niðurstaðan verið röng. Í öðru lagi þurfti Postgres fyrst að skanna og sleppa OFFSET röðum, og með vaxandi OFFSET framleiðnin myndi lækka enn frekar.

Tilraun 4: Handtaka textadump

Þá fékk ég snilldarhugmynd: að taka saman texta og greina síðustu upptöku línuna.

En fyrst skulum við kynna okkur 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) fyrir röðina. Áætlunin var sem hér segir:

  1. Við byrjum að vista gögnin í textaformi (í formi SQL skipana)
  2. Á einhverjum tímapunkti yrði niðurhalið truflað vegna villu, en textaskráin yrði samt vistuð á disknum.
  3. Við skoðum lok textaskrárinnar og finnum þannig auðkenni (id) síðustu línunnar sem var fjarlægð með góðum árangri.

Ég byrjaði að senda inn texta:

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

Eins og búist var við mistókst niðurhalið 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 leit á enda ruslahaugsins (hali -5 ./my_dump.dump) komst að því að niðurhalið var rofið á línunni með auðkenni 186 525„Vandamálið er þá með línunúmerið 186 526, það er bilað og það þarf að eyða því!“ hugsaði ég. En eftir að hafa sent fyrirspurn í gagnagrunninn:
«veldu * úr ws_log_smevlog þar sem id=186529„Það kom í ljós að allt var í lagi með þessa röð... Raðir með vísitölunum 186.530 - 186.540 virkuðu líka án vandræða. Önnur „snilldarhugmynd“ mistókst. Seinna skildi ég hvers vegna þetta gerðist: þegar gögnum er eytt/breytt úr töflu er þeim ekki eytt líkamlega, heldur merkt sem „dauðir tengipunktar“, þá kemur sjálfvirkt tómarúm og merkir þessar raðir sem eyddar og leyfir endurnýtingu þeirra. Til glöggvunar, ef gögnin í töflu breytast og sjálfvirk tæming er virkjuð, eru þau ekki geymd í röð.

Tilraun 5: VELJA, FRÁ, ÞAR SEM id=

Mistök gera okkur sterkari. Maður ætti aldrei að gefast upp, maður verður að halda áfram og trúa á sjálfan sig og hæfileika sína. Svo ég ákvað að prófa annan möguleika: einfaldlega að skoða allar færslurnar í gagnagrunninum eina af annarri. Þar sem ég þekki uppbyggingu töflunnar minnar (sjá hér að ofan) höfum við auðkennisreit, sem er einstakur (aðallykill). Við höfum 1.628.991 raðir í töflunni og id Þau eru í réttri röð, sem þýðir að við getum einfaldlega farið yfir þau eitt af öðru:

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

Fyrir þá sem skilja ekki, þá virkar skipunin svona: hún skannar töfluna línu fyrir línu og sendir stdout til / dev / null, en ef SELECT skipunin mistekst, þá er villutextinn prentaður (stderr er sent í stjórnborðið) og línan sem inniheldur villuna er prentuð (þökk sé ||, sem þýðir að select hafði vandamál (skilakóði skipunarinnar er ekki 0)).

Ég var heppinn, ég fékk vísitölur búnar til á 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 röðina með tilskildu auðkenninu. Í orði kveðnu ætti það að virka. Keyrum því skipunina í tmux og við skulum fara að sofa.

Um morguninn komst ég að því að um 90.000 færslur höfðu verið skoðaðar, sem eru rétt rúmlega 5%. Frábær niðurstaða miðað við fyrri aðferðina (2%)! En ég vildi ekki bíða í 20 daga...

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

Viðskiptavinurinn hafði úthlutað framúrskarandi netþjóni fyrir gagnagrunninn: einn með tveimur örgjörvum. Intel Xeon E5-2697 v2Við höfðum heil 48 þræði tiltæka! Álagið á netþjóninn var meðal, svo við gátum auðveldlega tekist á við um 20 þræði. Við höfðum líka nóg af vinnsluminni: heil 384 gígabæt!

Þess vegna þurfti að samstilla liðið:

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

Ég hefði getað skrifað fallegt og glæsilegt forskrift hér, en ég valdi hraðvirkustu samsíða aðferðina: að skipta sviðinu 0-1628991 handvirkt í bil með 100.000 færslum og keyra 16 skipanir af eftirfarandi gerð sérstaklega:

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. Að tengjast gagnagrunninum tekur líka tíma og kerfisauðlindir. Að tengja 1.628.991 var ekki mjög snjallt, þú munt vera sammála. Svo við skulum sækja 1000 raðir á hverja tengingu í staðinn fyrir bara eina. Skipunin endaði svona:

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 eftirfarandi skipanir:

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! Nánar tiltekið (gildin fyrir XXX og ZZZ voru ekki lengur vistuð):

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ð við höfum þrjár raðir með villum. Auðkenni fyrstu og annarrar vandræðalegu færslunnar voru á bilinu 829.000 til 830.000, og auðkenni þeirrar þriðju var á bilinu 146.000 til 147.000. Næst þurftum við einfaldlega að finna nákvæm auðkennisgildi vandræðafærslnanna. Til að gera þetta skönnum við úrval vandræðafærslnanna okkar í 1 þrepum og auðkennum auðkennin:

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ð höfum fundið raðir sem valda vandræðum. Við skulum nálgast gagnagrunninn með psql og reyna 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 vandræða, jafnvel án þess að valkosturinn væri til staðar. 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ð taka afrit með því að nota pg_dumpRuslið var tekið án nokkurra villna! Vandamálið var leyst með þessari ótrúlega heimskulegu aðferð. Ég var himinlifandi að finna loksins lausn eftir svo mörg mistök!

Þakkir og niðurstaða

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

Og að lokum vil ég þakka PostgresPro fyrir að þýða skjölunina yfir á rússnesku og fyrir... alveg ókeypis námskeið á netinu, sem voru mjög gagnleg við greiningu vandamálsins.

Heimild: www.habr.com

Kauptu áreiðanlega hýsingu fyrir síður með DDoS vernd, VPS VDS netþjónum 🔥 Kauptu áreiðanlega vefhýsingu með DDoS vörn, VPS VDS netþjónum | ProHoster