Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)

Ningependa kushiriki nawe uzoefu wangu wa kwanza uliofaulu wa kurejesha hifadhidata ya Postgres kwa utendakazi kamili. Nilifahamiana na Postgres DBMS nusu mwaka uliopita; kabla ya hapo sikuwa na uzoefu katika usimamizi wa hifadhidata hata kidogo.

Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)

Ninafanya kazi kama mhandisi wa nusu-DevOps katika kampuni kubwa ya IT. Kampuni yetu hutengeneza programu kwa huduma za upakiaji wa juu, na ninawajibika kwa utendakazi, matengenezo na usambazaji. Nilipewa kazi ya kawaida: kusasisha programu kwenye seva moja. Maombi yameandikwa kwa Django, wakati wa uhamishaji wa sasisho hufanywa (mabadiliko katika muundo wa hifadhidata), na kabla ya mchakato huu tunachukua utupaji kamili wa hifadhidata kupitia programu ya kawaida ya pg_dump, ikiwa tu.

Hitilafu isiyotarajiwa ilitokea wakati wa kuchukua taka (Toleo la Postgres 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

Mdudu "ukurasa batili kwenye kizuizi" inazungumzia matatizo katika ngazi ya mfumo wa faili, ambayo ni mbaya sana. Kwenye vikao mbalimbali ilipendekezwa kufanya UTUPU KAMILI na chaguo sifuri_kurasa_zilizoharibika kutatua tatizo hili. Naam, tujaribu ...

Kujiandaa kwa kupona

UTAJIRI! Hakikisha umechukua chelezo ya Postgres kabla ya jaribio lolote la kurejesha hifadhidata yako. Ikiwa una mashine pepe, simamisha hifadhidata na uchukue muhtasari. Iwapo haiwezekani kupiga picha, simamisha hifadhidata na unakili yaliyomo kwenye saraka ya Postgres (pamoja na faili za wal) hadi mahali salama. Jambo kuu katika biashara yetu sio kufanya mambo kuwa mabaya zaidi. Soma hii.

Kwa kuwa hifadhidata kwa ujumla ilinifanyia kazi, nilijiwekea dampo la kawaida la hifadhidata, lakini nilitenga jedwali na data iliyoharibiwa (chaguo -T, --exclude-meza=JEDWALI katika pg_dump).

Seva ilikuwa ya kimwili, haikuwezekana kupiga picha. Hifadhi rudufu imeondolewa, wacha tuendelee.

Angalia mfumo wa faili

Kabla ya kujaribu kurejesha hifadhidata, tunahitaji kuhakikisha kuwa kila kitu kiko sawa na mfumo wa faili yenyewe. Na katika kesi ya makosa, sahihisha, kwa sababu vinginevyo unaweza tu kufanya mambo kuwa mabaya zaidi.

Kwa upande wangu, mfumo wa faili na hifadhidata uliwekwa ndani "/srv" na aina ilikuwa ext4.

Kusimamisha hifadhidata: systemctl kuacha [barua pepe inalindwa] na angalia kuwa mfumo wa faili hautumiki na mtu yeyote na unaweza kupunguzwa kwa kutumia amri ls ya:
lsof +D /srv

Ilinibidi pia kusimamisha hifadhidata ya redis, kwani ilikuwa ikitumia pia "/srv". Ifuatayo nilishusha / srv (kupanda).

Mfumo wa faili ulikaguliwa kwa kutumia matumizi e2fsck na swichi -f (Lazimisha kukagua hata kama mfumo wa faili umewekwa alama kuwa safi):

Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)

Ifuatayo, kwa kutumia matumizi dumpe2fs (sudo dumpe2fs /dev/mapper/gu2β€”sys-srv | grep imeangaliwa) unaweza kuthibitisha kuwa ukaguzi ulifanywa:

Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)

e2fsck inasema kwamba hakuna shida zilizopatikana katika kiwango cha mfumo wa faili wa ext4, ambayo inamaanisha kuwa unaweza kuendelea kujaribu kurejesha hifadhidata, au tuseme kurudi kwa utupu umejaa (kwa kweli, unahitaji kuweka mfumo wa faili nyuma na kuanza hifadhidata).

