Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)

Би та бүхэнтэй Postgres мэдээллийн санг бүрэн ажиллагаатай болгож сэргээх анхны амжилттай туршлагаа хуваалцахыг хүсч байна. Би Postgres DBMS-тэй хагас жилийн өмнө танилцсан бөгөөд үүнээс өмнө би мэдээллийн сангийн удирдлагын туршлага огт байгаагүй.

Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)

Би мэдээллийн технологийн томоохон компанид хагас DevOps инженерээр ажилладаг. Манай компани ачаалал ихтэй үйлчилгээнд зориулсан программ хангамж боловсруулдаг бөгөөд би гүйцэтгэл, засвар үйлчилгээ, байршуулалтыг хариуцдаг. Надад стандарт даалгавар өгсөн: нэг сервер дээрх програмыг шинэчлэх. Аппликешн нь Django хэл дээр бичигдсэн бөгөөд шинэчлэлтийн явцад шилжилт хөдөлгөөн хийгддэг (өгөгдлийн сангийн бүтцэд гарсан өөрчлөлтүүд) бөгөөд энэ процессын өмнө бид стандарт pg_dump програмаар дамжуулан мэдээллийн санг бүрэн устгадаг.

Дамп хийх явцад гэнэтийн алдаа гарлаа (Postgres хувилбар 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

Алдаа "блок дахь хүчингүй хуудас" файлын системийн түвшний асуудлуудын талаар ярьдаг бөгөөд энэ нь маш муу юм. Төрөл бүрийн форум дээр үүнийг хийхийг санал болгосон БҮРЭН ВАКУМ сонголттой тэг_гэмтэлтэй_хуудас Энэ асуудлыг шийдэхийн тулд. За оролдоод үзье...

Сэргээхэд бэлдэж байна

Анхаар! Өгөгдлийн сангаа сэргээх оролдлого хийхээсээ өмнө Postgres нөөцлөлтийг авахаа мартуузай. Хэрэв танд виртуал машин байгаа бол мэдээллийн санг зогсоож, агшин зуурын зургийг аваарай. Хэрэв агшин зуурын зураг авах боломжгүй бол мэдээллийн санг зогсоож, Postgres лавлах (wal файлуудыг оруулаад) агуулгыг аюулгүй газар хуулж ав. Манай бизнесийн гол зүйл бол нөхцөл байдлыг улам дордуулахгүй байх явдал юм. Унших энэ нь.

Мэдээллийн сан ерөнхийдөө миний хувьд ажилладаг байсан тул би ердийн мэдээллийн санд өөрийгөө хязгаарласан боловч гэмтсэн өгөгдөл бүхий хүснэгтийг хассан (сонголт). -T, --exclude-хүснэгт=ХҮСНЭГТ pg_dump дотор).

Сервер нь физик байсан тул агшин зуурын зураг авах боломжгүй байсан. Нөөцийг устгасан, цаашаа явцгаая.

Файлын системийг шалгах

Өгөгдлийн санг сэргээх оролдлого хийхийн өмнө бид файлын системд бүх зүйл тохирсон эсэхийг шалгах хэрэгтэй. Мөн алдаа гарсан тохиолдолд тэдгээрийг засаарай, учир нь өөрөөр хэлбэл та зөвхөн нөхцөл байдлыг улам дордуулж чадна.

Миний хувьд мэдээллийн баазтай файлын системийг суулгасан "/srv" төрөл нь ext4 байсан.

Мэдээллийн санг зогсоох: systemctl зогсоох [имэйлээр хамгаалагдсан] файлын системийг хэн ч ашиглаагүй, командыг ашиглан салгаж болох эсэхийг шалгана уу тийм:
lsof +D /srv

Би бас redis мэдээллийн санг ашиглаж байсан тул зогсоох хэрэгтэй болсон "/srv". Дараа нь би салгасан / srv (хамгийн их).

Файлын системийг хэрэгслийг ашиглан шалгасан e2fsck -f шилжүүлэгчтэй (Файлын системийг цэвэр гэж тэмдэглэсэн байсан ч албадан шалгах):

Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)

Дараа нь хэрэгслийг ашиглана уу dumpe2fs (sudo dumpe2fs /dev/mapper/gu2—sys-srv | grep шалгасан) та шалгалт үнэхээр хийгдсэн эсэхийг шалгаж болно:

Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)

e2fsck ext4 файлын системийн түвшинд ямар ч асуудал олдоогүй бөгөөд энэ нь та мэдээллийн баазыг сэргээх оролдлогыг үргэлжлүүлж болно, эсвэл буцах боломжтой гэсэн үг юм. вакуум дүүрэн (мэдээжийн хэрэг, та файлын системийг буцааж холбож, мэдээллийн санг эхлүүлэх хэрэгтэй).

