My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)

Ek wil my eerste suksesvolle ervaring in die herstel van die volle funksionaliteit van 'n Postgres-databasis met jou deel. Ek het 'n halfjaar gelede kennis gemaak met die Postgres DBMS, voor dit het ek glad nie ervaring in databasisadministrasie gehad nie.

My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)

Ek werk as 'n semi-DevOps-ingenieur by 'n groot IT-maatskappy. Ons maatskappy ontwikkel sagteware vir hoëladingsdienste, terwyl ek verantwoordelik is vir werkverrigting, instandhouding en ontplooiing. Ek het 'n standaardtaak gekry: om die toepassing op een bediener op te dateer. Die toepassing is in Django geskryf, tydens die opdatering word migrasies uitgevoer (die databasisstruktuur verander), en voor hierdie proses neem ons 'n volledige databasisstorting deur die standaard pg_dump-program net vir ingeval.

'n Onverwagte fout het voorgekom tydens storting (Postgres-weergawe is 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

Bug "ongeldige bladsy in blok" praat van probleme op die lêerstelselvlak, wat baie erg is. Verskeie forums het voorgestel om te doen VOLLE VAKUUM met opsie nul_beskadigde_bladsye om hierdie probleem op te los. Wel, raai wat …

Voorbereiding vir herstel

WAARSKUWING! Maak seker dat u Postgres rugsteun voor enige poging om die databasis te herstel. As jy 'n virtuele masjien het, stop die databasis en neem 'n momentopname. As jy nie 'n momentopname kan neem nie, stop die databasis en kopieer die inhoud van die Postgres-gids (insluitend die wal-lêers) na 'n veilige plek. Die belangrikste ding in ons besigheid is om dinge nie te vererger nie. Lees hierdie.

Aangesien die databasis oor die algemeen vir my gewerk het, het ek myself beperk tot die gewone databasisstorting, maar die tabel met korrupte data uitgesluit (opsie -T, --exclude-table=TABEL in pg_dump).

Die bediener was fisies, dit was onmoontlik om 'n momentopname te neem. Die rugsteun is verwyder, ons gaan aan.

Lêerstelselkontrole

Voordat ons probeer om die databasis te herstel, moet ons seker maak dat alles in orde is met die lêerstelsel self. En in geval van foute, maak dit reg, want anders kan jy dinge net vererger.

In my geval is die lêerstelsel met die databasis gemonteer "/srv" en die tipe was ext4.

Stop die databasis: systemctl stop [e-pos beskerm] en maak seker dat die lêerstelsel nie deur enigiemand gebruik word nie en met behulp van die opdrag ontkoppel kan word lsof:
lsof +D /srv

Ek moes ook die redis-databasis stop, aangesien dit ook gebruik het "/srv". Volgende het ek afgeklim / srv (bedrag).

Die lêerstelselkontrole is uitgevoer met behulp van die hulpprogram e2fsck met die -f skakelaar (Dwing nagaan selfs al is lêerstelsel skoon gemerk):

My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)

Gebruik dan die hulpprogram dumpe2fs (sudo dumpe2fs /dev/mapper/gu2-sys-srv | grep nagegaan) kan jy seker maak dat die kontrole werklik gemaak is:

My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)

e2fsck sê dat geen probleme op die ext4-lêerstelselvlak gevind is nie, wat beteken dat jy kan voortgaan om die databasis te probeer herstel, of eerder terugkeer na vakuum vol (natuurlik moet jy die lêerstelsel terugkoppel en die databasis begin).

As jy 'n fisiese bediener het, maak seker dat jy die status van die skywe (via smartctl -a /dev/XXX) of die RAID-beheerder om seker te maak dat die probleem nie op die hardeware-vlak is nie. In my geval het die RAID geblyk "yster" te wees, so ek het die plaaslike admin gevra om die status van die RAID na te gaan (die bediener was etlike honderde kilometers van my af). Hy het gesê daar was geen foute nie, wat beteken dat ons beslis met die herstel kan begin.

Poging 1: zero_damaged_pages

Ons koppel aan die databasis deur psql met 'n rekening wat supergebruikersregte het. Ons het die supergebruiker nodig, want. opsie nul_beskadigde_bladsye net hy kan verander. In my geval is dit postgres:

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

opsie nul_beskadigde_bladsye nodig om leesfoute te ignoreer (van die postgrespro-webwerf):

Wanneer 'n bladsyopskrif beskadig is, sal PostgreSQL gewoonlik 'n fout rapporteer en die huidige transaksie staak. As zero_damaged_pages geaktiveer is, reik die stelsel eerder 'n waarskuwing uit, nul die beskadigde bladsy in die geheue uit en gaan voort met verwerking. Hierdie gedrag vernietig die data, naamlik alle lyne in die beskadigde bladsy.

