Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)

Postgres verilənlər bazasını tam funksional vəziyyətinə qaytarmaq üzrə ilk uğurlu təcrübəmi sizinlə bölüşmək istərdim. Postgres DBMS ilə yarım il əvvəl tanış olmuşam, bundan əvvəl verilənlər bazası idarəçiliyində ümumiyyətlə təcrübəm yox idi.

Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)

Mən böyük bir İT şirkətində yarı DevOps mühəndisi kimi işləyirəm. Şirkətimiz yüksək yüklü xidmətlər üçün proqram təminatı hazırlayır və mən performans, texniki xidmət və yerləşdirməyə cavabdehəm. Mənə standart tapşırıq verildi: bir serverdə tətbiqi yeniləmək. Tətbiq Django-da yazılmışdır, yeniləmə zamanı miqrasiyalar həyata keçirilir (verilənlər bazası strukturunda dəyişikliklər) və bu prosesdən əvvəl biz hər ehtimala qarşı standart pg_dump proqramı vasitəsilə tam verilənlər bazası zibilini götürürük.

Zibil götürərkən gözlənilməz xəta baş verdi (Postgres versiyası 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

Səhv "blokda yanlış səhifə" fayl sistemi səviyyəsində problemlərdən danışır, bu çox pisdir. Müxtəlif forumlarda bunu etmək təklif edildi TAM VAKUM seçimi ilə sıfır_zədəli_səhifələr bu problemi həll etmək üçün. Yaxşı, cəhd edək...

Bərpaya hazırlıq

DİQQƏT Verilənlər bazanızı bərpa etməyə cəhd etməzdən əvvəl Postgres ehtiyat nüsxəsini götürməyinizə əmin olun. Əgər virtual maşınınız varsa, verilənlər bazasını dayandırın və şəkil çəkin. Snapshot çəkmək mümkün deyilsə, verilənlər bazasını dayandırın və Postgres kataloqunun məzmununu (wal faylları daxil olmaqla) təhlükəsiz yerə köçürün. Bizim işimizdə əsas odur ki, vəziyyəti pisləşdirməyək. Oxuyun bu.

Verilənlər bazası ümumiyyətlə mənim üçün işlədiyi üçün özümü adi verilənlər bazası zibilliyi ilə məhdudlaşdırdım, lakin zədələnmiş məlumatları olan cədvəli istisna etdim (seçim). -T, --exclude-table=CƏDVƏL pg_dump-da).

Server fiziki idi, şəkil çəkmək mümkün deyildi. Yedək silindi, davam edək.

Fayl sisteminin yoxlanılması

Verilənlər bazasını bərpa etməyə cəhd etməzdən əvvəl, hər şeyin fayl sisteminin özü ilə uyğun olduğundan əmin olmalıyıq. Səhvlər halında, onları düzəldin, çünki əks halda yalnız vəziyyəti daha da pisləşdirə bilərsiniz.

Mənim vəziyyətimdə verilənlər bazası olan fayl sistemi quraşdırılmışdır "/srv" və növü ext4 idi.

Verilənlər bazasının dayandırılması: systemctl dayandırın [e-poçt qorunur] və fayl sisteminin heç kim tərəfindən istifadə edilmədiyini və əmrdən istifadə edərək sökülə biləcəyini yoxlayın lap:
lsof +D /srv

Mən də redis verilənlər bazasını dayandırmalı oldum, çünki o da istifadə edirdi "/srv". Sonra mən sökdüm / srv (miqdar).

Fayl sistemi yardım proqramı ilə yoxlanıldı e2fsck açarı ilə -f (Fayl sistemi təmiz olaraq qeyd olunsa belə, yoxlamanı məcbur edin):

Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)

Sonra, yardım proqramından istifadə edin dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep yoxlanılır) yoxlamanın həqiqətən həyata keçirildiyini yoxlaya bilərsiniz:

Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)

e2fsck ext4 fayl sistemi səviyyəsində heç bir problem tapılmadığını deyir, bu o deməkdir ki, verilənlər bazasını bərpa etməyə davam edə bilərsiniz, daha doğrusu geri qayıda bilərsiniz. vakuum dolu (əlbəttə ki, fayl sistemini yenidən quraşdırmalı və verilənlər bazasını işə salmalısınız).

