Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)

Haluan jakaa kanssasi ensimmäisen onnistuneen kokemukseni Postgres-tietokannan palauttamisesta täysimääräiseksi. Tutustuin Postgres DBMS:ään puoli vuotta sitten, sitä ennen minulla ei ollut kokemusta tietokannan hallinnasta.

Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)

Työskentelen puoliksi DevOps-insinöörinä suuressa IT-yrityksessä. Yrityksemme kehittää ohjelmistoja korkean kuormituksen palveluihin, ja olen vastuussa suorituskyvystä, ylläpidosta ja käyttöönotosta. Sain vakiotehtävän: päivittää sovellus yhdellä palvelimella. Sovellus on kirjoitettu Django-kielellä, päivityksen aikana suoritetaan migraatioita (muutoksia tietokantarakenteessa), ja ennen tätä prosessia teemme täyden tietokannan dumpin tavallisen pg_dump-ohjelman kautta varmuuden vuoksi.

Odottamaton virhe tapahtui vedosta otettaessa (Postgres-versio 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

Virhe "virheellinen sivu lohkossa" puhuu ongelmista tiedostojärjestelmätasolla, mikä on erittäin huono. Eri foorumeilla sitä ehdotettiin TÄYDELLINEN TYHJYYS vaihtoehdon kanssa nolla_vaurioituneita_sivuja tämän ongelman ratkaisemiseksi. No yritetään...

Valmistautuminen palautumiseen

VAROITUS Muista ottaa Postgres-varmuuskopio ennen kuin yrität palauttaa tietokantasi. Jos sinulla on virtuaalikone, pysäytä tietokanta ja ota tilannekuva. Jos tilannekuvan ottaminen ei ole mahdollista, pysäytä tietokanta ja kopioi Postgres-hakemiston sisältö (mukaan lukien wal-tiedostot) turvalliseen paikkaan. Pääasia liiketoiminnassamme ei ole pahentaa asioita. Lukea это.

Koska tietokanta yleensä toimi minulle, rajoitin tavalliseen tietokannan vedokseen, mutta jätin pois taulukon, jossa oli vaurioituneita tietoja (vaihtoehto -T, --exclude-table=TAULUKKO kohdassa pg_dump).

Palvelin oli fyysinen, siitä oli mahdotonta ottaa tilannekuvaa. Varmuuskopio on poistettu, jatketaan.

Tiedostojärjestelmän tarkistus

Ennen kuin yritämme palauttaa tietokannan, meidän on varmistettava, että kaikki on kunnossa itse tiedostojärjestelmässä. Ja jos on virheitä, korjaa ne, koska muuten voit vain pahentaa asioita.

Minun tapauksessani tiedostojärjestelmä tietokannan kanssa oli asennettu "/srv" ja tyyppi oli ext4.

Tietokannan pysäyttäminen: systemctl pysäytys [sähköposti suojattu] ja tarkista, että tiedostojärjestelmä ei ole kenenkään käytössä ja että se voidaan purkaa komennolla lof:
lsof +D /srv

Jouduin myös lopettamaan redis-tietokannan, koska sekin käytti "/srv". Seuraavaksi otin irti / srv (määrä).

Tiedostojärjestelmä tarkistettiin apuohjelmalla e2fsck kytkimellä -f (Pakota tarkistus, vaikka tiedostojärjestelmä on merkitty puhtaaksi):

Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)

Seuraavaksi apuohjelman käyttö dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep tarkistettu) voit varmistaa, että tarkistus todella suoritettiin:

Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)

e2fsck sanoo, että ongelmia ei löytynyt ext4-tiedostojärjestelmätasolla, mikä tarkoittaa, että voit jatkaa tietokannan palauttamista tai palata takaisin tyhjiö täynnä (Tietenkin sinun täytyy liittää tiedostojärjestelmä takaisin ja käynnistää tietokanta).

Jos sinulla on fyysinen palvelin, muista tarkistaa levyjen tila (välillä smartctl -a /dev/XXX) tai RAID-ohjainta varmistaaksesi, että ongelma ei ole laitteistotasolla. Minun tapauksessani RAID osoittautui "laitteistoksi", joten pyysin paikallista järjestelmänvalvojaa tarkistamaan RAIDin tilan (palvelin oli useiden satojen kilometrien päässä minusta). Hän sanoi, että virheitä ei ollut, mikä tarkoittaa, että voimme ehdottomasti aloittaa kunnostuksen.

Yritys 1: nolla_vaurioituneita_sivuja

Yhdistämme tietokantaan psql:n kautta tilillä, jolla on superkäyttäjäoikeudet. Tarvitsemme superkäyttäjän, koska... vaihtoehto nolla_vaurioituneita_sivuja vain hän voi muuttua. Minun tapauksessani se on postgres:

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

Vaihtoehto nolla_vaurioituneita_sivuja tarvitaan lukuvirheiden huomiotta jättämiseen (postgrespro-verkkosivustolta):