Хэрэв танд физик сервер байгаа бол дискний статусыг шалгахаа мартуузай ( smartctl -a /dev/XXX) эсвэл RAID хянагч ашиглан асуудал нь техник хангамжийн түвшинд байгаа эсэхийг шалгаарай. Миний хувьд RAID нь "техник хангамж" болж хувирсан тул би нутгийн админаас RAID-ийн статусыг шалгахыг хүссэн (сервер надаас хэдэн зуун километрийн зайд байсан). Тэр хэлэхдээ ямар ч алдаа гараагүй, энэ нь бид сэргээн босголтыг эхлүүлэх нь гарцаагүй гэсэн үг юм.

1-р оролдлого: тэг_гэмтэлтэй_хуудас

Бид өгөгдлийн сантай psql-ээр супер хэрэглэгчийн эрхтэй акаунтаар холбогддог. Бидэнд супер хэрэглэгч хэрэгтэй, учир нь... сонголт тэг_гэмтэлтэй_хуудас зөвхөн тэр л өөрчилж чадна. Миний хувьд энэ нь postgres юм:

psql -h 127.0.0.1 -U postgres -s [мэдээллийн сангийн_нэр]

Сонголт тэг_гэмтэлтэй_хуудас Унших алдааг үл тоомсорлохын тулд шаардлагатай (postgrespro вэбсайтаас):

PostgreSQL эвдэрсэн хуудасны толгойг илрүүлэх үед ихэвчлэн алдаа мэдээлдэг бөгөөд одоогийн гүйлгээг зогсоодог. Хэрэв XNUMX_damaged_pages идэвхжсэн бол систем нь анхааруулга өгч, санах ой дахь гэмтсэн хуудсыг тэглэж, үргэлжлүүлэн боловсруулна. Энэ үйлдэл нь өгөгдлийг, тухайлбал гэмтсэн хуудасны бүх мөрийг устгадаг.

Бид энэ сонголтыг идэвхжүүлж, хүснэгтүүдийн бүрэн вакуумыг хийхийг оролдоно.

VACUUM FULL VERBOSE

Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)
Харамсалтай нь азгүйтэл.

Бид ижил төстэй алдаатай тулгарсан:

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 – нэг хуудсанд багтахгүй бол Poetgres-д "урт өгөгдөл" хадгалах механизм (анхдагчаар 8кб).

2-р оролдлого: дахин индексжүүлэх

Google-ийн анхны зөвлөгөө тус болсонгүй. Хэдэн минут хайсны дараа би хийх хоёр дахь зөвлөгөөг оллоо дахин индекс гэмтсэн ширээ. Би энэ зөвлөгөөг олон газраас харсан ч итгэл төрүүлээгүй. Дахин индексжүүлье:

reindex table ws_log_smevlog

Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)

дахин индекс асуудалгүйгээр дуусгасан.

Гэсэн хэдий ч энэ нь тус болсонгүй, ВАКУМ ДҮҮРЭН үүнтэй төстэй алдаа гарсан. Би бүтэлгүйтэлд дассан тул интернетээс зөвлөгөө хайж эхэлсэн бөгөөд нэлээд сонирхолтой зүйл олж мэдсэн нийтлэл.

3-р оролдлого: СОНГОХ, ХЯЗГААРЛАХ, ОФФСЕТ

Дээрх нийтлэл нь хүснэгтийн мөрийг мөр болгон харж, асуудалтай өгөгдлийг арилгахыг санал болгосон. Эхлээд бид бүх мөрийг харах хэрэгтэй болсон:

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

Миний хувьд хүснэгтийг агуулсан 1 628 991 мөрүүд! Үүнийг сайн арчлах шаардлагатай байсан өгөгдөл хуваах, гэхдээ энэ бол тусдаа хэлэлцэх сэдэв юм. Бямба гариг ​​байсан, би энэ тушаалыг tmux дээр ажиллуулаад орондоо оров:

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

Өглөө болоход би бүх зүйл хэрхэн явагдаж байгааг шалгахаар шийдэв. 20 цагийн дараа өгөгдлийн ердөө 2% нь сканнердсан болохыг би гайхшрууллаа! Би 50 хоног хүлээхийг хүсээгүй. Өөр нэг бүрэн бүтэлгүйтэл.

Гэхдээ би бууж өгөөгүй. Сканнер яагаад ийм удаан үргэлжилсэн гэж би гайхсан. Баримт бичгээс (дахин postgrespro дээр) би олж мэдсэн:

OFFSET нь мөрүүдийг гаргаж эхлэхээс өмнө заасан тооны мөрийг алгасахыг заадаг.
Хэрэв OFFSET болон LIMIT хоёуланг нь зааж өгсөн бол систем эхлээд OFFSET мөрүүдийг алгасаад дараа нь LIMIT хязгаарлалтын мөрүүдийг тоолж эхэлнэ.