Fiziki serveriniz varsa, disklərin vəziyyətini yoxladığınızdan əmin olun (vasitəsilə smartctl -a /dev/XXX) və ya RAID nəzarətçi ilə problemin hardware səviyyəsində olmadığına əmin olun. Mənim vəziyyətimdə RAID "hardware" oldu, ona görə də yerli admindən RAID-in vəziyyətini yoxlamağı xahiş etdim (server məndən bir neçə yüz kilometr uzaqda idi). O bildirib ki, heç bir səhv yoxdur, bu o deməkdir ki, biz mütləq bərpaya başlaya bilərik.

Cəhd 1: sıfır_damaged_pages

Superuser hüquqlarına malik olan hesabla psql vasitəsilə verilənlər bazasına qoşuluruq. Bizə super istifadəçi lazımdır, çünki... seçim sıfır_zədəli_səhifələr yalnız o dəyişə bilər. Mənim vəziyyətimdə bu postgresdir:

psql -h 127.0.0.1 -U postgres -s [verilənlər bazası_adı]

Seçim sıfır_zədəli_səhifələr oxu səhvlərini görməməzlikdən gəlmək üçün lazımdır (postgrespro saytından):

PostgreSQL zədələnmiş səhifə başlığını aşkar etdikdə, adətən xəta haqqında məlumat verir və cari əməliyyatı dayandırır. Əgər zero_damaged_pages aktivləşdirilibsə, sistem bunun əvəzinə xəbərdarlıq verir, yaddaşdakı zədələnmiş səhifəni sıfırlayır və işləməyə davam edir. Bu davranış məlumatları, yəni zədələnmiş səhifədəki bütün sətirləri məhv edir.

Seçimi aktivləşdiririk və cədvəllərin tam vakuumunu etməyə çalışırıq:

VACUUM FULL VERBOSE

Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)
Təəssüf ki, uğursuzluq.

Bənzər bir xəta ilə qarşılaşdıq:

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 – bir səhifəyə uyğun gəlmirsə, Poetgres-də “uzun məlumatların” saxlanması mexanizmi (standart olaraq 8kb).

2-ci cəhd: yenidən indeksləşdirin

Google-un ilk məsləhəti kömək etmədi. Bir neçə dəqiqəlik axtarışdan sonra ikinci ipucu tapdım - etmək reindeks zədələnmiş masa. Mən bu məsləhəti bir çox yerdə görmüşəm, amma inam yaratmayıb. Yenidən indeksləşdirək:

reindex table ws_log_smevlog

Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)

reindeks problemsiz tamamlandı.

Ancaq bu kömək etmədi, VAKUM DOLU oxşar xəta ilə qəzaya uğradı. Uğursuzluqlara öyrəşdiyim üçün İnternetdə məsləhətlər axtarmağa başladım və olduqca maraqlı bir şeylə qarşılaşdım Məqalə.

Cəhd 3: SEÇ, LİMİT, OFFSET

Yuxarıdakı məqalə cədvələ sətir-sətir baxmağı və problemli məlumatları silməyi təklif etdi. Əvvəlcə bütün sətirlərə baxmalı olduq:

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

Mənim vəziyyətimdə cədvəl var 1 628 991 xətlər! Yaxşı qulluq etmək lazımdı məlumatların bölünməsi, lakin bu ayrı bir müzakirə mövzusudur. Şənbə günü idi, mən bu əmri tmux-da icra etdim və yatağa getdim:

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

Səhər işlərin necə getdiyini yoxlamaq qərarına gəldim. Təəccübləndim ki, 20 saatdan sonra məlumatların yalnız 2%-i skan edilib! 50 gün gözləmək istəmirdim. Başqa bir tam uğursuzluq.

Amma mən təslim olmadım. Skanlamanın niyə bu qədər uzun çəkdiyini düşündüm. Sənədlərdən (yenə postgrespro-da) öyrəndim:

