Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)

Ես կցանկանայի ձեզ հետ կիսվել Postgres-ի տվյալների բազան լիարժեք գործունակությամբ վերականգնելու իմ առաջին հաջող փորձով: Postgres DBMS-ին ծանոթացել եմ կես տարի առաջ, մինչ այդ տվյալների բազայի կառավարման փորձ ընդհանրապես չունեի։

Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)

Ես աշխատում եմ որպես կիսամյակային DevOps-ի ինժեներ ՏՏ խոշոր ընկերությունում: Մեր ընկերությունը մշակում է ծրագրակազմ բարձր բեռնված ծառայությունների համար, և ես պատասխանատու եմ կատարման, պահպանման և տեղակայման համար: Ինձ տրվեց ստանդարտ առաջադրանք՝ թարմացնել հավելվածը մեկ սերվերի վրա: Հավելվածը գրված է Django-ով, թարմացման ժամանակ կատարվում են միգրացիաներ (շտեմարանի կառուցվածքի փոփոխություններ), և մինչ այս պրոցեսը ստանդարտ pg_dump ծրագրով ամբողջական շտեմարան ենք վերցնում, ամեն դեպքում։

Անսպասելի սխալ տեղի ունեցավ աղբավայր վերցնելիս (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

Սխալ «անվավեր էջ բլոկում» խոսում է ֆայլային համակարգի մակարդակի խնդիրների մասին, ինչը շատ վատ է։ Տարբեր ֆորումներում առաջարկվել է անել ԼԻՎԱԾ ՎԱԿՈՒՈՒՄ տարբերակով զրոյական_վնասված_էջեր այս խնդիրը լուծելու համար։ Դե, եկեք փորձենք ...

Պատրաստվում է վերականգնման

Նախազգուշացում Համոզվեք, որ վերցրեք Postgres-ի կրկնօրինակում ձեր տվյալների բազան վերականգնելու ցանկացած փորձից առաջ: Եթե ​​ունեք վիրտուալ մեքենա, դադարեցրեք տվյալների բազան և լուսանկարեք: Եթե ​​հնարավոր չէ լուսանկարել, դադարեցրեք տվյալների բազան և պատճենեք Postgres գրացուցակի բովանդակությունը (ներառյալ wal ֆայլերը) ապահով վայրում: Մեր բիզնեսում գլխավորը ամեն ինչ չվատացնելն է։ Կարդացեք это.

Քանի որ տվյալների բազան ընդհանուր առմամբ աշխատում էր ինձ համար, ես սահմանափակվեցի սովորական տվյալների բազայով, բայց բացառեցի վնասված տվյալների աղյուսակը (տարբերակ -T, --exclude-table=TABLE pg_dump-ում):

Սերվերը ֆիզիկական էր, անհնար էր լուսանկարել: Կրկնօրինակը հեռացվել է, եկեք անցնենք առաջ:

Ֆայլային համակարգի ստուգում

Նախքան տվյալների բազան վերականգնելու փորձը, մենք պետք է համոզվենք, որ ամեն ինչ կարգին է հենց ֆայլային համակարգի հետ: Իսկ սխալների դեպքում ուղղեք դրանք, քանի որ հակառակ դեպքում կարող եք միայն վատթարացնել իրավիճակը։

Իմ դեպքում, տվյալների բազայով ֆայլային համակարգը տեղադրվել է «/srv» իսկ տեսակը եղել է ext4։

Տվյալների բազայի դադարեցում. systemctl կանգառ [էլեկտրոնային փոստով պաշտպանված] և ստուգեք, որ ֆայլային համակարգը չի օգտագործվում որևէ մեկի կողմից և կարող է ապամոնտաժվել հրամանի միջոցով lsof:
lsof +D /srv

Ես նույնպես ստիպված էի դադարեցնել redis տվյալների բազան, քանի որ այն նույնպես օգտագործում էր «/srv». Հաջորդը ես ապամոնտաժեցի / սվվ (քանակը):

Ֆայլային համակարգը ստուգվել է կոմունալ ծրագրի միջոցով e2fsck անջատիչով -f (Պարտադիր ստուգում, նույնիսկ եթե ֆայլային համակարգը մաքուր է նշված):

Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)

Հաջորդը, օգտագործելով կոմունալը dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep-ը ստուգվեց) կարող եք ստուգել, ​​որ ստուգումն իրականում կատարվել է.

Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)

e2fsck ասում է, որ ext4 ֆայլային համակարգի մակարդակում խնդիրներ չեն հայտնաբերվել, ինչը նշանակում է, որ դուք կարող եք շարունակել փորձել վերականգնել տվյալների բազան, ավելի ճիշտ՝ վերադառնալ վակուում լի (իհարկե, դուք պետք է ետ տեղադրեք ֆայլային համակարգը և գործարկեք տվյալների բազան):