Kun PostgreSQL havaitsee vioittuneen sivun otsikon, se yleensä raportoi virheestä ja keskeyttää nykyisen tapahtuman. Jos zero_vaurioged_pages on käytössä, järjestelmä antaa sen sijaan varoituksen, nollaa vahingoittuneen sivun muistista ja jatkaa käsittelyä. Tämä toiminta tuhoaa tiedot, nimittäin kaikki vaurioituneen sivun rivit.

Otamme vaihtoehdon käyttöön ja yritämme tehdä pöydistä täyden tyhjiön:

VACUUM FULL VERBOSE

Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)
Valitettavasti huonoa tuuria.

Havaitsimme samanlaisen virheen:

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 – mekanismi "pitkien tietojen" tallentamiseksi Poetgresiin, jos se ei mahdu yhdelle sivulle (oletusarvoisesti 8 kt).

Yritys 2: Indeksoi uudelleen

Googlen ensimmäinen neuvo ei auttanut. Muutaman minuutin etsimisen jälkeen löysin toisen vihjeen - tehdä indeksoida uudelleen vaurioitunut pöytä. Näin tämän neuvon monissa paikoissa, mutta se ei herättänyt luottamusta. Indeksoidaan uudelleen:

reindex table ws_log_smevlog

Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)

indeksoida uudelleen suoritettu ilman ongelmia.

Tämä ei kuitenkaan auttanut, MUMI TÄYNNÄ kaatui samanlaisella virheellä. Koska olen tottunut epäonnistumisiin, aloin etsiä lisää neuvoja Internetistä ja törmäsin melko mielenkiintoiseen Artikkeli.

Yritys 3: SELECT, LIMIT, OFFSET

Yllä olevassa artikkelissa ehdotettiin taulukon tarkastelua rivi riviltä ja ongelmallisten tietojen poistamista. Ensin meidän piti katsoa kaikki rivit:

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

Minun tapauksessani taulukko sisälsi 1 628 991 rivit! Siitä piti pitää hyvää huolta tietojen osiointi, mutta tämä on erillisen keskustelun aihe. Oli lauantai, suoritin tämän komennon tmuxissa ja menin nukkumaan:

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

Aamulla päätin tarkistaa, miten asiat etenevät. Yllätyksekseni huomasin, että 20 tunnin jälkeen vain 2 % tiedoista oli skannattu! En halunnut odottaa 50 päivää. Toinen täydellinen epäonnistuminen.

Mutta en luovuttanut. Mietin, miksi skannaus kesti niin kauan. Asiakirjoista (jälleen postgresprossa) sain selville:

OFFSET määrittää, että määritetty määrä rivejä ohitetaan ennen rivien tulostamista.
Jos sekä OFFSET että LIMIT on määritetty, järjestelmä ohittaa ensin OFFSET-rivit ja alkaa sitten laskea rivejä LIMIT-rajoitetta varten.

LIMITiä käytettäessä on tärkeää käyttää myös ORDER BY -lausetta, jotta tulosrivit palautetaan tietyssä järjestyksessä. Muussa tapauksessa palautetaan odottamattomia rivien osajoukkoja.

Ilmeisesti yllä oleva komento oli väärä: ensinnäkin ei ollut tilaaja, tulos voi olla virheellinen. Toiseksi, Postgresin täytyi ensin skannata ja ohittaa OFFSET-rivejä, ja kasvaessa OFFSET tuottavuus heikkenee entisestään.

Yritys 4: tee vedos tekstimuodossa

Sitten mieleeni tuli loistavan näköinen idea: ota tekstimuotoinen kaatopaikka ja analysoi viimeinen tallennettu rivi.

Mutta ensin katsotaan taulukon rakennetta. ws_log_smevlog:

Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)

Meidän tapauksessamme meillä on sarake "Id", joka sisälsi rivin yksilöllisen tunnisteen (laskurin). Suunnitelma oli tällainen:

  1. Aloitamme vedosten tekemisen tekstimuodossa (sql-komentojen muodossa)
  2. Tietyllä hetkellä dump keskeytyy virheen vuoksi, mutta tekstitiedosto tallentuisi silti levylle
  3. Katsomme tekstitiedoston lopun, jolloin löydämme viimeisen onnistuneesti poistetun rivin tunnisteen (id).

Aloin tehdä tyhjennystä tekstimuodossa:

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

Dump, kuten odotettiin, keskeytettiin samalla virheellä:

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

Kauempana pyrstö Katsoin kaatopaikan päätä (häntä -5 ./my_dump.dump) havaitsi, että kaato oli keskeytetty linjalla, jolla on id 186 525. "Joten ongelma on tunnuksella 186 526, se on rikki ja se on poistettava!" - Ajattelin. Mutta kyselyn tekeminen tietokantaan:
«valitse * ws_log_smevlogista, jossa id=186529"Kävi ilmi, että kaikki oli kunnossa tällä rivillä... Myös rivit indekseillä 186 530 - 186 540 toimivat ongelmitta. Toinen "loistava idea" epäonnistui. Myöhemmin ymmärsin miksi näin tapahtui: kun taulukosta poistetaan ja muutetaan tietoja, niitä ei fyysisesti poisteta, vaan ne merkitään "kuolleiksi tupeleiksi", sitten tulee automaattinen tyhjiö ja merkitsee nämä rivit poistetuiksi ja sallii näiden rivien uudelleenkäytön. Ymmärtääksemme, jos taulukon tiedot muuttuvat ja automaattinen tyhjiö on käytössä, niitä ei tallenneta peräkkäin.

