PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Quando l'autovuoto non aspira

Qualche settimana fa ho spiegato le basi della messa a punto dell'autovacuum. Alla fine di quel post ho promesso di esaminare presto i problemi con l'aspirapolvere. Bene, ci è voluto un po' più tempo del previsto, ma eccoci qui.

Per ricapitolare velocemente, autovacuum è un processo in background che ripulisce le righe morte, ad es. vecchie versioni di righe eliminate. Puoi anche eseguire la pulizia manualmente eseguendo VACUUM , ma autovacuum lo fa automaticamente a seconda della quantità di righe morte nella tabella, al momento giusto, non troppo spesso ma abbastanza frequentemente da tenere sotto controllo la quantità di "spazzatura".

In generale, autovacuum non può essere eseguito troppo spesso:la pulizia viene eseguita solo dopo aver raggiunto un certo numero di righe morte accumulate nella tabella. Ma può essere ritardato per vari motivi, con il risultato che tabelle e indici diventano più grandi di quanto desiderabile. E questo è esattamente l'argomento di questo post. Quindi quali sono i colpevoli comuni e come identificarli?

Limitazione

Come spiegato nelle nozioni di base sull'ottimizzazione, autovacuum i lavoratori sono limitati a eseguire solo una certa quantità di lavoro per intervallo di tempo. I limiti predefiniti sono piuttosto bassi:circa 4 MB/s di scritture, 8 MB/s di letture. È adatto per macchine minuscole come Raspberry Pi o piccoli server di 10 anni fa, ma le macchine attuali sono molto più potenti (sia in termini di CPU che di I/O) e gestiscono molti più dati.

Immagina di avere alcuni tavoli grandi e alcuni piccoli. Se tutti e tre autovacuum i lavoratori iniziano a pulire i tavoli grandi, nessuno dei tavolini verrà aspirato indipendentemente dalla quantità di file morte che accumulano. Identificare questo non è particolarmente difficile, supponendo che tu abbia un monitoraggio sufficiente. Cerca i periodi in cui tutti autovacuum i lavoratori sono occupati mentre i tavoli non vengono aspirati nonostante si accumulino molte file morte.

Tutte le informazioni necessarie sono in pg_stat_activity (numero di autovacuum processi di lavoro) e pg_stat_all_tables (last_autovacuum e n_dead_tup ).

Aumentando il numero di autovacuum lavoratori non è una soluzione, poiché la quantità totale di lavoro rimane la stessa. Puoi specificare limiti di limitazione per tabella, escludendo quel lavoratore dal limite totale, ma ciò non garantisce comunque che ci saranno lavoratori disponibili quando necessario.

La soluzione giusta è l'ottimizzazione del throttling, utilizzando limiti ragionevoli rispetto alla configurazione hardware e ai modelli di carico di lavoro. Alcuni consigli di base sulla limitazione sono menzionati nel post precedente. (Ovviamente, se puoi ridurre la quantità di righe morte generate nel database, sarebbe una soluzione ideale.)

Da questo punto assumiamo che il problema non sia il throttling, ovvero che l'autovacuum i lavoratori non sono saturati per lunghi periodi di tempo e che la pulizia viene attivata su tutti i tavoli senza ritardi irragionevoli.

Transazioni lunghe

Quindi, se il tavolo viene pulito regolarmente, sicuramente non può accumulare molte file morte, giusto? Sfortunatamente no. Le righe non sono effettivamente "rimovibili" subito dopo essere state eliminate, ma solo quando non ci sono transazioni che potrebbero vederle. Il comportamento esatto dipende da cosa stanno (stavano) facendo le altre transazioni e dal livello di serializzazione, ma in generale:

LEGGERE IMPEGNATO

  • Ripulitura del blocco delle query in esecuzione
  • Ripulitura del blocco delle transazioni inattive solo se hanno eseguito una scrittura
  • Le transazioni inattive (senza scritture) non bloccheranno la pulizia (ma non è una buona pratica tenerle comunque in giro)

SERIALIZZABILE

  • Ripulitura del blocco delle query in esecuzione
  • Ripulitura del blocco delle transazioni inattive (anche se eseguivano solo letture)

