PostgreSQL atjaunošana uz laika punktu (point-in-time recovery, PITR) ir tas, kas jūs izglābj, kad bojājums nāk no iekšpuses: DELETE bez WHERE, migrācija, kas izdzēsa nepareizo kolonnu, kļūda, kas kopš rīta pārraksta rindas. Replikas nepalīdz, jo tās precīzi nokopē kļūdu. Nakts pg_dump nozīmē izvēli starp dienas datu zaudēšanu un bojājuma paturēšanu. PITR ļauj atjaunot klasteri tādu, kāds tas bija minūti pirms kļūdas. Bet tikai tad, ja divas lietas bija iestatītas pirms incidenta, un tikai tad, ja atjaunošanas procedūru zināt pietiekami labi, lai to izpildītu spiediena apstākļos.
Kā tas darbojas
PostgreSQL katru izmaiņu vispirms ieraksta priekšrakstīšanas žurnālā (WAL) un tikai tad maina datu failus. PITR apvieno divas daļas, kas aprakstītas dokumentācijas nodaļā par nepārtrauktu arhivēšanu:
- Bāzes rezerves kopija: visa datu direktorija fiziska kopija, ko izveido, piemēram, ar
pg_basebackup. - Nepārtraukts WAL arhīvs: katrs pabeigts WAL segments, kas tiek nokopēts drošā vietā, tiklīdz tas ir uzrakstīts.
Lai atjaunotu, jūs atjaunojat bāzes kopiju un ļaujat PostgreSQL virs tās atskaņot arhivēto WAL, apstājoties jūsu izvēlētajā brīdī. No šīs uzbūves izriet divi ierobežojumi. Pirmkārt, tas darbojas tikai visam klasterim, nevis atsevišķai datubāzei vai tabulai. Otrkārt, pg_dump un pg_dumpall veido loģiskas izgāztuves, nevis failu sistēmas līmeņa kopijas, tāpēc PITR tās izmantot nevar vispār.
Pirms incidenta: arhivēšana un bāzes kopijas
Minimālā konfigurācija izskatās šādi. archive_command ir piemērs no dokumentācijas, kura pati to sauc par "piemēru, nevis ieteikumu":
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
archive_timeout = 60
wal_leveljābūtreplicavai augstākam, un ganwal_level, ganarchive_modemainās tikai pēc servera restarta. Plānojiet šo restartu; neatklājiet to incidenta laikā.%pir arhivējamā WAL faila ceļš,%f– tā nosaukums. Komandai jāatgriež nulle tikai tad, ja kopēšana izdevās. Saņemot nulli, PostgreSQL uzskata segmentu par drošu un to pārstrādā. Daļatest ! -fatsakās pārrakstīt arhīvā jau esošu failu.archive_timeoutpēc norādītā sekunžu skaita piespiež pāriet uz jaunu segmentu, ja bija aktivitāte, un tā ierobežo, cik daudz jaunāko datu atrodas tikai nearhivētā segmentā. Dokumentācija saka, ka parasti saprātīga ir aptuveni minūte; ļoti īsas vērtības uzpūš arhīvu, jo piespiedu segmenti joprojām ir pilnā izmērā.- Regulāri veidojiet bāzes kopijas. Atjaunošanas apstāšanās punktam jābūt pēc tās bāzes kopijas beigām, no kuras tā sākas, tāpēc kopija nevar jūs aizvest uz brīdi, kad tā vēl tika veidota, vai agrāk.
Produkcijā daudzas komandas cp vietā izmanto tādus rīkus kā pgBackRest, WAL-G vai Barman. Tie nodrošina saspiešanu, glabāšanas politiku, paralēlismu un objektu krātuvi, bet mehānisms zem tiem ir tas pats.
Pēc tam uzraugiet arhīvu. pg_stat_archiver rāda, vai arhivēšana tiek līdzi:
SELECT archived_count, last_archived_wal, last_archived_time,
failed_count, last_failed_wal, last_failed_time
FROM pg_stat_archiver;
Augošs failed_count vai sens last_archived_time noslogotā serverī nozīmē, ka jūsu PITR logs nav tāds, kādu domājat. Neizdevušies segmenti arī paliek pg_wal, līdz tiek arhivēti, tāpēc salūzis arhīvs galu galā var piepildīt disku; par šo situāciju rakstām mūsu ceļvedī par pilnu pg_wal disku. Ņemiet vērā, ka dokumentācija min gadījumus, kad kļūme pg_stat_archiver netiek parādīta, piemēram, ja komandu pārtrauc signāls, tāpēc brīdinājums par pēdējā arhivētā segmenta vecumu ir noderīga otrā pārbaude.
Incidenta laikā: atrodiet brīdi
Pirms pieskaraties jebkuram serverim, noskaidrojiet, kad bojājums sākās. Parastie avoti ir lietotnes žurnāli, izlaidumu laika zīmogi un PostgreSQL žurnāls (ja izteikumi tiek žurnalēti). Pierakstiet laiku ar skaidru UTC nobīdi. Dokumentācija iesaka skaitlisku nobīdi, piemēram, +02; darbojas arī pilns zonas nosaukums, piemēram, Europe/Riga, bet saīsinājumi, piemēram, EET, parasti ne.
Ja neesat pārliecināti par precīzo sekundi, izvēlieties nedaudz agrāku laiku. Uz priekšu vienmēr var aiziet tālāk; atpakaļ bez sākšanas no jauna nevar.
Atjaunojiet uz atsevišķa servera
Tā kā PITR atjauno visu klasteri, produkcijas atgriešana pagātnē nozīmē zaudēt visus likumīgos ierakstus kopš kļūdas. Parasti labāks plāns ir atjaunot uz atsevišķas mašīnas vai instances, pārbaudīt datus tur un atpakaļ nokopēt tikai skartās rindas vai tabulas. Produkcijas serveris tikmēr turpina strādāt.
Dokumentētā procedūra, pielāgota jaunam mērķa serverim:
- Atjaunojiet bāzes kopiju tukšā datu direktorijā, kas pieder datubāzes sistēmas lietotājam (nevis root), un pārbaudiet, vai tabulu telpu saites
pg_tblspc/norāda uz pareizajām vietām. Inkrementālu kopiju ķēdi (PostgreSQL 17 un jaunākās versijās) apvienojiet arpg_combinebackup. - Izdzēsiet visus failus, kas no kopijas palikuši
pg_wal/; dokumentācija tos sauc par "visticamāk novecojušiem". Atjaunošana WAL ņems no arhīva. - Konfigurējiet atjaunošanu
postgresql.conf:restore_command = 'cp /mnt/server/archivedir/%f "%p"' recovery_target_time = '2026-11-02 14:31:00+02' recovery_target_action = 'pause' - Izveidojiet tukšu failu
recovery.signaldatu direktorijā. - Ierobežojiet piekļuvi
pg_hba.conf, lai lietotnes nejauši nevarētu pieslēgties atjaunojamajam serverim. - Startējiet serveri un vērojiet žurnālu. Tas prasīs failus, kuru arhīvā nav, tostarp
.historyfailus; tas ir normāli, unrestore_commandtādos gadījumos vienkārši jābeidzas ar kļūdu.
Izmantojiet recovery_target_action = 'pause', kas ir arī noklusējums. Kad mērķis sasniegts, serveris pārtrauc atskaņošanu un ļauj vaicāt datus. Ja izdzēstās rindas ir vietā, beidziet atjaunošanu ar pg_wal_replay_resume(). Ja apstājāties par agru, apturiet serveri, pārceliet mērķi uz vēlāku laiku un startējiet serveri vēlreiz, lai turpinātu atjaunošanu. Pauzei vajadzīgs ieslēgts hot_standby; bez tā pause darbojas kā shutdown.
SELECT pg_is_in_recovery();
-- inspect the data, then end recovery:
SELECT pg_wal_replay_resume();
Divi iestatījumi nosaka, kur tieši atskaņošana apstājas. recovery_target_inclusive (noklusējums on) apstājas tūlīt pēc mērķa; iestatiet off, lai apstātos tieši pirms tā. Vienlaikus var iestatīt tikai vienu no mērķa iestatījumiem (recovery_target_time, _name, _xid, _lsn vai recovery_target = 'immediate'). Ja plānojat riskantas izmaiņas, pg_create_restore_point() izveido nosauktu punktu, uz kuru vēlāk var mērķēt ar recovery_target_name, un tas ir daudz vieglāk nekā minēt laika zīmogu.
Ja atjaunošanai arhivētais WAL beidzas pirms mērķa sasniegšanas, serveris apstājas ar fatālu kļūdu. Tā jūs uzzināt, ka arhīvā ir robs, un tāpēc augstāk aprakstītais monitorings ir svarīgs.
Pēc atjaunošanas: laika līnijas
Katra pabeigta arhīva atjaunošana sāk jaunu laika līniju (timeline). Laika līnijas ID ir katra WAL faila nosaukuma pirmā daļa, un neliels .history fails ieraksta, kur tā atzarojās. Tas ļauj PostgreSQL vienā arhīvā turēt veco un jauno vēsturi atsevišķi. Pēc noklusējuma atjaunošana seko jaunākajai laika līnijai arhīvā (recovery_target_timeline = 'latest'), tāpēc, ja veicat vairākas izmēģinājuma atjaunošanas, kas arhivē tajā pašā vietā, apzināti izvēlieties vajadzīgo laika līniju. Atcerieties arī, ka konfigurācijas faili, piemēram, postgresql.conf un pg_hba.conf, WAL atskaņošanā netiek atjaunoti; dublējiet tos atsevišķi.
Pārbaudiet, pirms tas vajadzīgs
PITR iestatījums, kas nekad nav atjaunots, ir cerība, nevis plāns. Regulāri veiciet atjaunošanu uz izvēlētu laika zīmogu testa serverī, nomēriet laiku un salīdziniet rezultātu ar gaidīto. Mūsu ceļvedis par rezerves kopiju un atjaunošanas testēšanu apraksta, kā to padarīt par rutīnu.
Galvenās atziņas
- PITR = bāzes kopija + nepārtraukts WAL arhīvs. Ja abi nav iestatīti pirms incidenta, nav uz ko atjaunot.
- Tas atjauno visu klasteri, tāpēc atjaunojiet uz atsevišķa servera un atpakaļ kopējiet tikai bojāto.
- Uzraugiet
pg_stat_archiverun pēdējā arhivētā segmenta vecumu; robs arhīvā beidz atjaunošanu ar fatālu kļūdu. - Izmantojiet
recovery_target_timear skaidru UTC nobīdi unrecovery_target_action = 'pause', tad pārbaudiet datus pirms atjaunošanas beigšanas. - Regulāri trenējiet atjaunošanu un konfigurācijas failus dublējiet atsevišķi.