LIMIT-г ашиглахдаа ORDER BY заалтыг ашиглах нь чухал бөгөөд ингэснээр үр дүнгийн мөрүүдийг тодорхой дарааллаар буцаана. Үгүй бол урьдчилан тааварлах боломжгүй дэд багц мөрүүдийг буцаана.

Дээрх тушаал буруу байсан нь ойлгомжтой: нэгдүгээрт, байхгүй байсан захиалгаар, үр дүн нь алдаатай байж болно. Хоёрдугаарт, Postgres эхлээд OFFSET мөрүүдийг сканнердаж, алгасах шаардлагатай болсон бөгөөд энэ нь нэмэгдэж байв OFFFSET бүтээмж улам бүр буурах болно.

4-р оролдлого: текст хэлбэрээр хогийн цэг авах

Дараа нь миний толгойд гайхалтай санаа орж ирэв: текст хэлбэрээр хогийн цэг авч, хамгийн сүүлд бичигдсэн мөрийг задлан шинжилнэ үү.

Гэхдээ эхлээд хүснэгтийн бүтцийг авч үзье. ws_log_smevlog:

Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)

Манай тохиолдолд багана байна "Id", мөрийн өвөрмөц танигч (тоолуур)-ыг агуулсан. Төлөвлөгөө нь иймэрхүү байв.

  1. Бид текст хэлбэрээр (sql команд хэлбэрээр) хогийн цэг авч эхэлдэг.
  2. Тодорхой хугацааны дараа алдааны улмаас дамп тасалдсан боловч текст файл диск дээр хадгалагдсан хэвээр байх болно.
  3. Бид текст файлын төгсгөлийг харж, амжилттай устгасан сүүлчийн мөрийн танигчийг (id) олдог.

Би текст хэлбэрээр хаягдал авч эхэлсэн:

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

Хүлээгдэж байсанчлан хогийн цэг ижил алдаагаар тасалдсан:

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

Цаашид сүүл Би хогийн цэгийг харсан (сүүл -5 ./my_dump.dump) id-тай шугам дээр овоолго тасалдсан болохыг олж мэдсэн 186 525. "Тиймээс асуудал id 186 526-тай холбоотой, энэ нь эвдэрсэн тул устгах шаардлагатай байна!" - Би бодсон. Гэхдээ мэдээллийн санд асуулга хийх:
«ws_log_smevlog-ээс *-г сонго, энд id=186529"Энэ шугамаар бүх зүйл сайхан байсан нь тогтоогдсон ... 186 - 530 индекстэй мөрүүд ч асуудалгүй ажилласан. Өөр нэг "гайхалтай санаа" бүтэлгүйтэв. Дараа нь би яагаад ийм зүйл болсныг ойлгосон: хүснэгтээс өгөгдлийг устгах, өөрчлөх үед тэдгээр нь физик байдлаар устгагдахгүй, харин "үхсэн залгуур" гэж тэмдэглэгдсэн байдаг. автовакуум мөн эдгээр мөрүүдийг устгасан гэж тэмдэглэж, эдгээр мөрүүдийг дахин ашиглахыг зөвшөөрдөг. Хэрэв хүснэгтэд байгаа өгөгдөл өөрчлөгдөж, автомат вакуум идэвхжсэн бол дараалсан хадгалагдахгүй гэдгийг ойлгох болно.

5-р оролдлого: SELECT, FROM, WHERE id=

Алдаа дутагдал биднийг илүү хүчирхэг болгодог. Та хэзээ ч бууж өгөх ёсгүй, та эцсээ хүртэл явж, өөртөө болон өөрийнхөө чадварт итгэх хэрэгтэй. Тиймээс би өөр хувилбарыг туршиж үзэхээр шийдсэн: мэдээллийн сан дахь бүх бүртгэлийг нэг нэгээр нь үзэхэд л хангалттай. Миний хүснэгтийн бүтцийг (дээрээс харна уу) мэдэж байгаа тул бидэнд өвөрмөц (үндсэн түлхүүр) гэсэн id талбар бий. Бид хүснэгтэд 1 мөр байна id Энэ нь бид тэдгээрийг нэг нэгээр нь дамжуулж болно гэсэн үг юм:

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

Хэрэв хэн нэгэн ойлгохгүй байвал тушаал дараах байдлаар ажиллана: энэ нь хүснэгтийг мөр мөрөөр нь сканнердаж, stdout-г илгээнэ. / dev / null, гэхдээ SELECT команд амжилтгүй болвол алдааны текст хэвлэгдэх болно (stderr-ийг консол руу илгээсэн) алдаа агуулсан мөрийг хэвлэнэ ( ||-ийн ачаар энэ нь сонгоход асуудал гарсан гэсэн үг юм (командын буцах код). 0 биш)).

