Incidentu reaģēšana

PostgreSQL "canceling statement due to statement timeout": cēloņi un risinājumi

Šī kļūda nozīmē, ka kāds iestatīts taimauts ir izdarījis savu darbu. Īstais darbs ir noskaidrot, kurš limits nostrādāja, kur tas iestatīts un kāpēc vaicājums izpildījās tik ilgi.

· 14 min lasīšanai · AG Data komanda

ERROR: canceling statement due to statement timeout ir PostgreSQL ziņojums, ka vaicājums izpildījās ilgāk, nekā atļauj statement_timeout, un tika atcelts. Datubāzē nekas nav salūzis. Limits nostrādāja, kā paredzēts. Noderīgie jautājumi ir: kurš limits nostrādāja, kas to iestatīja un kāpēc vaicājumam vajadzēja vairāk laika nekā agrāk.

Vispirms izlasiet precīzu ziņojumu

Vaicājumus atceļ vairāki dažādi mehānismi, un to ziņojumi no pirmā acu uzmetiena izskatās līdzīgi. Ja tos atšķir, nav jāmin:

ERROR:  canceling statement due to statement timeout   -- SQLSTATE 57014
ERROR:  canceling statement due to lock timeout        -- SQLSTATE 55P03
ERROR:  canceling statement due to user request        -- SQLSTATE 57014
FATAL:  terminating connection due to transaction timeout   -- SQLSTATE 25P04

Lietotnēm svarīgi ir SQLSTATE kodi: statement timeout un lietotāja atcelšanai ir kopīgs kods 57014 (query_canceled), bet lock timeout ir 55P03 (lock_not_available). Ja atkārtošanas loģika skatās tikai uz vienu kodu, tā var lock timeout uztvert kā fatālu kļūdu vai otrādi.

Hot standby serverī ir ceturtais līdzīgais ziņojums, "canceling statement due to conflict with recovery". Ar jūsu iestatītajiem taimautiem tam nav nekāda sakara. Par to rakstījām rakstā canceling statement due to conflict with recovery.

Atrodiet, no kurienes nāk taimauts

Noklusējuma statement_timeout ir 0, kas nozīmē, ka limita nav. Tātad, ja redzat šo kļūdu, kāds vērtību ir iestatījis. Tā var nākt no postgresql.conf, ALTER DATABASE ... SET, ALTER ROLE ... SET, SET sesijā, savienojuma virknes vai PGOPTIONS, savienojumu pūla sākuma vaicājuma vai ORM. Neminiet, pajautājiet datubāzei:

-- kādu vērtību redz šī sesija un no kurienes tā nāk?
SELECT name, setting, unit, source
FROM pg_settings
WHERE name IN ('statement_timeout', 'lock_timeout', 'transaction_timeout');

-- lomu un datubāzu līmeņa iestatījumi (psql saīsne: \drds)
SELECT r.rolname, d.datname, s.setconfig
FROM pg_db_role_setting s
LEFT JOIN pg_roles r ON r.oid = s.setrole
LEFT JOIN pg_database d ON d.oid = s.setdatabase;

Kolonna source rāda, vai vērtība nāk no konfigurācijas faila, datubāzes, lietotāja vai sesijas. Atcerieties, ka lomas un datubāzes iestatījumi attiecas tikai uz jaunām sesijām. Ilgi dzīvojošs pūls patur veco vērtību, līdz no jauna pieslēdzas. Ja lomas un datubāzes iestatījumi konfliktē, ALTER ROLE dokumentācija nosaka secību: lomas iestatījums konkrētā datubāzē ir svarīgāks par lomas iestatījumu, kas savukārt ir svarīgāks par datubāzes iestatījumu.

Vienu PostgreSQL dokumentācijas brīdinājumu vērts atkārtot: statement_timeout iestatīšana postgresql.conf nav ieteicama, jo tā ietekmē visas sesijas, arī apkopes un rezerves kopiju darbus.

Lēns vaicājums vai gaidīšana uz bloķējumu?

Tā kā gaidīšana uz bloķējumiem tiek ieskaitīta statement_timeout, pilnīgi ātrs UPDATE var sasniegt taimautu, ja tas stāv aiz citas transakcijas. Paskatieties, kas izpildās un ko tas gaida:

SELECT pid, usename, state, wait_event_type, wait_event,
       now() - query_start AS running_for,
       pg_blocking_pids(pid) AS blocked_by,
       left(query, 80) AS query
FROM pg_stat_activity
WHERE state <> 'idle' AND pid <> pg_backend_pid()
ORDER BY query_start
LIMIT 20;