Ikiwa unayo seva ya mwili, hakikisha uangalie hali ya diski (kupitia smartctl -a /dev/XXX) au kidhibiti cha RAID ili kuhakikisha kuwa tatizo haliko katika kiwango cha vifaa. Katika kesi yangu, RAID iligeuka kuwa "vifaa", kwa hiyo nilimwomba msimamizi wa ndani kuangalia hali ya RAID (seva ilikuwa kilomita mia kadhaa kutoka kwangu). Alisema kuwa hakukuwa na makosa, ambayo inamaanisha kuwa tunaweza kuanza marejesho.

Jaribio la 1: zero_damaged_pages

Tunaunganisha kwenye hifadhidata kupitia psql na akaunti ambayo ina haki za mtumiaji mkuu. Tunahitaji mtumiaji mkuu, kwa sababu ... chaguo sifuri_kurasa_zilizoharibika pekee ndiye anayeweza kubadilika. Kwa upande wangu ni postgres:

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

Chaguo sifuri_kurasa_zilizoharibika inahitajika ili kupuuza makosa ya kusoma (kutoka kwa wavuti ya postgrespro):

PostgreSQL inapogundua kichwa mbovu cha ukurasa, kwa kawaida huripoti hitilafu na kughairi shughuli ya sasa. Iwapo zero_damaged_pages imewezeshwa, mfumo badala yake unatoa onyo, kuondoa ukurasa ulioharibika kwenye kumbukumbu, na kuendelea kuchakata. Tabia hii huharibu data, yaani safu mlalo zote kwenye ukurasa ulioharibiwa.

Tunawezesha chaguo na jaribu kufanya utupu kamili wa meza:

VACUUM FULL VERBOSE

Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)
Kwa bahati mbaya, bahati mbaya.

Tumekumbana na hitilafu sawa:

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 - utaratibu wa kuhifadhi "data ndefu" katika Poetgres ikiwa haifai kwenye ukurasa mmoja (8kb kwa chaguo-msingi).

Jaribio la 2: reindex

Ushauri wa kwanza kutoka kwa Google haukusaidia. Baada ya dakika chache za kutafuta, nilipata kidokezo cha pili - kutengeneza reindex meza iliyoharibiwa. Niliona ushauri huu katika sehemu nyingi, lakini haukuchochea kujiamini. Wacha tuonyeshe tena:

reindex table ws_log_smevlog

Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)

reindex kukamilika bila matatizo.

Walakini, hii haikusaidia, UTUPU UMEJAA ilianguka na hitilafu sawa. Kwa kuwa nimezoea kutofaulu, nilianza kutafuta ushauri zaidi kwenye mtandao na nikapata wa kufurahisha zaidi nakala.

Jaribio la 3: CHAGUA, LIMIT, OFFSET

Kifungu kilicho hapo juu kilipendekeza kuangalia safu mlalo ya jedwali kwa safu mlalo na kuondoa data yenye matatizo. Kwanza tulihitaji kuangalia mistari yote:

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

Katika kesi yangu, meza iliyomo 1 628 991 mistari! Ilikuwa ni lazima kutunza vizuri ugawaji wa data, lakini hii ni mada ya mjadala tofauti. Ilikuwa Jumamosi, niliendesha amri hii kwa tmux na kwenda kulala:

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

Ilipofika asubuhi niliamua kuangalia jinsi mambo yalivyokuwa. Kwa mshangao wangu, niligundua kuwa baada ya masaa 20, ni 2% tu ya data ilikuwa imechanganuliwa! Sikutaka kusubiri siku 50. Kushindwa kwingine kamili.

Lakini sikukata tamaa. Nilishangaa kwa nini scanning ilichukua muda mrefu. Kutoka kwa nyaraka (tena kwenye postgrespro) niligundua:

OFFSET inabainisha kuruka idadi maalum ya safu mlalo kabla ya kuanza kutoa safu mlalo.
Ikiwa zote OFFSET na LIMIT zimebainishwa, mfumo kwanza unaruka safu mlalo za OFFSET na kisha kuanza kuhesabu safu mlalo kwa kikwazo cha LIMIT.