OFFSET, sətirləri çıxarmağa başlamazdan əvvəl müəyyən edilmiş sətir sayını atlamağı təyin edir.
Həm OFFSET, həm də LIMIT təyin olunarsa, sistem əvvəlcə OFFSET sətirlərini atlayır və sonra LIMIT məhdudiyyəti üçün sətirləri saymağa başlayır.

LIMIT istifadə edərkən, ORDER BY bəndindən də istifadə etmək vacibdir ki, nəticə sətirləri müəyyən qaydada qaytarılsın. Əks halda, gözlənilməz sıra alt çoxluqları qaytarılacaq.

Aydındır ki, yuxarıdakı əmr səhv idi: birincisi, yox idi tərəfindən sifariş, nəticə səhv ola bilər. İkincisi, Postgres əvvəlcə skan etməli və OFFSET sətirlərini atlamalı idi və artır OFFSET məhsuldarlıq daha da aşağı düşəcək.

4-cü cəhd: mətn şəklində bir zibil götürün

Sonra ağlıma parlaq görünən bir fikir gəldi: mətn şəklində bir zibil götürün və son yazılan sətri təhlil edin.

Ancaq əvvəlcə cədvəlin quruluşuna nəzər salaq. ws_log_smevlog:

Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)

Bizim vəziyyətimizdə bir sütun var "Id"cərgənin unikal identifikatorunu (sayğacını) ehtiva edən . Plan belə idi:

  1. Mətn şəklində zibil götürməyə başlayırıq (sql əmrləri şəklində)
  2. Müəyyən bir vaxtda zibil xəta səbəbindən kəsiləcək, lakin mətn faylı hələ də diskdə saxlanılacaq.
  3. Mətn faylının sonuna baxırıq, bununla da uğurla silinmiş sonuncu sətirin identifikatorunu (id) tapırıq.

Mətn şəklində zibil götürməyə başladım:

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

Dump, gözlənildiyi kimi, eyni xəta ilə kəsildi:

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

Daha sonra quyruq Mən zibilliyin sonuna baxdım (quyruq -5 ./my_dump.dump) zibilliyin id ilə xəttdə kəsildiyini aşkar etdi 186 525. "Beləliklə, problem id 186 526 ilə uyğundur, pozulub və silinməlidir!" – düşündüm. Lakin, verilənlər bazasına sorğu vermək:
«id=186529 olduğu ws_log_smevlog-dan * seçin“Məlum oldu ki, bu xəttlə hər şey qaydasındadır... 186 - 530 indeksli cərgələr də problemsiz işləyirdi. Daha bir "parlaq ideya" uğursuz oldu. Bunun niyə baş verdiyini sonradan başa düşdüm: cədvəldən məlumatları silərkən və dəyişdirərkən onlar fiziki olaraq silinmir, lakin "ölü tuple" kimi qeyd olunur, sonra gəlir avtovakuum və bu sətirləri silinmiş kimi qeyd edir və bu sətirlərin təkrar istifadəsinə icazə verir. Anlamaq üçün, cədvəldəki məlumatlar dəyişirsə və avtovakuum işə salınırsa, o zaman ardıcıl olaraq saxlanmır.

Cəhd 5: SEÇ, FROM, HARADAN id=

Uğursuzluqlar bizi daha güclü edir. Heç vaxt təslim olmamalısan, sona qədər getməlisən, özünə və öz imkanlarına inanmalısan. Ona görə də başqa variantı sınamaq qərarına gəldim: verilənlər bazasındakı bütün qeydləri bir-bir nəzərdən keçirmək kifayətdir. Cədvəlimin strukturunu bilməklə (yuxarıya bax) unikal olan id sahəsimiz var (əsas açar). Cədvəldə 1 sıra var id qaydasındadır, yəni biz onları bir-bir keçə bilərik:

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

Əgər kimsə başa düşmürsə, əmr aşağıdakı kimi işləyir: o, cədvəli sətir-sətir skan edir və stdout-u göndərir. / dev / null, lakin SELECT əmri uğursuz olarsa, o zaman səhv mətni çap olunur (stderr konsola göndərilir) və xətanı ehtiva edən sətir çap olunur ( || sayəsində, bu o deməkdir ki, seçimdə problemlər var (komandanın qaytarma kodu). 0 deyil)).