Lai tos pamanītu laika gaitā, pārliecinieties, ka log_min_error_statement ir noklusējuma vērtībā ERROR vai zemāk, tad atceltā vaicājuma teksts nonāk žurnālā. Pievienojiet log_min_duration_statement, lai redzētu vaicājumus, kas tuvojas limitam, un log_lock_waits = on, lai žurnālā rakstītu bloķējumu gaidīšanu, kas ilgāka par deadlock_timeout (noklusējumā 1s).

Risinājumi tādā secībā, kādā mēs tos izmēģinām

  1. Labojiet vaicājumu, nevis limitu. Vaicājumam, kas agrāk aizņēma 2 sekundes, bet tagad 40, parasti ir iemesls: pēc shēmas izmaiņām pazudis indekss, novecojusi statistika, plāna maiņa, tabulai augot, vai tabulas uzpūšanās. Taimauta palielināšana to tikai noslēpj līdz nākamajam incidentam.
  2. Novērsiet bloķētāju. Ja vaicājums gaidīja bloķējumu, labojiet transakciju, kas to tur. Saīsiniet transakcijas lietotnē un pievienojiet lomai idle_in_transaction_session_timeout, lai pamestas sesijas neturētu bloķējumus mūžīgi.
  3. Iestatiet taimautus katrai slodzei, nevis globāli. Tīmekļa pieprasījumiem, atskaitēm un pakešdarbiem vajag dažādus limitus. Piesaistiet tos lomām un palieliniet vienai transakcijai ar SET LOCAL, kas transakcijas beigās atgriežas iepriekšējā vērtībā (piemērs zemāk).
  4. Turiet lock_timeout zemāku par statement_timeout. Dokumentācija norāda, ka lock_timeout, kas vienāds ar vai lielāks par nenulles statement_timeout, ir bezjēdzīgs, jo statement timeout vienmēr nostrādās pirmais. Zems lock timeout liek bloķējumu gaidīšanai ātri beigties ar savu skaidru ziņojumu.
  5. Apstrādājiet kļūdu lietotnē. Atcelts vaicājums pārtrauc pašreizējo transakciju. Atritiniet to un, ja tas ir jēgpilni, atkārtojiet visu transakciju, bet neatkārtojiet uzreiz ciešā ciklā, ja datubāze jau ir pārslogota.
-- tīmekļa trafiks: īss pavadiņš
ALTER ROLE app_web IN DATABASE shop SET statement_timeout = '15s';
ALTER ROLE app_web IN DATABASE shop SET lock_timeout = '3s';

-- atskaišu loma: ilgāk, bet joprojām ar robežu
ALTER ROLE reporting SET statement_timeout = '20min';

-- viens smags darbs, kam tiešām vajag vairāk laika
BEGIN;
SET LOCAL statement_timeout = '3h';
-- ... ilgais vaicājums ...
COMMIT;   -- pēc COMMIT vai ROLLBACK atgriežas sesijas vērtība
Nelabojiet to ar statement_timeout = 0. Globāli izslēdzot limitu, nākamais nevaldāmais vaicājums var izpildīties stundām, turēt bloķējumus un neļaut vacuum iztīrīt mirušās rindas. Palieliniet limitu tikai tai lomai vai transakcijai, kurai tas vajadzīgs.

Taimauti lielās datubāzēs

Datubāzēs ar simtiem gigabaitu vai terabaitiem taimauti praksē uzvedas citādi:

SET lock_timeout = '3s';
ALTER TABLE orders ADD COLUMN note text;
-- ERROR: canceling statement due to lock timeout  -> wait, retry

Kad vaicājums jāaptur pašam

pg_cancel_backend(pid) atceļ pašreizējo vaicājumu un saglabā sesiju. pg_terminate_backend(pid) pārtrauc visu sesiju. Vispirms mēģiniet atcelt. Atcelts rakstīšanas vaicājums tiek atritināts un tā izmaiņas atmestas, bet jau ierakstītās mirušās rindu versijas paliek tabulā, līdz tās noņem vacuum, tāpēc liela atcelta datu aizpildīšana var atstāt manāmu uzpūšanos.

Galvenās atziņas

PostgreSQL statement_timeout lock_timeout Veiktspēja
Sadarbība ar mums

Vaicājumi produkcijā sasniedz taimautus?

Mēs atrodam lēnos vaicājumus un bloķēšanas ķēdes aiz PostgreSQL taimautiem, iestatām saprātīgus limitus katrai lomai un padarām migrācijas lielās tabulās drošas.

Sazināties Vairāk no emuāra