Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)

Mwen ta renmen pataje avèk ou premye eksperyans siksè mwen nan restore yon baz done Postgres nan fonksyonalite konplè. Mwen te fè konesans ak DBMS Postgres la mwatye yon ane de sa; anvan sa mwen pa te gen okenn eksperyans nan administrasyon baz done ditou.

Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)

Mwen travay kòm yon enjenyè semi-DevOps nan yon gwo konpayi IT. Konpayi nou an devlope lojisyèl pou sèvis gwo chaj, e mwen responsab pèfòmans, antretyen ak deplwaman. Yo te bay mwen yon travay estanda: mete ajou yon aplikasyon sou yon sèl sèvè. Aplikasyon an ekri nan Django, pandan migrasyon yo aktyalizasyon yo fèt (chanjman nan estrikti baz done a), epi anvan pwosesis sa a nou pran yon pil fatra baz done konplè atravè pwogram estanda pg_dump la, jis nan ka.

Yon erè inatandi ki te fèt pandan w ap pran yon pil fatra (Postgres vèsyon 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

Erè "paj ki pa valab nan blòk" pale de pwoblèm nan nivo sistèm fichye a, ki trè move. Sou divès fowòm li te sijere pou fè VASYON KONPLÈ ak opsyon paj_zewo_domaj pou rezoud pwoblèm sa a. Oke, ann eseye ...

Preparasyon pou rekiperasyon an

ATANSYON! Asire ou ke ou pran yon backup Postgres anvan nenpòt ki tantativ retabli baz done ou a. Si ou gen yon machin vityèl, sispann baz done a epi pran yon snapshot. Si li pa posib pou pran yon snapshot, sispann baz done a epi kopye sa ki nan anyè Postgres (ki gen ladan fichye wal) nan yon kote ki an sekirite. Bagay pwensipal lan nan biznis nou an se pa fè bagay sa yo vin pi mal. Li sa a.

Depi baz done a jeneralman te travay pou mwen, mwen limite tèt mwen nan yon pil fatra baz done regilye, men eskli tab la ak done domaje (opsyon -T, --exclude-table=TABLE nan pg_dump).

Sèvè a te fizik, li te enposib pran yon snapshot. Sovgad la te retire, ann kontinye.

Tcheke sistèm dosye

Anvan ou eseye retabli baz done a, nou bezwen asire w ke tout bagay an lòd ak sistèm nan dosye tèt li. Ak nan ka ta gen erè, korije yo, paske otreman ou ka sèlman fè bagay sa yo vin pi mal.

Nan ka mwen an, sistèm dosye a ak baz done a te monte nan "/srv" ak kalite a te ext4.

Sispann baz done a: systemctl sispann [imèl pwoteje] epi tcheke ke sistèm fichye a pa itilize pa nenpòt moun epi yo ka demonte lè l sèvi avèk kòmandman an lsof:
lsof +D /srv

Mwen menm mwen te oblije sispann baz done a redis, depi li te tou lè l sèvi avèk "/srv". Apre sa mwen demonte / srv (demonte).

Sistèm fichye a te tcheke lè l sèvi avèk sèvis piblik la e2fsck ak switch la -f (Fòse tcheke menm si sistèm fichye yo make pwòp):

Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)

Apre sa, sèvi ak sèvis piblik la dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep tcheke) ou ka verifye ke chèk la te aktyèlman fèt:

Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)

e2fsck di ke pa te jwenn okenn pwoblèm nan nivo sistèm fichye ext4, ki vle di ke ou ka kontinye eseye retabli baz done a, oswa pito retounen nan vakyòm plen (nan kou, ou bezwen monte sistèm dosye a tounen epi kòmanse baz done a).

Si ou gen yon sèvè fizik, asire w ou tcheke estati disk yo (via smartctl -a /dev/XXX) oswa kontwolè RAID pou asire w ke pwoblèm nan pa nan nivo pyès ki nan konpitè. Nan ka mwen an, ATAK la te tounen "pyès ki nan konpitè", kidonk mwen te mande administratè lokal la tcheke estati ATAK la (sèvè a te plizyè santèn kilomèt lwen mwen). Li te di ke pa te gen okenn erè, ki vle di ke nou ka definitivman kòmanse restorasyon.

Eseye 1: zero_damaged_pages

Nou konekte nan baz done a atravè psql ak yon kont ki gen dwa superitilize. Nou bezwen yon superutilisateur, paske... opsyon paj_zewo_domaj sèlman li ka chanje. Nan ka mwen an se postgres:

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

Opsyon paj_zewo_domaj nesesè yo nan lòd yo inyore erè lekti (ki soti nan sit entènèt la postgrespro):

Lè PostgreSQL detekte yon header paj fin pouri, li tipikman rapòte yon erè epi avòte tranzaksyon aktyèl la. Si zero_damaged_pages aktive, sistèm nan pito bay yon avètisman, zewo soti paj ki domaje a nan memwa, epi kontinye pwosesis. Konpòtman sa a detwi done, sètadi tout ranje nan paj ki domaje a.