In pratica è ovviamente più sfumato, ma spiegare tutti i vari bit richiederebbe prima di spiegare come funzionano gli XID e gli snapshot, e questo non è l'obiettivo di questo post. Ciò che dovresti davvero togliere a questo è che le transazioni lunghe sono una cattiva idea, in particolare se quelle transazioni potrebbero aver eseguito scritture.

Naturalmente, ci sono ragioni perfettamente valide per cui potrebbe essere necessario mantenere le transazioni per lunghi periodi di tempo (ad esempio se è necessario garantire ACID per tutte le modifiche). Ma assicurati che non succeda inutilmente, ad es. a causa di un design dell'applicazione scadente.

Una conseguenza alquanto inaspettata di ciò è l'elevato utilizzo di CPU e I/O, dovuto a autovacuum correndo più e più volte, senza pulire le file morte (o solo alcune di esse). Per questo motivo i tavoli possono ancora essere ripuliti nel round successivo, causando più danni che benefici.

Come rilevarlo? In primo luogo, è necessario monitorare le transazioni di lunga durata, in particolare quelle inattive. Tutto quello che devi fare è leggere i dati da pg_stat_activity . La definizione della vista cambia leggermente con la versione di PostgreSQL, quindi potrebbe essere necessario modificarla un po':

SELECT xact_start, state FROM pg_stat_activity;

-- count 'idle' transactions longer than 15 minutes (since BEGIN)
SELECT COUNT(*) FROM pg_stat_activity
 WHERE state = 'idle in transaction'
  AND (now() - xact_start) > interval '15 minutes'

-- count transactions 'idle' for more than 5 minutes
SELECT COUNT(*) FROM pg_stat_activity
 WHERE state = 'idle in transaction'
  AND (now() - state_change) > interval '5 minutes'

Puoi anche semplicemente utilizzare alcuni plug-in di monitoraggio esistenti, ad es. check_postgres.pl. Quelli includono già questo tipo di controlli di integrità. Dovrai decidere qual è una durata ragionevole della transazione/della query, che è specifica dell'applicazione.

Da PostgreSQL 9.6 puoi anche usare idle_in_transaction_session_timeout in modo che le transazioni inattive per troppo tempo vengano terminate automaticamente. Allo stesso modo, per le query lunghe c'è statement_timeout .

Un'altra cosa utile è VACUUM VERBOSE che in realtà ti dirà quante righe morte non possono essere ancora rimosse:

db=# VACUUM verbose z;
INFO:  vacuuming "public.z"
INFO:  "z": found 0 removable, 66797 nonremovable row versions in 443 out of 443 pages
DETAIL:  12308 dead row versions cannot be removed yet.
...

Non ti dirà quale backend sta impedendo la pulizia, ma è un segno abbastanza chiaro di ciò che sta accadendo.

Nota: . Non puoi ottenere facilmente queste informazioni da autovacuum perché è registrato solo con DEBUG2 per impostazione predefinita (e sicuramente non vorrai eseguire con quel livello di registro in produzione).

Query lunghe in hot standby

Supponiamo che le tabelle vengano aspirate in modo tempestivo, ma non rimuovano le tuple morte, con conseguente rigonfiamento della tabella e dell'indice. Stai monitorando pg_stat_activity e non ci sono transazioni di lunga durata. Quale potrebbe essere il problema?

Se si dispone di una replica in streaming, è probabile che il problema possa essere presente. Se la replica utilizza hot_standby_feedback=on , le query sulla replica agiscono più o meno come transazioni sul primario, inclusa la pulizia del blocco. Naturalmente, hot_standby_feedback=on viene utilizzato esattamente durante l'esecuzione di query lunghe (ad es. analisi e carichi di lavoro BI) sulle repliche, per evitare annullamenti dovuti a conflitti di replica.

Sfortunatamente, dovrai scegliere:mantieni hot_standby_feedback=on e accettare ritardi nella pulizia o gestire le query annullate. Potresti anche usare max_standby_streaming_delay per limitare l'impatto, anche se ciò non impedisce del tutto le cancellazioni (quindi devi comunque riprovare le query).

In realtà, ora c'è una terza opzione:la replica logica. Invece di utilizzare la replica di streaming fisica per la replica BI, è possibile copiare le modifiche utilizzando la nuova replica logica, disponibile in PostgreSQL 10. La replica logica rilassa l'accoppiamento tra primaria e replica e rende i cluster per lo più indipendenti (vengono ripuliti in modo indipendente, ecc.).