Եթե ​​ունեք ֆիզիկական սերվեր, համոզվեք, որ ստուգեք սկավառակների կարգավիճակը (միջոցով smartctl -a /dev/XXX) կամ RAID կարգավորիչ՝ համոզվելու համար, որ խնդիրը ապարատային մակարդակում չէ: Իմ դեպքում, RAID-ը պարզվեց, որ «ապարատային» է, ուստի ես տեղական ադմինիստրատորին խնդրեցի ստուգել RAID-ի կարգավիճակը (սերվերն ինձանից մի քանի հարյուր կիլոմետր հեռու էր): Նա ասաց, որ սխալներ չկան, ինչը նշանակում է, որ մենք միանշանակ կարող ենք սկսել վերականգնումը։

Փորձ 1. զրոյական_վնասված_էջեր

Մենք միանում ենք տվյալների շտեմարանին psql-ի միջոցով այնպիսի աքաունթով, որն ունի գերօգտագործողի իրավունքներ: Մեզ սուպերօգտատեր է պետք, քանի որ... տարբերակ զրոյական_վնասված_էջեր միայն նա կարող է փոխվել: Իմ դեպքում դա postgres է.

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

Տարբերակ զրոյական_վնասված_էջեր անհրաժեշտ է կարդալու սխալները անտեսելու համար (postgrespro կայքից).

Երբ PostgreSQL-ը հայտնաբերում է էջի կոռումպացված վերնագիր, այն սովորաբար հայտնում է սխալի մասին և ընդհատում ընթացիկ գործարքը: Եթե ​​zero_damaged_pages-ը միացված է, համակարգը դրա փոխարեն նախազգուշացում է տալիս, զրոյացնում է վնասված էջը հիշողության մեջ և շարունակում է մշակումը: Այս վարքագիծը ոչնչացնում է տվյալները, մասնավորապես վնասված էջի բոլոր տողերը:

Մենք միացնում ենք տարբերակը և փորձում ենք աղյուսակների ամբողջական վակուում կատարել.

VACUUM FULL VERBOSE

Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)
Ցավոք սրտի, դժբախտություն:

Մենք հանդիպեցինք նմանատիպ սխալի.

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 – Poetgres-ում «երկար տվյալների» պահպանման մեխանիզմ, եթե դրանք չեն տեղավորվում մեկ էջի վրա (կանխադրված 8 կբ):

Փորձ 2. վերաինդեքսավորում

Google-ի առաջին խորհուրդը չօգնեց. Մի քանի րոպե փնտրելուց հետո գտա երկրորդ խորհուրդը՝ պատրաստել վերաինդեքսավորել վնասված սեղան. Ես շատ տեղերում տեսա այս խորհուրդը, բայց վստահություն չներշնչեց։ Եկեք վերաինդեքսավորենք.

reindex table ws_log_smevlog

Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)

վերաինդեքսավորել ավարտված է առանց խնդիրների:

Այնուամենայնիվ, սա չօգնեց, ՎԱԿՈՒՈՒՄԸ ԼՐԱՑՎԱԾ է վթարի է ենթարկվել նմանատիպ սխալով: Քանի որ ես սովոր եմ անհաջողություններին, ես սկսեցի ավելի շատ խորհուրդներ փնտրել ինտերնետում և հանդիպեցի բավականին հետաքրքիր բանի статью.

Փորձ 3. SELECT, LIMIT, OFFSET

Վերոնշյալ հոդվածն առաջարկում էր աղյուսակը տող առ տող նայել և հեռացնել խնդրահարույց տվյալները։ Սկզբում մենք պետք է նայեինք բոլոր տողերին.

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

Իմ դեպքում աղյուսակը պարունակում էր 1 628 991 տողեր Պետք էր լավ հոգ տանել տվյալների բաժանում, բայց սա առանձին քննարկման թեմա է։ Շաբաթ էր, ես գործարկեցի այս հրամանը tmux-ով և գնացի քնելու.

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

Առավոտյան ես որոշեցի ստուգել, ​​թե ինչպես են ընթանում գործերը: Ի զարմանս ինձ, ես հայտնաբերեցի, որ 20 ժամ անց տվյալների միայն 2%-ն է սկանավորվել: Ես չէի ուզում սպասել 50 օր։ Եվս մեկ կատարյալ ձախողում.

Բայց ես չհանձնվեցի։ Ես զարմացա, թե ինչու սկանավորումն այդքան երկար տևեց: Փաստաթղթերից (կրկին postgrespro-ում) պարզեցի.

OFFSET-ը սահմանում է բաց թողնել նշված թվով տողեր՝ նախքան ելքային տողերը սկսելը:
Եթե ​​երկուսն էլ OFFSET-ը և LIMIT-ը նշված են, համակարգը նախ բաց է թողնում OFFSET-ի տողերը, այնուհետև սկսում է հաշվել տողերը LIMIT սահմանափակման համար:

