PostgreSQL kļūda "invalid page in block" nozīmē, ka serveris nolasīja lapu (pēc noklusējuma 8 kB) no datu faila un atklāja, ka tā ir bojāta: vai nu tās galvene nav derīga lapas galvene, vai, ja ieslēgtas datu kontrolsummas, kontrolsumma nesakrīt. Tas ir īsts datu bojājums, nevis vaicājuma problēma. Labā ziņa ir tā, ka bieži tas skar vienu objektu un dažreiz vienu lapu. Sliktā ziņa ir tā, ka steidzīgi labojumi to var padarīt neatgriezenisku.
Tipisks žurnāls ar ieslēgtām kontrolsummām:
WARNING: page verification failed, calculated checksum 21574 but expected 2207
ERROR: invalid page in block 48213 of relation "base/16384/16529"
Ziņojumi nāk no bufpage.c, kas pārbauda katru lapu nolasīšanas brīdī, un bufmgr.c, kas izraisa kļūdu. Pirms PostgreSQL 18 ceļš tiek izdrukāts bez pēdiņām. PostgreSQL 18 var arī ziņot par vairākām bojātām lapām uzreiz kā "N invalid pages among blocks X..Y of relation".
1. solis: nepadariet sliktāk
Bojājums gandrīz vienmēr nāk no slāņa zem PostgreSQL: bojāts disks, kontroliera vai RAID problēma, krātuves slānis, kas samelojis par pabeigtu ierakstu, slikta RAM vai kļūda kodolā vai failu sistēmā. Tas, kas ierakstīja bojāto lapu, var joprojām rakstīt. Tāpēc pirms jebkāda labojuma:
# pirms JEBKĀDA labošanas mēģinājuma: apturiet serveri un nokopējiet datu direktoriju
sudo systemctl stop postgresql
sudo rsync -a /var/lib/postgresql/18/main/ /backup/main-before-repair/
dmesg -T | grep -iE 'i/o error|ext4|xfs|nvme|ata' | tail -50
- Nokopējiet visu datu direktoriju, kamēr serveris ir apturēts, uz citu krātuvi. Katrs tālāk aprakstītais labošanas solis maina datus. Ar kopiju neizdevies mēģinājums ir mācība, nevis zaudējums.
- Paskatieties kodola žurnālā un diska veselības datos. I/O kļūdas vai failu sistēmas brīdinājumi ap pirmās kļūdas laiku pasaka, vai šim serverim vispār var uzticēties.
- Ja jums ir vesela replika, apsveriet failover uz to. Bojājuma, ko radījis primārā servera disks, fiziskajā replikā bieži nav, jo replika piemēro WAL saviem failiem, nevis kopē primārā servera datu failus, taču pirms uzticēšanās pārbaudiet repliku ar tālāk minētajiem rīkiem.
2. solis: noskaidrojiet, kas ir bojāts
Ceļš base/16384/16529 nozīmē datubāzes OID 16384, relācijas failu 16529 noklusējuma tabulvietā. Funkcija pg_filenode_relation to pārvērš atpakaļ nosaukumā:
-- 16384 ir datubāzes OID, 16529 relācijas filenode
SELECT datname FROM pg_database WHERE oid = 16384;
-- pieslēdzieties šai datubāzei, tad (0 = noklusējuma tabulvieta):
SELECT pg_filenode_relation(0, 16529) AS relation,
c.relkind
FROM pg_class c
WHERE c.oid = pg_filenode_relation(0, 16529);
Atbilde nosaka visu turpmāko incidentu:
- Indekss (
relkind = 'i') ir labākais gadījums. Indeksa saturu var pārbūvēt no tabulas, tāpēc dati netiek zaudēti. - Tabula (
relkind = 'r') vai tās TOAST tabula nozīmē, ka rindas var būt zudušas. - Sistēmas katalogs ir grūtākais gadījums. Neeksperimentējiet; atjaunojiet vai meklējiet palīdzību.
Pārbaudiet, vai kontrolsummas ir ieslēgtas un vai citas datubāzes jau nav skartas. Skats pg_stat_database kopš PostgreSQL 12 skaita kontrolsummu kļūmes:
SHOW data_checksums;
SELECT datname, checksum_failures, checksum_last_failure
FROM pg_stat_database
WHERE checksum_failures IS NOT NULL;
3. solis: izmēriet bojājumu
Viena bojāta lapa reti ir viena. Pirms risinājuma izvēles pārbaudiet, cik tālu tas sniedzas. Paplašinājums amcheck pārbauda tabulas (verify_heapam, PostgreSQL 14+) un B-tree indeksus (bt_index_check):
CREATE EXTENSION IF NOT EXISTS amcheck;
-- tabulas lapas (PostgreSQL 14+)
SELECT blkno, offnum, msg FROM verify_heapam('public.orders') LIMIT 20;
-- B-tree indeksa struktūra
SELECT bt_index_check('public.orders_pkey');
Visam klasterim pg_amcheck (arī 14+) paralēli palaiž tās pašas pārbaudes visās datubāzēs. Ja kontrolsummas ir ieslēgtas, pg_checksums --check noskenē katru korekti apturēta klastera failu un beidzas ar nenulles kodu, ja kāda kontrolsumma nesakrīt:
pg_amcheck --all --jobs=4 --heapallindexed --install-missing
# bezsaistē, viss klasteris, tikai ja kontrolsummas ir ieslēgtas:
pg_checksums --check -D /var/lib/postgresql/18/main --progress
Ja sākotnējais serveris ir aizdomīgs, palaidiet tās uz kopijas. Ilga skenēšana uz bojāta diska var to pabeigt pavisam.
4. solis: izvēlieties risinājumu
Bojāts indekss: pārbūvējiet to
Izdzēsiet un izveidojiet indeksu no jauna vai izpildiet REINDEX. Noslogotā sistēmā REINDEX ... CONCURRENTLY neliedz rakstīšanu; kā tas uzvedas un ko darīt, ja tas neizdodas pusceļā, skatiet mūsu ceļvedī par REINDEX CONCURRENTLY. Pēc tam vēlreiz palaidiet bt_index_check.
Bojāta tabula: atjaunojiet, ja varat
Ja jums ir bāzes rezerves kopija un WAL arhīvs, atjaunošana ir vienīgais veids, kā atgūt zaudētās rindas. Nav jāatritina visa produkcijas datubāze: atjaunojiet atsevišķā serverī uz brīdi tieši pirms bojājuma, tad nokopējiet atpakaļ skartās rindas vai visu tabulu. Mūsu ceļvedis par atjaunošanu uz laika punktu to izskaidro soli pa solim. Atcerieties, ka bojājums var būt pastāvējis kādu laiku, pirms vaicājums skāra lapu, tāpēc atjaunošanas punktu izvēlieties uzmanīgi un arī atjaunoto tabulu pārbaudiet ar amcheck.
Nav izmantojamas rezerves kopijas: glābiet, kas palicis
Kad nav no kā atjaunot, PostgreSQL ir divas izstrādātāju opcijas, lai tiktu garām bojātām lapām. Abas ir bīstamas, un dokumentācija to saka tieši:
zero_damaged_pagesliek PostgreSQL izdot brīdinājumu, atmiņā aizpildīt bojāto lapu ar nullēm un turpināt. Dokumentācija saka, ka tas iznīcinās datus, proti, visas rindas bojātajā lapā, un ka to parasti nevajag ieslēgt, kamēr neesat zaudējuši cerības atgūt datus no bojātajām lapām. Ar nullēm aizpildītās lapas netiek piespiedu kārtā ierakstītas diskā, tāpēc dokumentācija iesaka pirms opcijas izslēgšanas izveidot tabulu vai indeksu no jauna.ignore_checksum_failuredarbojas tikai ar ieslēgtām kontrolsummām. Tā izlaiž kontrolsummas kļūdu (joprojām ar brīdinājumu) un var ļaut nolasīt nebojātas rindas no lapas, kuras galvene vēl ir saprātīga. Dokumentācija brīdina, ka tas var izraisīt avārijas, izplatīt vai paslēpt bojājumu.
Abām noklusējums ir off, un tām vajag superlietotāju vai atbilstošu SET privilēģiju. Glābšana parasti izskatās šādi, palaista uz kopijas vai servera, ko varat atļauties pazaudēt:
-- PĒDĒJAIS LĪDZEKLIS, ja iespējams, uz kopijas: rindas bojātajās lapās tiek zaudētas
BEGIN;
SET LOCAL zero_damaged_pages = on;
CREATE TABLE public.orders_salvaged AS SELECT * FROM public.orders;
COMMIT;
SELECT count(*) FROM public.orders_salvaged; -- salīdziniet ar gaidīto
Tad salīdziniet skaitus un atslēgu diapazonus, noskaidrojiet, kuru rindu trūkst (no lietotnes žurnāliem, eksportiem vai citas sistēmas), pārbūvējiet indeksus un ierobežojumus un nomainiet tabulu pret izglābto. Nekad neatstājiet nevienu no šīm opcijām ieslēgtu servera konfigurācijā.
5. solis: padariet nākamo incidentu mazāku
- Ieslēdziet datu kontrolsummas. Kopš PostgreSQL 18
initdbtās ieslēdz pēc noklusējuma. Vecākos klasteros (PostgreSQL 12+) pg_checksums --enable tās var ieslēgt bezsaistē. Tas pārraksta relāciju blokus to vietā, tāpēc plānojiet dīkstāvi, kas aug līdz ar klastera izmēru. Piezīme jaunināšanai: pg_upgrade prasa sakrītošus kontrolsummu iestatījumus, tāpēc PostgreSQL 18 pievienojainitdb --no-data-checksumsvecu klasteru bez kontrolsummām jaunināšanai. - Brīdiniet par
checksum_failuresskatāpg_stat_database. Jebkura vērtība virs nulles ir pelnījusi pieteikumu tajā pašā dienā. - Regulāri palaidiet pg_amcheck, piemēram, reizi nedēļā uz replikas vai atjaunotas rezerves kopijas, lai bojājumu atrastu jūs, nevis klienta vaicājums.
- Testējiet atjaunošanu. Tabulas līmeņa atjaunošana 4. solī strādā tikai tad, ja rezerves kopijas un WAL arhivēšana patiešām darbojas.
- Novērsiet cēloni. Nomainiet disku, atjauniniet aparātprogrammatūru, pārbaudiet, vai krātuve ievēro flush. Citādi jūs atgriezīsieties.
Galvenās atziņas
- "invalid page in block" ir īsts bojājums diskā, ko gandrīz vienmēr izraisa aparatūra, krātuve vai OS.
- Pirms jebkāda labošanas mēģinājuma apturiet serveri un nokopējiet datu direktoriju.
- Piesaistiet ceļu ar
pg_filenode_relation: indeksu var pārbūvēt, tabulai vajag atjaunošanu vai glābšanu. - Pirms risinājuma izvēles izmēriet apjomu ar amcheck, pg_amcheck un pg_checksums.
zero_damaged_pagesiznīcina rindas bojātajās lapās. Izmantojiet to tikai kā pēdējo līdzekli, uz kopijas, un nekad neatstājiet ieslēgtu.- Ieslēdziet kontrolsummas (noklusējums PostgreSQL 18), brīdiniet par kontrolsummu kļūmēm un regulāri palaidiet amcheck.