Nou pèmèt opsyon a epi eseye fè yon vakyòm konplè sou tab yo:

VACUUM FULL VERBOSE

Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)
Malerezman, move chans.

Nou te rankontre yon erè menm jan an:

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_griye – yon mekanis pou estoke "done long" nan Poetgres si li pa anfòm sou yon paj (8kb pa default).

Eseye 2: reindex

Premye konsèy Google pa t ede. Apre kèk minit nan rechèch, mwen jwenn pwent an dezyèm - fè reindex tab domaje. Mwen te wè konsèy sa a anpil kote, men li pa t enspire konfyans. Ann reindex:

reindex table ws_log_smevlog

Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)

reindex fini san pwoblèm.

Sepandan, sa pa t ede, VACUUM PLEN te fè aksidan ak yon erè menm jan an. Depi mwen abitye ak echèk, mwen te kòmanse chèche plis konsèy sou entènèt la ak te vin atravè yon olye enteresan yon atik.

Eseye 3: CHWAZI, LIMIT, OFFSET

Atik ki pi wo a sijere gade tab la ranje pa ranje epi retire done pwoblèm. Premye nou te bezwen gade tout liy yo:

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

Nan ka mwen an, tab la genyen 1 628 991 liy! Li te nesesè yo pran bon swen nan patisyon done, men sa a se yon sijè pou yon diskisyon separe. Se te Samdi, mwen te kouri kòmandman sa a nan tmux epi mwen te ale nan kabann:

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

Nan maten mwen deside tcheke kijan bagay yo t ap pase. Nan sipriz mwen, mwen dekouvri ke apre 20 èdtan, sèlman 2% nan done yo te tcheke! Mwen pa t vle tann 50 jou. Yon lòt echèk konplè.

Men, mwen pa t abandone. Mwen te mande poukisa optik la te pran anpil tan. Soti nan dokiman an (ankò sou postgrespro) mwen te jwenn:

OFFSET espesifye pou sote kantite ranje espesifye anvan yo kòmanse pwodiksyon ranje.
Si yo espesifye tou de OFFSET ak LIMIT, sistèm nan premye sote ranje OFFSET yo epi answit kòmanse konte ranje yo pou kontrent LIMIT la.

Lè w ap itilize LIMIT, li enpòtan tou pou w itilize yon kloz ORDER BY pou ranje rezilta yo retounen nan yon lòd espesifik. Sinon, yo pral retounen sou-ansanm enprevizib nan ranje.

Li evidan, kòmandman ki anwo a te mal: premyèman, pa te gen okenn lòd pa, rezilta a ta ka erè. Dezyèmman, Postgres te premye eskane ak sote ranje OFFSET, ak ogmante DESANTRE pwodiktivite ta diminye menm pi lwen.

Eseye 4: pran yon pil fatra nan fòm tèks

Lè sa a, yon lide w pèdi briyan te vin nan tèt mwen: pran yon pil fatra nan fòm tèks epi analize dènye liy ki anrejistre a.

Men, anvan, kite a pran yon gade nan estrikti a nan tab la. ws_log_smevlog:

Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)

Nan ka nou an nou gen yon kolòn "Id", ki te genyen idantifikasyon inik (kontwa) ranje a. Plan an te tankou sa a:

  1. Nou kòmanse pran yon pil fatra nan fòm tèks (nan fòm kòmandman sql)
  2. Nan yon sèten pwen nan tan, pil fatra a ta dwe entèwonp akòz yon erè, men dosye tèks la ta toujou sove sou disk.
  3. Nou gade nan fen dosye tèks la, kidonk nou jwenn idantifyan (id) dènye liy ki te retire avèk siksè.

Mwen te kòmanse pran yon pil fatra nan fòm tèks:

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

Depotwa a, jan yo espere, te entèwonp ak menm erè a:

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

Pli lwen nan ke Mwen gade nan fen pil fatra a (ke -5 ./my_dump.dump) te dekouvri ke pil fatra a te koupe sou liy lan ak id 186 525. "Se konsa, pwoblèm nan se nan liy lan ak id 186 526, li kase, epi li bezwen yo dwe efase!" - M 'te panse. Men, fè yon rechèch nan baz done a:
«chwazi * nan ws_log_smevlog kote id=186529"Li te tounen soti ke tout bagay te byen ak liy sa a ... Ranje ak endis 186 - 530 tou te travay san pwoblèm. Yon lòt "lide briyan" echwe. Apre sa, mwen te konprann poukisa sa te rive: lè w ap efase ak chanje done ki sòti nan yon tab, yo pa efase fizikman, men yo make kòm "tuples mouri", Lè sa a, vini. otovakyòm epi make liy sa yo kòm efase epi pèmèt liy sa yo dwe reyitilize. Pou konprann, si done yo nan tablo a chanje epi otovakyòm yo aktive, Lè sa a, li pa estoke sekans.