Yritys 5: SELECT, FROM, WHERE id=

Epäonnistumiset tekevät meistä vahvempia. Älä koskaan anna periksi, sinun on mentävä loppuun asti ja uskottava itseesi ja kykyihisi. Joten päätin kokeilla toista vaihtoehtoa: vain katsoa kaikki tietokannan tietueet yksitellen läpi. Kun tiedämme taulukoni rakenteen (katso yllä), meillä on id-kenttä, joka on ainutlaatuinen (ensisijainen avain). Meillä on 1 628 991 riviä taulukossa ja id ovat kunnossa, mikä tarkoittaa, että voimme käydä ne läpi yksitellen:

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

Jos joku ei ymmärrä, komento toimii seuraavasti: se skannaa taulukon rivi riviltä ja lähettää stdout / Dev / null, mutta jos SELECT-komento epäonnistuu, tulostetaan virheteksti (stderr lähetetään konsoliin) ja tulostetaan virheen sisältävä rivi (||:n ansiosta, mikä tarkoittaa, että valinnassa oli ongelmia (komennon palautuskoodi). ei ole 0)).

Olin onnekas, minulla oli indeksit luotu kentälle id:

Ensimmäinen kokemukseni Postgres-tietokannan palauttamisesta vian jälkeen (virheellinen sivu relatton base/4123007:n lohkossa 16490)

Tämä tarkoittaa, että rivin löytäminen halutulla tunnuksella ei vie paljon aikaa. Teoriassa sen pitäisi toimia. No, ajetaan komento sisään TMUX: illa ja mennään nukkumaan.

Huomasin aamuun mennessä, että noin 90 000 merkintää oli katsottu, mikä on hieman yli 5 %. Erinomainen tulos verrattuna edelliseen menetelmään (2%)! Mutta en halunnut odottaa 20 päivää...

Yritys 6: SELECT, FROM, WHERE id >= ja id

Asiakkaalla oli erinomainen tietokannalle omistettu palvelin: kaksiprosessori Intel Xeon E5-2697 v2, sijainnissamme oli peräti 48 säiettä! Palvelimen kuormitus oli keskimääräistä; pystyimme lataamaan noin 20 säiettä ilman ongelmia. Myös RAM-muistia riitti: peräti 384 gigatavua!

Siksi komento oli rinnastettava:

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

Täällä oli mahdollista kirjoittaa kaunis ja tyylikäs kirjoitus, mutta valitsin nopeimman rinnakkaismenetelmän: jaa manuaalisesti alue 0-1628991 100 000 tietueen välein ja suorita erikseen 16 lomakkeen komentoa:

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

Mutta siinä ei vielä kaikki. Teoriassa yhteyden muodostaminen tietokantaan vie myös jonkin verran aikaa ja järjestelmäresursseja. Yhdistäminen 1 628 991 ei ollut kovin fiksua, olet samaa mieltä. Haetaan siis 1000 riviä yhden yhteyden sijaan. Tämän seurauksena joukkue muuttui seuraavaksi:

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

Avaa 16 ikkunaa tmux-istunnossa ja suorita komennot:

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

Päivää myöhemmin sain ensimmäiset tulokset! Nimittäin (arvoja XXX ja ZZZ ei enää säilytetä):

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

Tämä tarkoittaa, että kolmella rivillä on virhe. Ensimmäisen ja toisen ongelmallisen tietueen id:t olivat välillä 829 000 - 830 000, kolmannen tunnukset 146 000 - 147 000. Seuraavaksi piti vain löytää ongelmallisten tietueiden tarkka id-arvo. Tätä varten tarkastelemme ongelmallisia tietueita, joiden vaihe on 1, ja tunnistamme tunnuksen:

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

Hyvää loppua

Löysimme ongelmalliset linjat. Menemme tietokantaan psql:n kautta ja yritämme poistaa ne:

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

Yllätyksekseni merkinnät poistettiin ilman ongelmia jopa ilman vaihtoehtoa nolla_vaurioituneita_sivuja.

Sitten liityin tietokantaan, tein MUMI TÄYNNÄ (Mielestäni tätä ei tarvinnut tehdä), ja lopulta poistin varmuuskopion onnistuneesti käyttämällä pg_dump. Kaatopaikka otettiin ilman virheitä! Ongelma ratkesi niin typerällä tavalla. Ilolla ei ollut rajoja, niin monen epäonnistumisen jälkeen onnistuimme löytämään ratkaisun!

Kiitokset ja johtopäätökset

Näin kävi ensimmäinen kokemukseni todellisen Postgres-tietokannan palauttamisesta. Muistan tämän kokemuksen pitkään.

Ja lopuksi haluan kiittää PostgresProa dokumentaation kääntämisestä venäjäksi ja siitä täysin ilmaisia ​​verkkokursseja, joka auttoi paljon ongelman analysoinnissa.

Lähde: will.com

Lisää kommentti