LIMIT-ն օգտագործելիս կարևոր է նաև օգտագործել ORDER BY կետը, որպեսզի արդյունքի տողերը վերադարձվեն որոշակի հերթականությամբ: Հակառակ դեպքում տողերի անկանխատեսելի ենթաբազմությունները կվերադարձվեն:

Ակնհայտորեն, վերը նշված հրահանգը սխալ էր. նախ՝ չկար ըստ կարգի, արդյունքը կարող է սխալ լինել։ Երկրորդը, Postgres-ը նախ պետք է սկանավորեր և բաց թողներ OFFSET տողերը, ընդ որում՝ ավելանալով OFFSET արտադրողականությունն էլ ավելի կնվազի։

Փորձ 4. աղբահանություն վերցրեք տեքստային ձևով

Հետո մի փայլուն թվացող միտք ծագեց իմ գլխում. տեքստի տեսքով մի աղբանոց վերցրու և վերլուծիր վերջին ձայնագրված տողը:

Բայց նախ, եկեք նայենք աղյուսակի կառուցվածքին: ws_log_smevlog:

Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)

Մեր դեպքում մենք ունենք սյունակ "Id", որը պարունակում էր տողի եզակի նույնացուցիչը (հաշվիչը): Պլանն այսպիսին էր.

  1. Մենք սկսում ենք աղբանոց վերցնել տեքստային ձևով (sql հրամանների տեսքով)
  2. Ժամանակի որոշակի պահի, աղբավայրը կդադարեցվի սխալի պատճառով, բայց տեքստային ֆայլը դեռ կպահվի սկավառակի վրա
  3. Մենք նայում ենք տեքստային ֆայլի վերջում, դրանով իսկ գտնում ենք հաջողությամբ հեռացված վերջին տողի նույնացուցիչը (id)

Ես սկսեցի աղբանոց վերցնել տեքստային ձևով.

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

Աղբավայրը, ինչպես և սպասվում էր, ընդհատվեց նույն սխալով.

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

Հետագա միջոցով պոչ Ես նայեցի աղբի ծայրին (tail -5 ./my_dump.dump) հայտնաբերել է, որ աղբավայրն ընդհատվել է id-ով գծի վրա 186 525. «Այսպիսով, խնդիրը id 186 526-ի գծում է, այն կոտրված է և պետք է ջնջվի»: - Ես մտածեցի. Բայց տվյալների բազայում հարցում կատարելով.
«ընտրեք * ws_log_smevlog-ից, որտեղ id=186529«Պարզվեց, որ այս տողով ամեն ինչ կարգին է... 186 530 - 186 540 ինդեքսներով շարքերն էլ են աշխատել առանց խնդիրների։ Մեկ այլ «փայլուն գաղափար» ձախողվեց. Ավելի ուշ ես հասկացա, թե ինչու է դա տեղի ունեցել. աղյուսակից տվյալները ջնջելիս և փոխելիս դրանք ֆիզիկապես չեն ջնջվում, այլ նշվում են որպես «մեռած կուպլիկներ», հետո գալիս է. ավտովակուում և նշում է այս տողերը որպես ջնջված և թույլ է տալիս այս տողերը կրկին օգտագործել: Հասկանալու համար, եթե աղյուսակի տվյալները փոխվում են, և autovacuum-ը միացված է, ապա դրանք հաջորդաբար չեն պահվում:

Փորձ 5. SELECT, FROM, WHERE id=

Անհաջողությունները մեզ ավելի ուժեղ են դարձնում: Երբեք չպետք է հանձնվեք, դուք պետք է գնաք մինչև վերջ և հավատաք ինքներդ ձեզ և ձեր հնարավորություններին: Այսպիսով, ես որոշեցի փորձել մեկ այլ տարբերակ. պարզապես մեկ առ մեկ նայեք տվյալների բազայի բոլոր գրառումները: Իմանալով իմ աղյուսակի կառուցվածքը (տես վերևում), մենք ունենք id դաշտ, որը եզակի է (առաջնային բանալի): Աղյուսակում ունենք 1 տող և id կարգին են, ինչը նշանակում է, որ մենք պարզապես կարող ենք անցնել դրանց միջով մեկ առ մեկ.

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

Եթե ​​որևէ մեկը չի հասկանում, հրամանն աշխատում է հետևյալ կերպ. այն սկանավորում է աղյուսակը տող առ տող և ուղարկում stdout / dev / null, բայց եթե SELECT հրամանը ձախողվի, ապա տպագրվում է սխալի տեքստը (stderr-ն ուղարկվում է վահանակ) և տպվում է սխալ պարունակող տող (շնորհիվ ||-ի, ինչը նշանակում է, որ select-ը խնդիրներ է ունեցել (հրամանի վերադարձի կոդը 0 չէ)):