Unapotumia LIMIT, ni muhimu pia kutumia ORDER BY clause ili safu mlalo za matokeo zirudishwe kwa mpangilio maalum. Vinginevyo, safu mlalo zisizotabirika zitarejeshwa.

Ni wazi, amri hapo juu haikuwa sahihi: kwanza, hakukuwa na kuagiza na, matokeo yanaweza kuwa na makosa. Pili, Postgres kwanza ilibidi kuchanganua na kuruka safu mlalo za OFFSET, na kwa kuongezeka Kukabiliana tija ingepungua zaidi.

Jaribio la 4: chukua taka katika fomu ya maandishi

Kisha wazo lililoonekana kuwa zuri sana likaja akilini mwangu: chukua utupaji katika umbo la maandishi na uchanganue mstari wa mwisho uliorekodiwa.

Lakini kwanza, hebu tuangalie muundo wa meza. ws_log_smevlog:

Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)

Kwa upande wetu tuna safu "Kitambulisho", ambayo ilikuwa na kitambulisho cha kipekee (kaunta) cha safu mlalo. Mpango ulikuwa hivi:

  1. Tunaanza kuchukua dampo katika fomu ya maandishi (katika mfumo wa amri za sql)
  2. Kwa wakati fulani, utupaji ungeingiliwa kwa sababu ya kosa, lakini faili ya maandishi bado ingehifadhiwa kwenye diski.
  3. Tunaangalia mwisho wa faili ya maandishi, kwa hivyo tunapata kitambulisho (id) cha mstari wa mwisho ambao uliondolewa kwa mafanikio.

Nilianza kuchukua dampo kwa njia ya maandishi:

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

Utupaji, kama inavyotarajiwa, ulikatizwa na hitilafu sawa:

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

Zaidi kupitia mkia Niliangalia mwisho wa dampo (mkia -5 ./my_dump.dump) iligundua kuwa dampo lilikatizwa kwenye mstari na id 186 525. "Kwa hivyo shida iko kwenye mstari wa id 186 526, imevunjika, na inahitaji kufutwa!" - Nilidhani. Lakini, kufanya swali kwa hifadhidata:
Β«chagua * kutoka ws_log_smevlog ambapo id=186529"Ilibadilika kuwa kila kitu kilikuwa sawa na mstari huu ... Safu zilizo na fahirisi 186 - 530 pia zilifanya kazi bila matatizo. "Wazo la kipaji" lingine lilishindwa. Baadaye nilielewa kwa nini hii ilitokea: wakati wa kufuta na kubadilisha data kutoka kwa jedwali, hazifutwa kabisa, lakini zimewekwa alama kama "tuples zilizokufa", kisha inakuja. otomatiki na kuashiria mistari hii kuwa imefutwa na kuruhusu mistari hii kutumika tena. Ili kuelewa, ikiwa data kwenye jedwali inabadilika na otomatiki imewezeshwa, basi haihifadhiwa kwa mlolongo.

Jaribio la 5: CHAGUA, KUTOKA, WAPI id=

Kushindwa hutufanya kuwa na nguvu zaidi. Haupaswi kamwe kukata tamaa, unahitaji kwenda hadi mwisho na ujiamini mwenyewe na uwezo wako. Kwa hivyo niliamua kujaribu chaguo jingine: angalia tu rekodi zote kwenye hifadhidata moja baada ya nyingine. Kujua muundo wa jedwali langu (tazama hapo juu), tunayo uwanja wa kitambulisho ambao ni wa kipekee (ufunguo wa msingi). Tuna safu 1 kwenye jedwali na id ziko katika mpangilio, ambayo inamaanisha tunaweza kuzipitia moja baada ya nyingine:

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

Ikiwa mtu yeyote haelewi, amri inafanya kazi kama ifuatavyo: inachanganua safu mlalo ya jedwali kwa safu na kutuma stdout kwa / dev / null, lakini ikiwa amri ya SELECT itashindwa, basi maandishi ya makosa yanachapishwa (stderr hutumwa kwa koni) na mstari ulio na kosa huchapishwa (shukrani kwa ||, ambayo inamaanisha kuwa mteule alikuwa na shida (nambari ya kurudi ya amri. sio 0)).