Aktiveer die opsie en probeer om volle vakuumtabelle te doen:

VACUUM FULL VERBOSE

My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)
Ongelukkig mislukking.

Ons het 'n soortgelyke fout teëgekom:

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

bl_roosterbrood - die meganisme vir die stoor van "lang data" in Poetgres, as hulle nie op een bladsy pas nie (verstek 8kb).

Poging 2: herindeks

Die eerste raad van Google het nie gehelp nie. Na 'n paar minute se soektog het ek die tweede wenk gekry - om te doen herindekseer beskadigde tafel. Ek het hierdie raad op baie plekke gesien, maar dit het nie vertroue aangewakker nie. Kom ons maak 'n herindeks:

reindex table ws_log_smevlog

My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)

herindekseer sonder probleme voltooi.

Dit het egter nie gehelp nie. VAKUUM VOL het met dieselfde fout neergestort. Aangesien ek gewoond is aan mislukking, het ek begin om raad op die internet verder te soek en op 'n nogal interessante afgekom 'n artikel.

Poging 3: KIES, LIMIT, OFFSET

In die artikel hierbo is voorgestel om reël vir reël na die tabel te kyk en die problematiese data uit te vee. Om mee te begin, was dit nodig om al die lyne te sien:

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

In my geval het die tabel bevat 1 628 991 lyne! Op 'n goeie manier was dit nodig om te sorg data partisionering, maar dit is 'n onderwerp vir 'n aparte bespreking. Dit was Saterdag, ek het hierdie opdrag in tmux uitgevoer en gaan slaap:

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

Teen die oggend het ek besluit om te kyk hoe dit gaan. Tot my verbasing het ek gevind dat slegs 20% van die data binne 2 uur gekruip is! Ek wou nie 50 dae wag nie. Nog 'n algehele mislukking.

Maar ek het nie moed opgegee nie. Ek het gewonder hoekom die skandering so lank geneem het. Uit die dokumentasie (weer op postgrespro) het ek geleer:

OFFSET vertel dit om die gespesifiseerde aantal lyne oor te slaan voordat daar begin word om lyne uit te voer.
As beide OFFSET en LIMIT gespesifiseer word, slaan die stelsel eers OFFSET-rye oor en begin dan om rye vir die LIMIT-limiet te tel.

Wanneer LIMIT gebruik word, is dit belangrik om ook die ORDER BY-klousule te gebruik sodat die resultaatrye in 'n bepaalde volgorde teruggestuur word. Andersins sal onvoorspelbare substelle rye teruggestuur word.

Dit is duidelik dat bogenoemde opdrag foutief was: eerstens was daar geen bestel deur, kan die resultaat verkeerd wees. Tweedens, Postgres moes eers OFFSET-rye skandeer en oorslaan, en inkrementeel OFFSET prestasie nog verder sou daal.

Poging 4: stort in teksvorm

Toe kom 'n skynbaar briljante idee by my op: om 'n storting in teksvorm te vat en die laaste geskrewe reël te ontleed.

Maar laat ons eers kennis maak met die struktuur van die tafel ws_log_smevlog:

My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)

In ons geval het ons 'n rubriek "Id", wat die unieke identifiseerder (teller) van die ry bevat het. Die plan was soos volg:

  1. Ons begin om 'n storting in teksvorm te neem (in die vorm van sql-opdragte)
  2. Op 'n sekere tydstip sal die storting weens 'n fout onderbreek word, maar die tekslêer sal steeds op skyf gestoor word
  3. Ons kyk na die einde van die tekslêer, daardeur vind ons die identifiseerder (id) van die laaste reël wat suksesvol verwyder is

Ek het in teksvorm begin stort:

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

Storting, soos verwag, is met dieselfde fout afgelas:

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

Kom deur stert Ek het na die einde van die storting gekyk (stert -5 ./my_dump.dump) gevind dat die storting op die lyn met id 186 525. "So die probleem is in lyn met id 186 526, dit is stukkend, en dit moet uitgevee word!" Ek dink. Maar deur 'n navraag na die databasis te maak:
«kies * uit ws_log_smevlog waar id=186529” dit het geblyk dat alles reg was met hierdie lyn ... Lyne met indekse 186 530 - 186 540 het ook sonder probleme gewerk. Nog 'n "briljante idee" het misluk. Later het ek verstaan ​​hoekom dit gebeur het: wanneer data uit die tabel uitgevee en verander word, word dit nie fisies uitgevee nie, maar gemerk as "dooie tuples", dan kom outovakuum en merk hierdie reëls as geskrap en laat toe dat hierdie reëls hergebruik word. Vir begrip, as die data in die tabel verander en outovakuum is geaktiveer, dan word hulle nie opeenvolgend gestoor nie.