Իմ բախտը բերել է, ես ինդեքսներ ունեի ստեղծված խաղադաշտում id:

Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը ձախողումից հետո (անվավեր էջ relatton base/4123007 բլոկի 16490-ում)

Սա նշանակում է, որ ցանկալի id-ով տող գտնելը շատ ժամանակ չպետք է խլի: Տեսականորեն դա պետք է աշխատի։ Դե, եկեք գործարկենք հրամանը tmux և գնանք քնելու:

Առավոտյան ես պարզեցի, որ մոտ 90 գրառում է դիտվել, ինչը 000%-ից մի փոքր ավելի է: Գերազանց արդյունք՝ համեմատած նախորդ մեթոդի հետ (5%): Բայց ես չէի ուզում սպասել 2 օր…

Փորձ 6. SELECT, FROM, WHERE id >= և id <

Հաճախորդն ուներ տվյալների բազային նվիրված հիանալի սերվեր՝ երկակի պրոցեսոր Intel Xeon E5-2697 v2, մեր գտնվելու վայրում կար 48 թեմա: Սերվերի ծանրաբեռնվածությունը միջին էր, մենք կարողացանք ներբեռնել մոտ 20 թեմա առանց որևէ խնդիրների: Նաև բավականաչափ RAM կար՝ 384 գիգաբայթ:

Հետևաբար, հրամանը պետք է զուգահեռեցվի.

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

Այստեղ հնարավոր եղավ գրել գեղեցիկ և էլեգանտ սցենար, բայց ես ընտրեցի ամենաարագ զուգահեռացման մեթոդը. 0-1628991 միջակայքը ձեռքով բաժանեցի 100 գրառումների միջակայքերի և առանձին գործարկեցի ձևի 000 հրամանները.

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

Բայց սա դեռ ամենը չէ: Տեսականորեն տվյալների շտեմարանին միանալը նույնպես պահանջում է որոշակի ժամանակ և համակարգի ռեսուրսներ: 1-ին միացնելը այնքան էլ խելացի չէր, կհամաձայնեք։ Հետևաբար, եկեք առբերենք 628 տող՝ մեկ առ մեկ կապի փոխարեն: Արդյունքում թիմը վերածվեց հետևյալի.

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

Բացեք 16 պատուհան tmux նիստում և գործարկեք հրամանները.

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

Մեկ օր անց ես ստացա առաջին արդյունքները: Մասնավորապես (XXX և ZZZ արժեքներն այլևս չեն պահպանվում).

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

Սա նշանակում է, որ երեք տող պարունակում է սխալ: Առաջին և երկրորդ խնդրի գրառումների id-ները եղել են 829-ից 000-ի միջև, երրորդի ID-ները՝ 830-ից 000-ի միջև: Այնուհետև մենք պարզապես պետք է գտնեինք խնդրի գրառումների ճշգրիտ id արժեքը: Դա անելու համար մենք դիտարկում ենք մեր տիրույթը խնդրահարույց գրառումներով 146 քայլով և նույնականացնում ենք ID-ն.

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

Լավ ավարտ

Մենք գտանք խնդրահարույց տողերը. Մենք մտնում ենք տվյալների բազա psql-ի միջոցով և փորձում ջնջել դրանք.

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

Ի զարմանս ինձ, գրառումները ջնջվեցին առանց որևէ խնդրի նույնիսկ առանց տարբերակի զրոյական_վնասված_էջեր.

Հետո միացա տվյալների բազային, արեցի ՎԱԿՈՒՈՒՄԸ ԼՐԱՑՎԱԾ է (Կարծում եմ, որ դա անելու անհրաժեշտություն չկար), և վերջապես ես հաջողությամբ հեռացրեցի կրկնօրինակը օգտագործելով pg_dump. Աղբավայրը վերցվել է առանց որևէ սխալի: Խնդիրը լուծվեց այսքան հիմար կերպով։ Ուրախությանը չափ ու սահման չկար, այսքան անհաջողություններից հետո մեզ հաջողվեց լուծում գտնել։

Երախտագիտություն և եզրակացություն

Ահա թե ինչպես ստացվեց իրական Postgres տվյալների բազայի վերականգնման իմ առաջին փորձը։ Այս փորձառությունը ես դեռ երկար կհիշեմ։

Եվ վերջապես, ուզում եմ շնորհակալություն հայտնել PostgresPro-ին փաստաթղթերը ռուսերեն թարգմանելու և դրա համար ամբողջովին անվճար առցանց դասընթացներ, ինչը շատ օգնեց խնդրի վերլուծության ժամանակ։

Source: www.habr.com

Добавить комментарий