Nilikuwa na bahati, nilikuwa na faharisi zilizoundwa kwenye uwanja id:

Uzoefu wangu wa kwanza kupata hifadhidata ya Postgres baada ya kutofaulu (ukurasa usio sahihi katika block 4123007 ya relatton base/16490)

Hii ina maana kwamba kutafuta mstari na kitambulisho unachotaka haipaswi kuchukua muda mwingi. Kwa nadharia inapaswa kufanya kazi. Kweli, wacha tuendeshe amri ndani tmux na twende tukalale.

Kufikia asubuhi niligundua kuwa maingizo 90 yametazamwa, ambayo ni zaidi ya 000%. Matokeo bora ikilinganishwa na njia ya awali (5%)! Lakini sikutaka kusubiri siku 2 ...

Jaribio la 6: CHAGUA, KUTOKA, WHERE id >= na kitambulisho

Mteja alikuwa na seva bora iliyojitolea kwa hifadhidata: processor mbili Intel Xeon E5-2697 v2, kulikuwa na nyuzi 48 hivi katika eneo letu! Mzigo kwenye seva ulikuwa wa wastani; tunaweza kupakua takriban nyuzi 20 bila matatizo yoyote. Pia kulikuwa na RAM ya kutosha: hadi gigabytes 384!

Kwa hivyo, amri ilihitaji kusawazishwa:

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

Hapa iliwezekana kuandika hati nzuri na ya kifahari, lakini nilichagua njia ya kusawazisha haraka sana: kwa mikono niligawanya safu 0-1628991 katika vipindi vya rekodi 100 na kukimbia kando amri 000 za fomu:

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

Lakini si hivyo tu. Kinadharia, kuunganisha kwenye hifadhidata pia huchukua muda na rasilimali za mfumo. Kuunganisha 1 haikuwa nzuri sana, utakubali. Kwa hivyo, wacha tupate safu 628 badala ya moja kwenye unganisho moja. Kama matokeo, timu ilibadilika kuwa hii:

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

Fungua madirisha 16 kwenye kikao cha tmux na utekeleze amri:

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

Siku moja baadaye nilipokea matokeo ya kwanza! Yaani (thamani XXX na ZZZ hazihifadhiwi tena):

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

Hii ina maana kwamba mistari mitatu ina makosa. Vitambulisho vya rekodi zenye matatizo ya kwanza na ya pili vilikuwa kati ya 829 na 000, vitambulisho vya tatu vilikuwa kati ya 830 na 000. Kisha, ilibidi tupate thamani halisi ya kitambulisho cha rekodi zenye matatizo. Ili kufanya hivyo, tunaangalia safu yetu na rekodi zenye shida na hatua ya 146 na kutambua kitambulisho:

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

Heri kuishia

Tulipata mistari yenye matatizo. Tunaingia kwenye hifadhidata kupitia psql na jaribu kuifuta:

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

Kwa mshangao wangu, maingizo yalifutwa bila matatizo yoyote hata bila chaguo sifuri_kurasa_zilizoharibika.

Kisha niliunganisha kwenye hifadhidata, nikafanya UTUPU UMEJAA (Nadhani haikuwa lazima kufanya hivi), na mwishowe nilifanikiwa kuondoa nakala rudufu kwa kutumia pg_dampo. Dampo lilichukuliwa bila makosa yoyote! Tatizo lilitatuliwa kwa njia ya kijinga. Furaha haikuwa na kikomo, baada ya kushindwa mara nyingi tulifanikiwa kupata suluhisho!

Shukrani na Hitimisho

Hivi ndivyo uzoefu wangu wa kwanza wa kurejesha hifadhidata halisi ya Postgres ulivyotokea. Nitakumbuka uzoefu huu kwa muda mrefu.

Na hatimaye, ningependa kusema asante kwa PostgresPro kwa kutafsiri nyaraka kwa Kirusi na kwa kozi za mtandaoni za bure kabisa, ambayo ilisaidia sana wakati wa uchambuzi wa tatizo.

Chanzo: mapenzi.com

Kuongeza maoni