Poging 5: SELECT, FROM, WHERE id=

Mislukking maak ons ​​sterker. Jy moet nooit moed opgee nie, jy moet tot die einde toe gaan en in jouself en jou vermoëns glo. Ek het dus besluit om 'n ander opsie te probeer: kyk net een vir een na al die rekords in die databasis. As ons my tabelstruktuur ken (sien hierbo), het ons 'n ID-veld wat uniek is (primêre sleutel). Ons het 1 628 991 rye in die tabel en id is in orde, wat beteken dat ons net een op 'n slag oor hulle kan herhaal:

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

As iemand nie verstaan ​​nie, werk die opdrag soos volg: dit skandeer die tabel reël vir reël en stuur stdout na / Dev / null, maar as die SELECT-opdrag misluk, word 'n foutteks gedruk (stderr word na die konsole gestuur) en 'n reël wat die fout bevat word gedruk (danksy ||, wat beteken dat die kies probleme gehad het (die terugkeerkode van die opdrag). is nie 0)).

Ek was gelukkig, ek het indekse op die veld geskep id:

My eerste postgres databasis ongeluk herstel ervaring (ongeldige bladsy in blok 4123007 van Relatton base/16490)

En dit beteken dat die vind van 'n lyn met die verlangde ID nie veel tyd moet neem nie. In teorie behoort dit te werk. Wel, kom ons voer die opdrag in tmux en ons gaan slaap.

Teen die oggend het ek gevind dat daar ongeveer 90 000 inskrywings bekyk is, wat net meer as 5% is. Uitstekende resultaat in vergelyking met die vorige metode (2%)! Maar ek wou nie 20 dae wag nie...

Poging 6: SELECT, FROM, WHERE id >= en id

Die kliënt het 'n uitstekende bediener vir die databasis toegeken: 'n twee-verwerker Intel Xeon E5-2697 v2, daar was soveel as 48 drade in ons reëling! Die las op die bediener was gemiddeld, ons kon ongeveer 20 strome optel sonder enige probleme. RAM was ook genoeg: soveel as 384 gigagrepe!

Daarom moes die opdrag parallel gemaak word:

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

Dit was moontlik om 'n pragtige en elegante skrif hier te skryf, maar ek het die vinnigste manier gekies om te paralleliseer: verdeel die reeks 0-1628991 handmatig in intervalle van 100 000 rekords en voer afsonderlik 16 opdragte van die vorm uit:

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

Maar dit is nie al nie. In teorie neem dit ook tyd en stelselhulpbronne om aan die databasis te koppel. Dit was nie baie redelik om 1 628 991 te koppel nie, sien jy. So kom ons haal 1000 rye op een verbinding in plaas van een. As gevolg hiervan het die opdrag in hierdie verander:

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

Maak 16 vensters in 'n tmux-sessie oop en voer die opdragte uit:

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

'n Dag later het ek die eerste resultate gekry! Naamlik (die waardes van XXX en ZZZ word nie meer bewaar nie):

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

Dit beteken dat ons drie reëls het wat 'n fout bevat. Die id van die eerste en tweede problematiese rekords was tussen 829 000 en 830 000, die id van die derde was tussen 146 000 en 147 000. Vervolgens moes ons net die presiese waarde van die id van die problematiese rekords vind. Om dit te doen, kyk ons ​​na ons reeks met problematiese rekords met 'n stap van 1 en identifiseer die 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

Gelukkige einde

Ons het die probleemlyne gevind. Ons gaan na die databasis deur psql en probeer om dit uit te vee:

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

Tot my verbasing is die inskrywings sonder enige probleme uitgevee, selfs sonder die opsie nul_beskadigde_bladsye.

Toe het ek aan die basis gekoppel, gedoen VAKUUM VOL (Ek dink dit was opsioneel), en uiteindelik die rugsteun suksesvol verwyder met behulp van bl_dump. Die storting het sonder enige foute opgestyg! Die probleem is op so 'n dom manier opgelos. Joy het geen perke geken nie, na soveel mislukkings het ons daarin geslaag om 'n oplossing te vind!

Erkennings en gevolgtrekking

Dit is my eerste ervaring met die herstel van 'n regte Postgres-databasis. Ek sal hierdie ervaring nog lank onthou.

En laastens wil ek graag dankie sê aan PostgresPro vir die vertaalde dokumentasie in Russies en vir heeltemal gratis aanlyn kursusse, wat baie gehelp het tydens die ontleding van die probleem.

Bron: will.com

Voeg 'n opmerking