PostgreSQL transakciju ID wraparound ir viena no retajām problēmām, kas var apturēt rakstīšanu veselīgi izskatošā datubāzē. Tā nerodas no zila gaisa: nedēļām vai mēnešiem XID vecums aug, tad žurnāls pildās ar WARNING: database "mydb" must be vacuumed within N transactions, un, ja nekas nemainās, PostgreSQL atsakās pieņemt jaunas rakstīšanas transakcijas, lai pasargātu jūsu datus. Labā ziņa ir tā, ka tas ir paredzams, viegli uzraugāms un, ja to pamanāt laikus, pilnībā novēršams.
Kas ir wraparound, vienkāršiem vārdiem
Katra transakcija, kas raksta, saņem transakcijas ID (XID). PostgreSQL izmanto XID, lai noteiktu, kuras rindu versijas katra transakcija redz. XID ir 32 bitu skaitlis, tāpēc, kā skaidro dokumentācija, ID tiek salīdzināti pa apli: jebkuram XID aptuveni divi miljardi ir "pagātnē" un divi miljardi "nākotnē". Ja veca rindas versija saglabātu savu sākotnējo XID ilgāk par diviem miljardiem transakciju, tā pēkšņi izskatītos kā no nākotnes un kļūtu neredzama. Dati joprojām būtu diskā, bet jūs tos neredzētu.
VACUUM to novērš, iesaldējot vecās rindas: atzīmējot tās kā apstiprinātas tik sen, ka tās redz visi, neatkarīgi no skaitītāja. Tātad noteikums ir vienkāršs: katra tabula katrā datubāzē ir jāapstrādā ar VACUUM un tās vecās rindas jāiesaldē vismaz reizi divos miljardos transakciju.
Drošības tīkls un kad tas beidzas
PostgreSQL seko vecākā neiesaldētā XID vecumam katrā tabulā (relfrozenxid) un katrā datubāzē (datfrozenxid):
- Pie
autovacuum_freeze_max_age(noklusējums 200 miljoni) autovacuum tabulai palaiž anti-wraparound vakuumu, pat ja autovacuum ir izslēgts.pg_stat_activitytas redzams ar "(to prevent wraparound)" vaicājuma tekstā. - Pie
vacuum_failsafe_age(noklusējums 1,6 miljardi) VACUUM pārslēdzas uz savu "pēdējā līdzekļa stratēģiju": pārstāj ievērot izmaksu aizkaves, izlaiž nebūtisku darbu, piemēram, indeksu tīrīšanu, un var izmantot visu shared buffers. Tas grib pabeigt iesaldēšanu pēc iespējas ātrāk. - 40 miljonus transakciju pirms robežas sākas brīdinājumi:
must be vacuumed within N transactions. - 3 miljonus transakciju pirms robežas PostgreSQL pārstāj piešķirt jaunus XID:
ERROR: database is not accepting commands that assign new transaction IDs to avoid wraparound data loss. Tikai lasīšanas vaicājumi joprojām darbojas; ievietošana, atjaunināšana, dzēšana un TRUNCATE izgāžas.
Pie 200 miljoniem pat noslogota sistēma vēl ir ļoti tālu no problēmām. Šīs atstarpes jēga ir dot autovacuum laiku strādāt. Wraparound incidenti notiek, kad kaut kas ilgstoši neļauj autovacuum šo darbu pabeigt.
1. solis: izmēriet XID vecumu
Katrai datubāzei:
SELECT datname, age(datfrozenxid) AS xid_age,
round(100.0 * age(datfrozenxid) / 2000000000, 1) AS pct_to_limit
FROM pg_database
ORDER BY xid_age DESC;
Katrai tabulai, ieskaitot TOAST tabulas, balstoties uz dokumentācijas vaicājumu:
SELECT c.oid::regclass AS table_name,
greatest(age(c.relfrozenxid), age(t.relfrozenxid)) AS xid_age,
pg_size_pretty(pg_total_relation_size(c.oid)) AS size
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm')
ORDER BY xid_age DESC
LIMIT 20;
Datubāze, kas pastāvīgi ir virs 200 miljoniem un pēc anti-wraparound vakuumiem nekad nenokrītas, ir modelis, par kuru jāuztraucas. Tas nozīmē, ka vakuumi darbojas, bet nespēj pavirzīt iesaldēšanas robežu, vai dažas milzīgas tabulas nekad netiek pabeigtas.
2. solis: atrodiet, kas aiztur robežu
VACUUM var iesaldēt tikai tās rindas, kas nevienai strādājošai transakcijai vairs nevar būt vajadzīgas. Jebkas, kas uztur dzīvu vecu momentuzņēmumu, notur robežu vecu, lai cik bieži vakuums darbotos. Tipiskie aizdomās turamie ir tieši tie, ko dokumentācija min savos atgūšanas soļos:
-- old transactions
SELECT pid, state, age(backend_xid) AS xid_age, age(backend_xmin) AS xmin_age,
now() - xact_start AS running_for, left(query, 60) AS query
FROM pg_stat_activity
WHERE backend_xid IS NOT NULL OR backend_xmin IS NOT NULL
ORDER BY greatest(age(backend_xid), age(backend_xmin)) DESC
LIMIT 10;
-- forgotten prepared transactions
SELECT gid, prepared, age(transaction) AS xid_age FROM pg_prepared_xacts;
-- replication slots holding back the horizon
SELECT slot_name, active, age(xmin) AS xmin_age, age(catalog_xmin) AS catalog_xmin_age
FROM pg_replication_slots;
- Ilgstošas vai idle-in-transaction sesijas. Atskaite, kas darbojas diennakti, vai lietotnes savienojums, kas atvēra transakciju un apklusa.
- Sagatavotās transakcijas no divfāžu commit, kuras neviens nav apstiprinājis vai atritinājis. Tās pārdzīvo pārstartēšanu, un tās ir viegli aizmirst, jo lielākā daļa komandu nezina, ka tās izmanto.
- Replikācijas sloti replikai vai CDC patērētājam, kas ir pazudis vai tālu atpaliek. Slots ar vecu
xminvaicatalog_xminaiztur tīrīšanu visam klasterim. Tie paši sloti var arī piepildīt disku ar WAL. hot_standby_feedbackrezerves serverī ar gariem vaicājumiem, kas nodod rezerves servera veco momentuzņēmumu atpakaļ primārajam. Šo kompromisu aplūkojām mūsu ceļvedī par PostgreSQL replikācijas nobīdi.
3. solis: ja rakstīšana jau ir apstājusies
Pašreizējā PostgreSQL dokumentācija skaidri saka, ka serveri nevajag apturēt un pāriet vienlietotāja režīmā, kā ieteica vecāki padomi. VACUUM joprojām darbojas normāli. Ieteicamā secība:
- Apstipriniet vai atritiniet vecās sagatavotās transakcijas (
pg_prepared_xacts). - Pabeidziet ilgstošas atvērtas transakcijas, ja nepieciešams, ar
pg_terminate_backend. - Dzēsiet vecus replikācijas slotus, kas pieder serveriem, kuru vairs nav vai kuri ilgi nav darbojušies. Ja dzēšat slotu, ko vēl izmanto dzīva replika, šī replika var būt jāpārbūvē, tāpēc vispirms pārbaudiet.
- Palaidiet
VACUUMskartajā datubāzē kā superlietotājs, lai tas apstrādātu arī sistēmas katalogus. Vienkāršākais ir VACUUM visai datubāzei; lai ietaupītu laiku, vispirms apstrādājiet tabulas ar vecāko XID vecumu.
No divām lietām jāizvairās: VACUUM FULL vajag jaunu XID, un tas izgāzīsies, bet VACUUM FREEZE veic vairāk darba, nekā vajag, lai atgrieztos darbā. Vienlietotāja režīms ir jēgpilns tikai tad, ja gribat ar DROP vai TRUNCATE noņemt lielas nevajadzīgas tabulas, lai tās nebūtu jāapstrādā ar vakuumu, un tas izslēdz wraparound aizsardzību, tāpēc ir riskantāks.
Klasiska kļūda šī incidenta laikā ir atcelt anti-wraparound autovacuum, jo tas "patērē pārāk daudz I/O" vai bloķē izlaišanu, un pēc tam no nulles sākt manuālu VACUUM uz vairāku terabaitu tabulas. Vispirms noskaidrojiet, kas aiztur robežu; vakuums, kas to nevar pavirzīt, nepalīdzēs, lai cik ātri tas darbotos. Ja rakstīšana produkcijā jau izgāžas, šis ir labs brīdis piesaistīt ārkārtas DBA, nevis apgūt atgūšanas secību zem spiediena.
Kā to novērst
- Ļaujiet autovacuum tikt līdzi. Lielām, noslogotām tabulām noklusējuma ierobežojums bieži ir par lēnu. Palieliniet
autovacuum_vacuum_cost_limit, ja vajag, pievienojiet darbiniekus un izmantojiet tabulu līmeņa iestatījumus lielākajām tabulām. Parametrus aplūko mūsu autovacuum noskaņošanas ceļvedis. - Ierobežojiet, cik ilgi transakcijas var palikt atvērtas. Iestatiet
idle_in_transaction_session_timeoutun dodiet atskaišu lietotājiemstatement_timeout. - Sakopiet slotus un sagatavotās transakcijas. Brīdiniet par neaktīviem replikācijas slotiem un par jebkuru sagatavoto transakciju, kas vecāka par dažām minūtēm. Iestatiet
max_prepared_transactions = 0, ja neizmantojat divfāžu commit. - Sekojiet tabulām, kurās tikai ievieto datus. Tabulām, kurām dati tikai tiek pievienoti, nevajag vakuumu mirušo rindu dēļ, bet tās joprojām jāiesaldē. To pirmais nopietnais vakuums mēdz būt anti-wraparound, bieži vissliktākajā brīdī un uz lielākās tabulas.
- Atcerieties par multixact. Rindu bloķējumi, ko kopīgi tur vairākas transakcijas, izmanto multixact ID, kas arī ir 32 bitu un prasa tādu pašu uzmanību. Pārbaudiet
mxid_age(relminmxid)kopā ar XID vecumu.
Par ko brīdināt
age(datfrozenxid)katrai datubāzei, ar brīdinājumu ap 500 miljoniem un kritisku brīdinājumu krietni pirms 1 miljarda. Pielāgojiet tam, cik ātri patērējat XID.- Vecākās transakcijas un vecākā
backend_xminvecumspg_stat_activity. - Neaktīvi replikācijas sloti un to
xminvecums. - Jebkurš
must be vacuumed withinziņojums žurnālā, kam nekavējoties jāmodina dežurants. - Anti-wraparound autovacuum, kas stundām ilgi darbojas uz vienas tabulas.
Galvenās atziņas
- Katra tabula ar VACUUM jāiesaldē vismaz reizi divos miljardos transakciju. PostgreSQL brīdina 40 miljonus pirms robežas un aptur jaunu rakstīšanu 3 miljonus pirms tās.
- Wraparound incidentus gandrīz vienmēr izraisa kaut kas, kas bloķē iesaldēšanu: vecas transakcijas, aizmirstas sagatavotās transakcijas, novecojuši replikācijas sloti vai rezerves servera atgriezeniskā saite.
- Lai atgūtos, noņemiet šos bloķētājus un palaidiet parastu VACUUM kā superlietotājs. Neizmantojiet vienlietotāja režīmu, VACUUM FULL vai VACUUM FREEZE bez konkrēta iemesla.
- Neatceliet anti-wraparound autovacuum, nesaprotot, kāpēc tas ir lēns.
- Uzraugiet XID vecumu, transakciju vecumu un slotus. Wraparound ir redzams nedēļām iepriekš.