PostgreSQL kļūda "canceling statement due to conflict with recovery" parādās tikai uz hot standby. Tā nozīmē, ka replikai bija jāpiemēro izmaiņa no primārā servera, kurai jūsu vaicājums traucēja, tā gaidīja tik ilgi, cik atļauts, un tad vaicājumu atcēla. Nekas nav bojāts, un replika strādā tā, kā paredzēts. Risinājums ir lēmums: cik lielu replikācijas aizkavi vai cik lielu uzpūšanos (bloat) primārajā serverī esat gatavi pieņemt apmaiņā pret gariem vaicājumiem uz replikas.
Tipiski klients redz šo:
ERROR: canceling statement due to conflict with recovery
DETAIL: User query might have needed to see row versions that must be removed.
Saskaņā ar postgres.c un kļūdu kodu tabulu vaicājums beidzas ar SQLSTATE 40001 (serialization_failure), to pašu kodu, ko lietotnes jau izmanto atkārtojamām transakcijām. Dažos gadījumos tiek pārtraukta visa sesija, ar ziņojumu "terminating connection due to conflict with recovery". Noderīgākā daļa ir DETAIL rinda: tā pasaka, kāda veida konflikts radies.
Kāpēc replika vispār atceļ vaicājumus
Primārajā serverī konfliktējošs darbs vienkārši gaida: DROP TABLE gaida vaicājumu, kas lasa tabulu. Primārais serveris nezina, kas notiek uz tā replikām, tāpēc tās negaida. Replika saņem WAL ierakstu vēlāk un nevar to atteikt, jo izmaiņa jau ir notikusi. Kā teikts hot standby dokumentācijā, replikai ir vai nu jāaizkavē WAL piemērošana un viss, kas seko, vai arī jāatceļ konfliktējošais vaicājums.
Cik ilgi tā aizkavē, nosaka divi parametri, abu noklusējums ir 30 sekundes (replikācijas iestatījumi):
- max_standby_streaming_delay WAL, kas pienāk ar straumēšanas replikāciju;
- max_standby_archive_delay WAL, kas nolasīts no arhīva, piemēram, kamēr replika panāk primāro serveri.
Viena detaļa daudzus pārsteidz: taimeris skaita no brīža, kad replika saņēma WAL, nevis no brīža, kad sākās jūsu vaicājums. Ja replika jau atpaliek, vaicājumu var atcelt krietni ātrāk par 30 sekundēm.
Izlasiet DETAIL rindu
DETAIL teksti nāk tieši no pirmkoda. Praksē sastopamākie:
- "User query might have needed to see row versions that must be removed." Snapshot konflikts, krietni biežākais. VACUUM primārajā serverī izdzēsa mirušās rindas, kuras jūsu garais vaicājums uz replikas vēl varēja redzēt. Pat tas, ka VACUUM atzīmē lapas kā all-visible, var to izraisīt, jo index-only scan ir atkarīgs no redzamības kartes.
- "User was holding a relation lock for too long." Slēdzeņu konflikts. Kaut kas primārajā serverī paņēma
ACCESS EXCLUSIVEslēdzeni: DDL, piemēram,ALTER TABLEvaiDROP, tiešsLOCKvai VACUUM, kas nogriež tukšās lapas tabulas beigās, jo arī tam šī slēdzene ir vajadzīga (VACUUM dokumentācija). - "User was holding shared buffer pin for too long." Bufera piespraudes konflikts: atjaunošanai vajadzēja attīrīt lapu, kuru jūsu vaicājums bija piespraudis.
- "User was connected to a database that must be dropped." Datubāze primārajā serverī tika dzēsta. Sesija tiek pārtraukta ar SQLSTATE
57P04.
Vēl divi noteikumi no dokumentācijas: ja konflikts ir par slēdzeni, ko tur dīkstāvē esoša transakcija, tiek pārtraukta visa sesija, ne tikai vaicājums. Un atcelts vaicājums var lieliski izdoties, ja to vienkārši palaižat vēlreiz jaunā transakcijā.
1. solis: izmēriet, pirms pielāgojat
Skats pg_stat_database_conflicts skaita atcelšanas pa datubāzēm un iemesliem. Tas tiek pildīts tikai uz replikas, tāpēc vaicājiet to tur:
-- palaidiet uz REPLIKAS: skaitītāji tiek pildīti tikai tur
SELECT datname, confl_snapshot, confl_lock, confl_bufferpin,
confl_deadlock, confl_tablespace, confl_active_logicalslot
FROM pg_stat_database_conflicts
ORDER BY confl_snapshot + confl_lock DESC;
Ja dominē confl_snapshot, skatieties uz VACUUM un gariem vaicājumiem. Ja aug confl_lock, skatieties uz DDL un tabulu nogriešanu primārajā serverī. Ieslēdziet arī log_recovery_conflict_waits (PostgreSQL 14+, pēc noklusējuma izslēgts): tad replika žurnālā ieraksta, kad atjaunošana konflikta dēļ gaida ilgāk par deadlock_timeout, un problēma kļūst redzama, pirms vaicājumi sāk krist. Salīdziniet to ar replikācijas aizkaves grafikiem: konflikti un aizkave ir viena kompromisa divas puses.
2. solis: izlemiet, kam replika paredzēta
Šis jautājums nosaka konfigurāciju. Dokumentācijas ieteikums ir skaidrs: replikai, kas pastāv galvenokārt augstas pieejamības dēļ, aizkaves iestatījumiem jābūt īsiem, lai tā nekad stipri neatpaliktu. Replikai gariem vaicājumiem var būt liela vai pat bezgalīga aizkave (-1). Viens serveris reti labi veic abus darbus. Ja viena un tā pati replika ir jūsu failover mērķis un BI datubāze, 40 minūšu atskaite var atstāt to 40 minūtes aizmugurē tieši tad, kad to vajag paaugstināt.
3. solis: izvēlieties risinājumu
Lūk, varianti no lētākās izmaiņas līdz lielākajai. Visi šie iestatījumi atrodas postgresql.conf vai komandrindā, nevis SET:
# postgresql.conf uz replikas, tad: SELECT pg_reload_conf();
log_recovery_conflict_waits = on # PostgreSQL 14+, noklusējums off
max_standby_streaming_delay = 5min # noklusējums 30s, -1 = gaidīt bezgalīgi
max_standby_archive_delay = 5min # noklusējums 30s
hot_standby_feedback = on # noklusējums off
- Atkārtojiet lietotnē. Īsiem vaicājumiem, kas konfliktu sastop tikai reizēm, bieži pietiek ar atkārtošanu pie
40001. Ja jūsu kods jau atkārto serializācijas kļūmes, tas pats ceļš sedz arī šo kļūdu. - Palieliniet max_standby_streaming_delay atskaišu replikai. Vaicājumi saņem vairāk laika, un cena ir replikācijas aizkave, kamēr tie darbojas. Uztveriet galīgu vērtību kā
statement_timeoutvaicājumiem, kas saduras ar atjaunošanu. - Ieslēdziet hot_standby_feedback. Replika paziņo primārajam serverim, kuras rindas tās vaicājumiem vēl vajadzīgas, un VACUUM primārajā serverī tās neaiztiek. Tas novērš snapshot konfliktus, ko rada tīrīšana, taču dokumentācija brīdina, ka tas var uzpūst tabulas primārajā serverī: viena aizmirsta transakcija uz replikas tagad aiztur arī VACUUM primārajā serverī. Pēc ieslēgšanas sekojiet līdzi autovacuum un bloat.
- Pievienojiet fizisku replikācijas slotu, ja replika bieži pārslēdzas. Feedback viens pats neko neaizsargā, kamēr replika nav pieslēgta; slots aizsardzību saglabā. Taču slots saglabā arī WAL, un pamests slots var piepildīt
pg_wal, tāpēc iestatietmax_slot_wal_keep_size(replikācijas sloti). - Nodaliet HA no atskaitēm. Replika ar īsu aizkavi failover vajadzībām un replika ar garu aizkavi vai ieslēgtu feedback analītikai parasti ir tīrākais risinājums.
Neviens no tiem nepalīdz pret slēdzeņu konfliktiem no DDL. Tiem plānojiet migrācijas ārpus smago atskaišu logiem. Ja cēlonis ir VACUUM nogriešana, tabulas glabāšanas opcija vacuum_truncate = false ļauj izvairīties no ACCESS EXCLUSIVE slēdzenes, bet šī vieta netiek atdota OS. PostgreSQL 18 pievieno arī servera līmeņa iestatījumu vacuum_truncate.
Pēc feedback ieslēgšanas vērojiet primāro serveri. Ar hot_standby_feedback replikas horizonts parādās primārajā serverī. Pārbaudiet to regulāri, jo vecs xmin tur nozīmē, ka VACUUM tiek aizturēts:
-- palaidiet uz PRIMĀRĀ servera: kura xmin aiztur VACUUM?
SELECT application_name, client_addr, backend_xmin, age(backend_xmin)
FROM pg_stat_replication;
SELECT slot_name, slot_type, active, xmin, age(xmin)
FROM pg_replication_slots;
Ja age(backend_xmin) turpina augt, atrodiet garo transakciju uz replikas, parasti aizmirstu sesiju pg_stat_activity ar stāvokli idle in transaction, un pārtrauciet to. No mūsu pieredzes, lietotāja līmeņa idle_in_transaction_session_timeout un statement_timeout atskaišu lomām uz replikas to aptur, pirms tas sāp.
Galvenās atziņas
- "canceling statement due to conflict with recovery" ir replika, kas aizsargā WAL atjaunošanu, nevis kļūda vai bojājums.
- Izlasiet DETAIL rindu: snapshot konflikti nāk no VACUUM, slēdzeņu konflikti no DDL, LOCK vai VACUUM nogriešanas.
- Mēriet uz replikas ar
pg_stat_database_conflictsunlog_recovery_conflict_waits. - Aizkaves iestatījumu noklusējums ir 30 sekundes, un tie apmaina vaicājumu laiku pret replikācijas aizkavi.
hot_standby_feedbacknovērš tīrīšanas konfliktus, bet var uzpūst primāro serveri; izmantojiet slotu, ja replika atslēdzas.- Ja iespējams, turiet failover replikas un atskaišu replikas atsevišķi.