Capire perché una query che funziona bene in fase di sviluppo e test esploda in produzione a volte può essere una sfida. Continua a leggere per saperne di più su alcune funzionalità che possono fornire informazioni dettagliate sull'andamento delle tue query in produzione.
Query attualmente in esecuzione
Quando un client si connette a un server PostgreSQL, il processo del server Postgres principale (storicamente chiamato postmaster ) genera un nuovo processo (chiamato backend ) per soddisfare le richieste del cliente. Ogni back-end, quindi, sta aspettando che il suo client invii una query o sta tentando di eseguirne una.
La vista di sistema pg_stat_activity mostra informazioni su ogni backend attualmente in esecuzione. In particolare, mostra la query che il backend sta attualmente eseguendo se attiva, o l'ultima query eseguita se è in attesa che il client invii un'altra query.
Ecco due backend che servono i client collegati al database testdb
, con entrambi che eseguono attivamente le loro query:
testdb=# select usename,datname,state,query from pg_stat_activity where datname='testdb';
-[ RECORD 1 ]-----------------------------------------------------------------------------
usename | postgres
datname | testdb
state | active
query | SELECT pg_sleep(10);
-[ RECORD 2 ]-----------------------------------------------------------------------------
usename | postgres
datname | testdb
state | active
query | select usename,datname,state,query from pg_stat_activity where datname='testdb';
A volte la query potrebbe essere in attesa di un blocco e anche questo viene visualizzato inpg_stat_activity. Puoi vedere un INSERT in attesa di un blocco di relazione qui:
testdb=# select wait_event_type, wait_event, left(query, 60) from pg_stat_activity where datname='testdb';
-[ RECORD 1 ]---+-------------------------------------------------------------
wait_event_type | Client
wait_event | ClientRead
left | lock table t in access exclusive mode;
-[ RECORD 2 ]---+-------------------------------------------------------------
wait_event_type |
wait_event |
left | select wait_event_type, wait_event, left(query, 60) from pg_
-[ RECORD 3 ]---+-------------------------------------------------------------
wait_event_type | Lock
wait_event | relation
left | insert into t values (1);
Per ulteriori informazioni su pg_stat_activity, vedere i documenti.
Sebbene questa visualizzazione sia utile per capire cosa sta facendo attualmente Postgres, non fornisce informazioni sulle statistiche di esecuzione delle query o informazioni sulle query che hanno terminato l'esecuzione.
Tutte le query vengono eseguite nel passato
Per questo, l'estensione pg_stat_statements è prezioso. Questa estensione è inclusa nella distribuzione principale di PostgreSQL ed è disponibile anche su servizi gestiti come AWS RDS e GCP SQL.
pg_stat_statements (PSS) è una "estensione" in termini di PostgreSQL e deve essere prima installato:
- Consulta la documentazione della tua distribuzione Linux per vedere se l'estensione è preinstallata o se richiede l'installazione di un altro pacchetto. Ad esempio, su Centos 7 dovrai
sudo yum install postgresql-contrib
. - Modifica il file di configurazione principale postgresql.conf (in genere in
/etc
, come/etc/postgresql/10/main/postgresql.conf
su Debian) e cambia il valore dishared_preload_libraries
a “pg_stat_statements”. Questo è un elenco di valori separati da virgole, quindi se c'è già qualcosa, aggiungi una virgola e poi "pg_stat_statements". - Per AWS RDS, dovrai modificare il tuo gruppo di parametri attivo e impostare il valore.
- Dopo aver modificato "shared_preload_libraries", dovrai riavviare il demone PostgreSQL. Purtroppo non c'è modo di aggirare questo. Su AWS RDS, dovrai riavviare l'istanza RDS.
- Dopo un riavvio, il server PostgreSQL avrebbe caricato la libreria condivisa e possiamo installare l'estensione eseguendo
CREATE EXTENSION pg_stat_statements
. Devi essere un superutente per eseguire questo comando. - Puoi effettivamente installare l'estensione in qualsiasi database e tuttavia visualizzare le query su tutti i database.
Una volta installata l'estensione, puoi interrogare la vista chiamatapg_stat_statements
per ottenere informazioni su ogni singola query eseguita dall'installazione dell'estensione.
I numeri, come il tempo impiegato per eseguire la query, vengono accumulati come somma. Per il solo tempo di esecuzione della query, vengono presentate alcune statistiche (media, min, max, deviazione standard). Questi valori possono essere cancellati usando la funzionepg_stat_statements_reset
.
Ecco come una riga da pg_stat_statements
assomiglia a:
testdb=# select * from pg_stat_statements where query like '%pg_sleep%' and dbid=42548;
-[ RECORD 1 ]-------+--------------------
userid | 10
dbid | 42548
queryid | 2649515222348904837
query | SELECT pg_sleep($1)
calls | 1
total_time | 10016.782625
min_time | 10016.782625
max_time | 10016.782625
mean_time | 10016.782625
stddev_time | 0
rows | 1
shared_blks_hit | 0
shared_blks_read | 0
shared_blks_dirtied | 0
shared_blks_written | 0
local_blks_hit | 0
local_blks_read | 0
local_blks_dirtied | 0
local_blks_written | 0
temp_blks_read | 0
temp_blks_written | 0
blk_read_time | 0
blk_write_time | 0
Oltre ai parametri identificativi (utente, database, query), puoi capire molte cose interessanti sulla tua query:
- Quanto tempo ci vuole per l'esecuzione in genere (
mean_time
) - Quante righe restituisce in media (
rows
/calls
) - La quantità di dati letti dalla cache del buffer condivisa e la quantità di dati letti dal disco (il
shared_blks_read
mostra la quantità totale di dati letti dalla query, di cuishared_blks_hit
proveniva dalla cache) - La quantità di dati che doveva essere scritta sul disco in modo sincrono a causa della pressione della cache (
shared_blks_written
) - La quantità di dati scritti, come il numero di blocchi toccati(
shared_blks_dirtied
) - La quantità di tempo trascorso in letture e scritture su disco (
blk_{read,write}_time
) - File temporanei scritti e letti da (
temp_blks_{read,written}
) - Tabelle temporanee scritte e lette da (
local_*
)
I tempi di lettura e scrittura del disco sono disponibili solo se il parametro di configurazionetrack_io_timing
è acceso. Per impostazione predefinita, non lo è. Sulla maggior parte dei moderni sistemi Linux dovrebbe essere possibile attivare questo parametro. Leggi di più.
Vale la pena eseguire uno snapshot di pg_stat_statements
dati continuamente a intervalli regolari per vedere l'andamento di questi parametri in base alla query. Lo strumento open source pgmetrics può estrarre ed esporre pg_stat_statements
dati come JSON per semplificare l'automazione.
Le query vengono eseguite durante un intervallo di tempo
Una volta installato un tale sistema, diventa facile tenere traccia delle query eseguite in un determinato periodo di tempo. Ciò semplifica il debug di problemi come il motivo per cui il lavoro batch notturno ha richiesto più tempo del previsto.
Sottraendo i contatori tra due timestamp dati, puoi trovare la maggior parte dei numeri come prima, ad eccezione della deviazione minima, massima e standard. Questo è sufficiente per identificare le query che sono state eseguite nell'intervallo di tempo e le risorse che hanno consumato.
Registrazione delle query lente
Un altro modo per identificare rapidamente le query che richiedono più tempo del previsto è attivare la registrazione delle istruzioni. È possibile specificare una durata di soglia e, se la query impiega più tempo per terminare, viene registrata. (Nel normale file di registro di PostgreSQL, non ce n'è uno separato per le query lente.)
Per attivare questa funzione, modifica la configurazione come di seguito:
log_min_duration_statement = 1000 # in milliseconds
e ricarica Postgres. Puoi anche usare ALTER SYSTEM
:
ALTER SYSTEM SET log_min_duration_statement = 1000; -- in milliseconds
Con questo, qualsiasi istruzione (comprese quelle non DML) che impiega più di un secondo per finire viene registrata:
2019-12-02 16:57:05.727 UTC [8040] postgres@testdb LOG: duration: 10017.862 ms statement: SELECT pg_sleep(10);
Viene registrato il tempo effettivo impiegato dalla query, nonché il testo SQL completo.
Se disponi di un sistema di monitoraggio dei registri e puoi monitorare il numero di query lente all'ora/al giorno, può fungere da buon indicatore delle prestazioni dell'applicazione.
Piani di esecuzione delle query
Dopo aver individuato una query che ritieni debba essere eseguita più velocemente, il passaggio successivo consiste nel dare un'occhiata al suo piano di query. In genere, è necessario utilizzare il piano di query effettivo dei server di produzione. Se sei in grado di eseguire EXPLAIN su server di produzione così bene, altrimenti devi fare affidamento su auto_explain
.
auto_explain
è un'altra estensione di base di PostgreSQL, già installata o disponibile come pacchetto "contrib" per la tua distribuzione. È disponibile anche su AWSRDS. auto_explain
è un po' più semplice da installare rispetto a pg_stat_statements
:
- Modifica la configurazione di postgres (o il gruppo di parametri RDS)
shared_preload_libraries
per includereauto_explain
. - Non devi però riavviare Postgres, puoi invece semplicemente eseguire:
LOAD 'auto_explain';
. - Ti consigliamo di configurare le sue impostazioni, almeno questa:
auto_explain.log_min_duration = 1000 # seconds
In sostanza, ogni volta che una query richiede più tempo di auto_explain.log_min_duration
numero di secondi per il completamento, auto_explain registra la query e il suo piano di esecuzione della query nel file di registro, in questo modo:
2019-12-04 09:23:05.130 UTC [12823] postgres@testdb LOG: duration: 11025.765 ms plan:
Query Text: select pg_sleep(11);
Result (cost=0.00..0.01 rows=1 width=4) (actual time=11025.716..11025.718 rows=1 loops=1)
Output: pg_sleep('11'::double precision)
Può registrare il piano anche in formato JSON, se disponi di script in grado di elaborarlo:
2019-12-02 17:30:53.676 UTC [8040] postgres@testdb LOG: duration: 10000.230 ms plan:
{
"Query Text": "SELECT pg_sleep(10);",
"Plan": {
"Node Type": "Result",
"Parallel Aware": false,
"Startup Cost": 0.00,
"Total Cost": 0.01,
"Plan Rows": 1,
"Plan Width": 4,
"Actual Startup Time": 10000.205,
"Actual Total Time": 10000.206,
"Actual Rows": 1,
"Actual Loops": 1,
"Output": ["pg_sleep('10'::double precision)"],
"Shared Hit Blocks": 0,
"Shared Read Blocks": 0,
"Shared Dirtied Blocks": 0,
"Shared Written Blocks": 0,
"Local Hit Blocks": 0,
"Local Read Blocks": 0,
"Local Dirtied Blocks": 0,
"Local Written Blocks": 0,
"Temp Read Blocks": 0,
"Temp Written Blocks": 0,
"I/O Read Time": 0.000,
"I/O Write Time": 0.000
},
"Triggers": [
]
}
In Postgres, non c'è altro modo che auto_explain per esaminare il piano di esecuzione di una query che è già stata eseguita, il che rende auto_explain uno strumento importante nella tua cassetta degli attrezzi.