Ciò risolve i due problemi associati alla replica fisica del flusso:la pulizia ritardata sulle query primarie o annullata sulla replica BI. Tuttavia, per le repliche che servono a scopi di ripristino di emergenza, la replica in streaming resta la scelta giusta. Ma quelle repliche non stanno (o non dovrebbero) eseguire query lunghe.

Nota: Mentre ho menzionato che la replica logica sarà disponibile in PostgreSQL 10, una parte significativa dell'infrastruttura era disponibile nelle versioni precedenti (in particolare PostgreSQL 9.6). Quindi potresti essere in grado di farlo anche su versioni precedenti (l'abbiamo fatto per alcuni dei nostri clienti), ma PostgreSQL 10 lo renderà molto più comodo e comodo.

Problemi con autoanalyze

Un dettaglio che potresti perdere è che autovacuum i lavoratori svolgono effettivamente due compiti diversi. Innanzitutto la pulizia (come se si stesse eseguendo VACUUM ), ma anche raccogliere statistiche (come se si stesse eseguendo ANALYZE ). E entrambi le parti vengono limitate utilizzando autovacuum_cost_limit .

Ma c'è una grande differenza nella gestione delle transazioni. Ogni volta che il VACUUM la parte raggiunge autovacuum_cost_limit , il lavoratore rilascia l'istantanea e dorme per un po'. Il ANALYZE tuttavia deve essere eseguito in un'unica istantanea/transazione, cosa che fa pulizia del blocco.

Questo è un modo elegante per spararti ai piedi, in particolare se fai anche parte di questo:

  • aumentare default_statistics_target per creare statistiche più accurate da campioni più grandi
  • inferiore autovacuum_analyze_scale_factor per raccogliere statistiche più frequentemente

La conseguenza non intenzionale ovviamente è che ANALYZE accadrà più frequentemente, richiederà molto più tempo e (a differenza di VACUUM parte) impedire la pulizia. La soluzione di solito è abbastanza semplice:non abbassare autovacuum_analyze_scale_factor troppo. Esecuzione di ANALYZE ogni volta che il 10% delle modifiche alla tabella dovrebbe essere più che sufficiente nella maggior parte dei casi.

n_dead_tup

Un'ultima cosa che vorrei menzionare riguarda le modifiche in pg_stat_all_tables.n_dead_tup valori. Potresti pensare che il valore sia un semplice contatore, incrementato ogni volta che viene creata una nuova tupla morta e decrementato ogni volta che viene ripulita. Ma in realtà è solo una stima del numero di tuple morte, aggiornata da ANALYZE . Per i tavoli piccoli (meno di 240 MB) non è una grande differenza, perché ANALYZE legge l'intera tabella e quindi è abbastanza esatto. Per le tabelle di grandi dimensioni, tuttavia, può cambiare leggermente a seconda del sottoinsieme di tabelle che viene campionato. E abbassando autovacuum_vacuum_scale_factor lo rende più casuale.

Quindi fai attenzione quando guardi n_dead_tup in un sistema di monitoraggio. Improvvisi cali o aumenti del valore possono essere semplicemente dovuti a ANALYZE ricalcolare una stima diversa e non a causa della pulizia effettiva e/o della comparsa di nuove tuple morte nella tabella.

Riepilogo

Per riassumere questo in pochi semplici punti:

  • autovacuum può fare il suo lavoro solo se non ci sono transazioni che potrebbero aver bisogno delle tuple morte.
  • Le query di lunga durata eseguono la pulizia dei blocchi. Prendi in considerazione l'utilizzo di statement_timeout per limitare i danni.
  • La transazione di lunga durata potrebbe bloccare la pulizia. Il comportamento esatto dipende da cose come il livello di isolamento o da cosa è successo nella transazione. Monitorarli e terminarli se possibile.
  • Query di lunga durata sulle repliche con hot_standby_feedback=on potrebbe anche bloccare la pulizia.
  • autoanalyze è anch'esso limitato, ma a differenza di VACUUM parte mantiene una singola istantanea (e quindi blocca la pulizia).
  • n_dead_tup è solo una stima gestita da ANALYZE , quindi aspettati qualche fluttuazione (soprattutto su tavoli grandi).