Eseye 5: CHWAZI, SOTI, KOTE id=

Echèk fè nou pi fò. Ou pa ta dwe janm abandone, ou bezwen ale nan fen a epi kwè nan tèt ou ak kapasite ou. Se konsa, mwen deside eseye yon lòt opsyon: jis gade nan tout dosye yo nan baz done a youn pa youn. Lè nou konnen estrikti tab mwen an (gade pi wo a), nou gen yon jaden id ki inik (kle prensipal). Nou gen 1 ranje nan tablo a ak id yo nan lòd, ki vle di nou ka jis ale nan yo youn pa youn:

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

Si yon moun pa konprann, kòmandman an ap travay jan sa a: li analize tab la ranje pa ranje epi voye stdout bay. / dev / nil, men si lòd SELECT la echwe, Lè sa a, tèks erè a enprime (yo voye stderr nan konsole a) epi yo enprime yon liy ki gen erè a (gras a ||, ki vle di ke seleksyon an te gen pwoblèm (kòd la retounen nan lòd la). se pa 0)).

Mwen te gen chans, mwen te gen endis ki te kreye sou teren an id:

Premye eksperyans mwen rekipere yon baz done Postgres apre yon echèk (paj ki pa valab nan blòk 4123007 nan baz relatton/16490)

Sa vle di ke jwenn yon liy ak id vle a pa ta dwe pran anpil tan. Nan teyori li ta dwe travay. Oke, ann kouri lòd la nan tmux epi ann al dòmi.

Nan maten mwen te jwenn ke anviwon 90 antre yo te wè, ki se jis plis pase 000%. Yon rezilta ekselan lè yo konpare ak metòd anvan an (5%)! Men mwen pat vle tann 2 jou...

Eseye 6: SELECT, FROM, WHERE id >= ak id <

Kliyan an te gen yon sèvè ekselan dedye a baz done a: doub-prosesè Intel Xeon E5-2697 v2, te gen otan ke 48 fil nan kote nou an! Chaj la sou sèvè a te mwayèn; nou te kapab telechaje apeprè 20 fil san okenn pwoblèm. Te gen ase RAM tou: otan ke 384 jigokte!

Se poutèt sa, lòd la te bezwen paralelize:

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

Isit la li te posib yo ekri yon script bèl ak elegant, men mwen te chwazi metòd la paralelizasyon pi rapid: manyèlman divize seri a 0-1628991 nan entèval nan 100 dosye epi kouri separeman 000 kòmandman nan fòm lan:

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

Men, sa a se pa tout. Nan teyori, konekte ak yon baz done tou pran kèk tan ak resous sistèm. Konekte 1 pa te trè entelijan, ou pral dakò. Se poutèt sa, ann rekipere 628 ranje olye pou yo youn sou youn koneksyon. Kòm yon rezilta, ekip la te transfòme nan sa a:

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

Louvri 16 fenèt nan yon sesyon tmux epi kouri kòmandman yo:

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

Yon jou apre mwen te resevwa premye rezilta yo! Savwa (valè XXX ak ZZZ yo pa konsève ankò):

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

Sa vle di ke twa liy genyen yon erè. Id yo nan premye ak dezyèm dosye pwoblèm yo te ant 829 ak 000, id yo nan twazyèm lan te ant 830 ak 000. Apre sa, nou tou senpleman te gen pou jwenn valè idantifikasyon egzak dosye pwoblèm yo. Pou fè sa, nou gade nan seri nou an ak dosye pwoblèm ak yon etap nan 146 epi idantifye id la:

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

Kè kontan fini

Nou jwenn liy pwoblèm yo. Nou ale nan baz done a atravè psql epi eseye efase yo:

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

Nan sipriz mwen, antre yo te efase san okenn pwoblèm menm san opsyon a paj_zewo_domaj.

Lè sa a, mwen konekte nan baz done a, te fè VACUUM PLEN (Mwen panse ke li pa t nesesè pou fè sa), epi finalman mwen avèk siksè retire backup la lè l sèvi avèk pg_dump. Depotwa a te pran san okenn erè! Pwoblèm lan te rezoud nan yon fason estipid. Lajwa a pa te konnen limit, apre anpil echèk nou te rive jwenn yon solisyon!

Rekonesans ak Konklizyon

Sa a se ki jan premye eksperyans mwen nan restore yon baz done Postgres reyèl te tounen soti. Mwen pral sonje eksperyans sa a pou yon tan long.

Epi finalman, mwen ta renmen di PostgresPro mèsi pou tradwi dokiman an nan Ris ak pou kou sou entènèt konplètman gratis, ki te ede anpil pandan analiz pwoblèm nan.

Sous: www.habr.com

Add nouvo kòmantè