Би азтай байсан тул талбай дээр индексүүд бий болсон id:

Би бүтэлгүйтлийн дараа Postgres мэдээллийн санг сэргээх анхны туршлага (relatton base/4123007 блок 16490 дахь хүчингүй хуудас)

Энэ нь хүссэн id-тэй мөрийг олоход их цаг хугацаа шаардагдахгүй гэсэн үг юм. Онолын хувьд энэ нь ажиллах ёстой. За ингээд командаа оруулъя tmux тэгээд орондоо орцгооё.

Өглөө болоход би 90 орчим бичлэг үзсэнийг олж мэдсэн бөгөөд энэ нь ердөө 000% гаруй юм. Өмнөх аргатай харьцуулахад маш сайн үр дүн (5%)! Гэхдээ би 2 хоног хүлээхийг хүссэнгүй...

6-р оролдлого: SELECT, FROM, WHERE id >= болон id

Үйлчлүүлэгч нь мэдээллийн санд зориулагдсан маш сайн сервертэй байсан: хос процессор Intel Xeon E5-2697 v2, манай байршилд 48 хүртэлх хэлхээс байсан! Сервер дээрх ачаалал дундаж байсан тул бид 20 орчим урсгалыг ямар ч асуудалгүйгээр татаж авах боломжтой. Мөн хангалттай RAM байсан: 384 гигабайт!

Тиймээс тушаалыг зэрэгцээ болгох шаардлагатай:

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

Энд үзэсгэлэнтэй, дэгжин скрипт бичих боломжтой байсан ч би хамгийн хурдан зэрэгцүүлэх аргыг сонгосон: 0-1628991 мужийг 100 бичлэгийн интервалд гараар хувааж, маягтын 000 командыг тусад нь ажиллуул.

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

Гэхдээ энэ нь бүгд биш юм. Онолын хувьд өгөгдлийн сантай холбогдоход бага зэрэг цаг хугацаа, системийн нөөц шаардлагатай байдаг. 1-ийг холбох нь тийм ч ухаалаг биш байсан, та санал нийлэх болно. Тиймээс, нэг холболтын оронд 628 мөрийг татаж авцгаая. Үүний үр дүнд баг дараахь байдлаар өөрчлөгдсөн.

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 сессээр 16 цонх нээж дараах тушаалуудыг ажиллуулна уу.

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

Нэг өдрийн дараа би анхны үр дүнг хүлээн авлаа! Тухайлбал (XXX ба ZZZ утгууд хадгалагдахаа больсон):

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

Энэ нь гурван мөрөнд алдаа байна гэсэн үг юм. Эхний болон хоёр дахь асуудалтай бүртгэлүүдийн ID нь 829-аас 000-ийн хооронд, гурав дахь нь 830-аас 000-ийн хооронд байсан. Дараа нь бид асуудалтай бүртгэлүүдийн яг тодорхой id утгыг олох хэрэгтэй болсон. Үүнийг хийхийн тулд бид 146-ийн алхамтай асуудалтай бүртгэлүүдийнхээ хүрээг судалж, 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

Аз жаргалтай төгсгөл

Бид асуудалтай шугамуудыг олсон. Бид psql-ээр дамжуулан мэдээллийн сан руу орж тэдгээрийг устгахыг оролдоно.

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

Миний гайхшрал нь, оруулгууд сонголтгүйгээр ч гэсэн ямар ч асуудалгүйгээр устгагдсан тэг_гэмтэлтэй_хуудас.

Дараа нь би мэдээллийн санд холбогдсон ВАКУМ ДҮҮРЭН (Би үүнийг хийх шаардлагагүй гэж бодож байна), эцэст нь би нөөцлөлтийг ашиглан амжилттай устгасан pg_dump. Хогийн цэгийг ямар ч алдаагүйгээр авсан! Асуудлыг ийм тэнэг аргаар шийдсэн. Баяр баясгалан хязгааргүй байсан тул маш олон бүтэлгүйтлийн дараа бид шийдлийг олж чадсан!

Талархал ба дүгнэлт

Жинхэнэ Postgres мэдээллийн санг сэргээх анхны туршлага маань ингэж гарсан юм. Би энэ туршлагыг удаан хугацаанд санах болно.

Эцэст нь бичиг баримтыг орос хэл рүү орчуулсан PostgresPro-д баярлалаа гэж хэлмээр байна бүрэн үнэгүй онлайн курсууд, энэ нь асуудлыг шинжлэх явцад маш их тусалсан.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх