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.
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):
Seuraavaksi apuohjelman käyttö dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep tarkistettu) voit varmistaa, että tarkistus todella suoritettiin:
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
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
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
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
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
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:
Meidän tapauksessamme meillä on sarake "Id", joka sisälsi rivin yksilöllisen tunnisteen (laskurin). Suunnitelma oli tällainen:
- Aloitamme vedosten tekemisen tekstimuodossa (sql-komentojen muodossa)
- Tietyllä hetkellä dump keskeytyy virheen vuoksi, mutta tekstitiedosto tallentuisi silti levylle
- 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:
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ä
Lähde: will.com