Bəxtim gətirdi, meydanda indekslərim var idi id:

Uğursuzluqdan sonra Postgres verilənlər bazasını bərpa etmək üzrə ilk təcrübəm (relatton bazası/4123007 blok 16490-də ​​etibarsız səhifə)

Bu o deməkdir ki, istədiyiniz id ilə xətti tapmaq çox vaxt aparmamalıdır. Teorik olaraq işləməlidir. Yaxşı, əmri daxil edək tmux və gedək yataq.

Səhərə qədər mən 90-ə yaxın yazıya baxıldığını gördüm ki, bu da 000%-dən bir qədər çoxdur. Əvvəlki üsulla müqayisədə əla nəticə (5%)! Amma 2 gün gözləmək istəmirdim...

Cəhd 6: SELECT, FROM, WHERE id >= və id

Müştərinin verilənlər bazasına həsr olunmuş əla serveri var idi: ikili prosessor Intel Xeon E5-2697 v2, məkanımızda 48 mövzu var idi! Serverdə yük orta səviyyədə idi, biz 20-yə yaxın mövzunu heç bir problem olmadan yükləyə bildik. Kifayət qədər operativ yaddaş da var idi: 384 gigabayt qədər!

Buna görə əmri paralelləşdirmək lazım idi:

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

Burada gözəl və zərif bir skript yazmaq mümkün idi, lakin mən ən sürətli paralelləşdirmə üsulunu seçdim: 0-1628991 diapazonunu əl ilə 100 qeyd intervalına bölün və ayrıca formada 000 əmri yerinə yetirin:

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

Ancaq bu, hamısı deyil. Teorik olaraq, verilənlər bazasına qoşulmaq da müəyyən vaxt və sistem resursları tələb edir. 1-i birləşdirmək çox ağıllı deyildi, razılaşacaqsınız. Buna görə də, bir əlaqə əvəzinə 628 cərgə əldə edək. Nəticədə komanda buna çevrildi:

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

tmux sessiyasında 16 pəncərə açın və əmrləri yerinə yetirin:

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

Bir gün sonra ilk nəticələri aldım! Yəni (XXX və ZZZ dəyərləri artıq qorunmur):

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

Bu o deməkdir ki, üç sətirdə xəta var. Birinci və ikinci problem qeydlərinin id-ləri 829 ilə 000 arasında, üçüncünün idləri 830 ilə 000 arasında idi.Sonra, sadəcə olaraq problem qeydlərinin dəqiq id dəyərini tapmaq lazım idi. Bunu etmək üçün, 146 addımı ilə problemli qeydlərlə diapazonumuzu nəzərdən keçiririk və id-i müəyyən edirik:

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

Xoşbəxt son

Problemli xətləri tapdıq. Psql vasitəsilə verilənlər bazasına daxil oluruq və onları silməyə çalışırıq:

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

Təəccübləndirdiyim odur ki, girişlər seçim olmadan da heç bir problem olmadan silindi sıfır_zədəli_səhifələr.

Sonra verilənlər bazasına bağlandım, etdim VAKUM DOLU (Məncə bunu etmək lazım deyildi) və nəhayət istifadə edərək ehtiyat nüsxəsini uğurla çıxardım pg_dump. Zibil heç bir səhv olmadan götürüldü! Problem belə axmaq şəkildə həll olundu. Sevincin həddi-hüdudu yox idi, bu qədər uğursuzluqdan sonra həllini tapa bildik!

Təşəkkür və Nəticə

Həqiqi Postgres verilənlər bazasını bərpa etməkdə ilk təcrübəm belə oldu. Bu təcrübəni uzun müddət xatırlayacağam.

Və nəhayət, sənədləri rus dilinə tərcümə etdiyi üçün PostgresPro-ya təşəkkür etmək istərdim tamamilə pulsuz onlayn kurslar, problemin təhlili zamanı çox kömək etdi.

Mənbə: www.habr.com

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