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

Un foglio informativo sulle prestazioni per PostgreSQL

Le prestazioni sono una delle attività più importanti e complesse nella gestione di un database. Può essere influenzato dalla configurazione, dall'hardware o anche dal design del sistema. Per impostazione predefinita, PostgreSQL è configurato pensando alla compatibilità e alla stabilità, poiché le prestazioni dipendono molto dall'hardware e dal nostro sistema stesso. Possiamo avere un sistema con molti dati letti ma le informazioni non cambiano frequentemente. Oppure possiamo avere un sistema che scrive continuamente. Per questo motivo, è impossibile definire una configurazione predefinita che funzioni per tutti i tipi di carichi di lavoro.

In questo blog vedremo come analizzare il carico di lavoro, o le query, in esecuzione. Esamineremo quindi alcuni parametri di configurazione di base per migliorare le prestazioni del nostro database PostgreSQL. Come accennato, vedremo solo alcuni dei parametri. L'elenco dei parametri di PostgreSQL è ampio, toccheremo solo alcuni di quelli chiave. Tuttavia, è sempre possibile consultare la documentazione ufficiale per approfondire i parametri e le configurazioni che sembrano più importanti o utili nel nostro ambiente.

SPIEGA

Uno dei primi passi che possiamo fare per capire come migliorare le prestazioni del nostro database è analizzare le query che vengono fatte.

PostgreSQL escogita un piano di query per ogni query che riceve. Per vedere questo piano, useremo EXPLAIN.

La struttura di un piano di query è un albero di nodi del piano. I nodi nel livello inferiore dell'albero sono nodi di scansione. Restituiscono righe grezze da una tabella. Esistono diversi tipi di nodi di scansione per diversi metodi di accesso alla tabella. L'output EXPLAIN ha una riga per ogni nodo nell'albero del piano.

world=# EXPLAIN SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
                               QUERY PLAN                                
--------------------------------------------------------------------------
Nested Loop  (cost=0.00..734.81 rows=50662 width=144)
  ->  Seq Scan on city t1  (cost=0.00..93.19 rows=347 width=31)
        Filter: ((id > 100) AND (population > 700000))
  ->  Materialize  (cost=0.00..8.72 rows=146 width=113)
        ->  Seq Scan on country t2  (cost=0.00..7.99 rows=146 width=113)
              Filter: (population < 7000000)
(6 rows)

Questo comando mostra come verranno scansionate le tabelle nella nostra query. Vediamo a cosa corrispondono questi valori che possiamo osservare nel nostro EXPLAIN.

  • Il primo parametro mostra l'operazione che il motore sta eseguendo sui dati in questo passaggio.
  • Costo di avviamento stimato. Questo è il tempo trascorso prima che la fase di output possa iniziare.
  • Costo totale stimato. Ciò viene affermato presupponendo che il nodo del piano venga eseguito fino al completamento. In pratica, il nodo padre di un nodo potrebbe non leggere tutte le righe disponibili.
  • Numero stimato di righe emesse da questo nodo del piano. Anche in questo caso, si presume che il nodo venga eseguito fino al completamento.
  • Larghezza media stimata delle righe generate da questo nodo del piano.

La parte più critica della visualizzazione è il costo di esecuzione della dichiarazione stimata, che è l'ipotesi del pianificatore su quanto tempo ci vorrà per eseguire la dichiarazione. Quando si confronta l'efficacia di una query rispetto all'altra, in pratica confronteremo i valori di costo di esse.

È importante comprendere che il costo di un nodo di livello superiore include il costo di tutti i suoi nodi figlio. È anche importante rendersi conto che il costo riflette solo le cose che interessano al pianificatore. In particolare il costo non tiene conto del tempo dedicato alla trasmissione delle righe di risultato al cliente, che potrebbe essere un fattore importante nel tempo reale trascorso; ma il pianificatore lo ignora perché non può cambiarlo alterando il piano.

I costi sono misurati in unità arbitrarie determinate dai parametri di costo del pianificatore. La pratica tradizionale consiste nel misurare i costi in unità di recupero delle pagine del disco; cioè, seq_page_cost è convenzionalmente impostato su 1.0 e gli altri parametri di costo sono impostati in relazione a quello.

SPIEGAZIONE ANALISI

Con questa opzione, EXPLAIN esegue la query, quindi visualizza i conteggi di righe reali e il tempo di esecuzione effettivo accumulato all'interno di ciascun nodo del piano, insieme alle stesse stime mostrate da un semplice EXPLAIN.

Vediamo un esempio di utilizzo di questo strumento.

world=# EXPLAIN ANALYZE SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
                                                     QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
Nested Loop  (cost=0.00..734.81 rows=50662 width=144) (actual time=0.081..22.066 rows=51100 loops=1)
  ->  Seq Scan on city t1  (cost=0.00..93.19 rows=347 width=31) (actual time=0.069..0.618 rows=350 loops=1)
        Filter: ((id > 100) AND (population > 700000))
        Rows Removed by Filter: 3729
  ->  Materialize  (cost=0.00..8.72 rows=146 width=113) (actual time=0.000..0.011 rows=146 loops=350)
        ->  Seq Scan on country t2  (cost=0.00..7.99 rows=146 width=113) (actual time=0.007..0.058 rows=146 loops=1)
              Filter: (population < 7000000)
              Rows Removed by Filter: 93
Planning time: 0.136 ms
Execution time: 24.627 ms
(10 rows)

Se non troviamo il motivo per cui le nostre domande richiedono più tempo del dovuto, possiamo controllare questo blog per ulteriori informazioni.

VUOTO

Il processo VACUUM è responsabile di diverse attività di manutenzione all'interno del database, una delle quali recupera lo spazio di archiviazione occupato da tuple morte. Nel normale funzionamento di PostgreSQL, le tuple che vengono eliminate o obsolete da un aggiornamento non vengono rimosse fisicamente dalla loro tabella; rimangono presenti fino a quando non viene eseguito un VACUUM. Pertanto, è necessario eseguire periodicamente il VACUUM, soprattutto nelle tabelle aggiornate di frequente.

Se il VACUUM richiede troppo tempo o risorse, significa che dobbiamo farlo più frequentemente, in modo che ogni operazione abbia meno da pulire.

In ogni caso potrebbe essere necessario disabilitare il VACUUM, ad esempio quando si caricano dati in grandi quantità.

Il VACUUM recupera semplicemente lo spazio e lo rende disponibile per il riutilizzo. Questa forma del comando può operare in parallelo con la normale lettura e scrittura della tabella, in quanto non si ottiene un blocco esclusivo. Tuttavia, lo spazio aggiuntivo non viene restituito al sistema operativo (nella maggior parte dei casi); è disponibile solo per il riutilizzo all'interno della stessa tabella.

VACUUM FULL riscrive tutto il contenuto della tabella in un nuovo file su disco senza spazio aggiuntivo, il che consente allo spazio inutilizzato di tornare al sistema operativo. Questo modulo è molto più lento e richiede un blocco esclusivo su ogni tabella durante l'elaborazione.

VACUUM ANALYZE esegue un VACUUM e quindi un'ANALYZE per ogni tabella selezionata. Questo è un modo pratico per combinare gli script di manutenzione ordinaria.

ANALYZE raccoglie statistiche sul contenuto delle tabelle nel database e memorizza i risultati in pg_statistic. Successivamente, il pianificatore di query utilizza queste statistiche per determinare i piani di esecuzione più efficienti per le query.

Scarica il whitepaper oggi Gestione e automazione di PostgreSQL con ClusterControlScopri cosa devi sapere per distribuire, monitorare, gestire e ridimensionare PostgreSQLScarica il whitepaper

Parametri di configurazione

Per modificare questi parametri dobbiamo modificare il file $ PGDATA / postgresql.conf. Dobbiamo tenere presente che alcuni di essi richiedono il riavvio del nostro database.

max_connessioni

Determina il numero massimo di connessioni simultanee al nostro database. Esistono risorse di memoria che possono essere configurate per client, pertanto il numero massimo di client può suggerire la quantità massima di memoria utilizzata.

connessioni_riservate_superutente

In caso di raggiungimento del limite di max_connection, queste connessioni sono riservate al superutente.

buffer_condivisi

Imposta la quantità di memoria utilizzata dal server di database per i buffer di memoria condivisa. Se hai un server di database dedicato con 1 GB o più di RAM, un valore iniziale ragionevole per shared_buffers è il 25% della memoria del tuo sistema. Configurazioni più grandi per shared_buffer richiedono generalmente un corrispondente aumento di max_wal_size, per estendere il processo di scrittura di grandi quantità di dati nuovi o modificati per un periodo di tempo più lungo.

temp_buffer

Imposta il numero massimo di buffer temporanei utilizzati per ciascuna sessione. Questi sono buffer di sessione locali utilizzati solo per accedere alle tabelle temporanee. Una sessione assegnerà i buffer temporanei secondo necessità fino al limite dato da temp_buffers.

work_mem

Specifica la quantità di memoria che verrà utilizzata dalle operazioni interne delle tabelle ORDER BY, DISTINCT, JOIN e hash prima di scrivere sui file temporanei su disco. Quando configuriamo questo valore, dobbiamo tenere conto del fatto che diverse sessioni eseguono queste operazioni contemporaneamente e ogni operazione potrà utilizzare tutta la memoria specificata da questo valore prima che inizi a scrivere i dati nei file temporanei.

Questa opzione era chiamata sort_mem nelle versioni precedenti di PostgreSQL.

manutenzione_lavoro_mem

Specifica la quantità massima di memoria utilizzata dalle operazioni di manutenzione, ad esempio VACUUM, CREATE INDEX e ALTER TABLE ADD FOREIGN KEY. Poiché solo una di queste operazioni può essere eseguita contemporaneamente da una sessione e un'installazione di solito non ne ha molte in esecuzione contemporaneamente, può essere più grande di work_mem. Configurazioni più grandi possono migliorare le prestazioni per VACUUM e ripristini del database.

Quando viene eseguito l'autovacuum, a questa memoria può essere assegnato il numero di volte in cui è configurato il parametro autovacuum_max_workers, quindi dobbiamo tenerne conto o, in caso contrario, configurare il parametro autovacuum_work_mem per gestirlo separatamente.

fsync

Se fsync è abilitato, PostgreSQL cercherà di assicurarsi che gli aggiornamenti siano scritti fisicamente sul disco. Ciò garantisce che il cluster di database possa essere ripristinato a uno stato coerente dopo un arresto anomalo del sistema operativo o dell'hardware.

Sebbene la disabilitazione di fsync in genere migliori le prestazioni, può causare la perdita di dati in caso di interruzione di corrente o arresto anomalo del sistema. Pertanto, è consigliabile disattivare fsync solo se puoi ricreare facilmente l'intero database da dati esterni.

segmenti di checkpoint (PostgreSQL <9.5)

Numero massimo di segmenti di file di record tra punti di controllo WAL automatici (ogni segmento è normalmente di 16 megabyte). L'aumento di questo parametro può aumentare la quantità di tempo necessaria per il ripristino dei guasti. In un sistema con molto traffico, può influire sulle prestazioni se è impostato su un valore molto basso. Si consiglia di aumentare il valore di checkpoint_segments su sistemi con molte modifiche ai dati.

Inoltre, è buona norma salvare i file WAL su un disco diverso da PGDATA. Questo è utile sia per bilanciare la scrittura che per la sicurezza in caso di guasto hardware.

A partire da PostgreSQL 9.5 la variabile di configurazione "checkpoint_segments" è stata rimossa ed è stata sostituita da "max_wal_size" e "min_wal_size"

max_wal_size (PostgreSQL>=9.5)

Dimensione massima consentita per la crescita del WAL tra i punti di controllo. La dimensione di WAL può superare max_wal_size in circostanze speciali. Aumentando questo parametro è possibile aumentare il tempo necessario per il ripristino dei guasti.

min_wal_size (PostgreSQL>=9.5)

Quando il file WAL viene mantenuto al di sotto di questo valore, viene riciclato per un uso futuro in un checkpoint, invece di essere eliminato. Questo può essere utilizzato per garantire che sia riservato spazio WAL sufficiente per gestire i picchi nell'uso di WAL, ad esempio durante l'esecuzione di lavori batch di grandi dimensioni.

wal_sync_method

Metodo utilizzato per forzare gli aggiornamenti WAL sul disco. Se fsync è disabilitato, questa impostazione non ha effetto.

wal_buffer

La quantità di memoria condivisa utilizzata per i dati WAL che non sono stati ancora scritti su disco. L'impostazione predefinita è circa il 3% di shared_buffers, non inferiore a 64 KB o superiore alla dimensione di un segmento WAL (solitamente 16 MB). L'impostazione di questo valore su almeno alcuni MB può migliorare le prestazioni di scrittura su un server con molte transazioni simultanee.

dimensione_cache_effettiva

Questo valore viene utilizzato dal pianificatore di query per tenere conto dei piani che possono o meno rientrare nella memoria. Questo è preso in considerazione nelle stime dei costi dell'utilizzo di un indice; un valore alto rende più probabile l'utilizzo delle scansioni dell'indice e un valore basso rende più probabile l'utilizzo di scansioni sequenziali. Un valore ragionevole sarebbe il 50% della RAM.

obiettivo_statistico_predefinito

PostgreSQL raccoglie le statistiche da ciascuna delle tabelle nel suo database per decidere come verranno eseguite le query su di esse. Per impostazione predefinita, non raccoglie troppe informazioni e, se non stai ottenendo buoni piani di esecuzione, dovresti aumentare questo valore e quindi eseguire nuovamente ANALYZE nel database (o attendere l'AUTOVACUUM).

commit_sincrono

Specifica se il commit della transazione attenderà che i record WAL vengano scritti su disco prima che il comando restituisca un'indicazione di "successo" al client. I valori possibili sono:"on", "remote_apply", "remote_write", "local" e "off". L'impostazione predefinita è "on". Quando è disabilitato, potrebbe esserci un ritardo tra il momento in cui il client ritorna e quando è garantita la sicurezza della transazione contro un blocco del server. A differenza di fsync, la disabilitazione di questo parametro non crea alcun rischio di incoerenza del database:un crash del sistema operativo o del database può comportare la perdita di alcune transazioni recenti presumibilmente commesse, ma lo stato del database sarà esattamente lo stesso di quelle transazioni era stato cancellato in modo pulito. Pertanto, la disattivazione di synchronous_commit può essere un'utile alternativa quando le prestazioni sono più importanti dell'esatta certezza sulla durata di una transazione.

Registrazione

Esistono diversi tipi di dati da registrare che possono essere utili o meno. Vediamone alcuni:

  • log_min_error_statement:imposta il livello di registrazione minimo.
  • log_min_duration_statement:utilizzato per registrare query lente nel sistema.
  • log_line_prefix:aderisce alle informazioni all'inizio di ogni riga di registro.
  • log_statement:puoi scegliere tra NONE, DDL, MOD, ALL. L'utilizzo di "tutto" può causare problemi di prestazioni.

Progettazione

In molti casi, il design del nostro database può influire sulle prestazioni. Dobbiamo fare attenzione nella nostra progettazione, normalizzando il nostro schema ed evitando dati ridondanti. In molti casi è conveniente avere più tavolini invece di un tavolo enorme. Ma come dicevamo prima, tutto dipende dal nostro sistema e non esiste un'unica soluzione possibile.

Dobbiamo anche utilizzare gli indici in modo responsabile. Non dovremmo creare indici per ogni campo o combinazione di campi, poiché, sebbene non dobbiamo viaggiare per l'intera tabella, stiamo utilizzando lo spazio su disco e aggiungendo un sovraccarico per le operazioni di scrittura.

Un altro strumento molto utile è la gestione del pool di connessioni. Se abbiamo un sistema con molto carico, possiamo usarlo per evitare di saturare le connessioni nel database e per poterle riutilizzare.

Hardware

Come accennato all'inizio di questo blog, l'hardware è uno dei fattori importanti che influenzano direttamente le prestazioni del nostro database. Vediamo alcuni punti da tenere a mente.

  • Memoria:più RAM abbiamo, più dati di memoria possiamo gestire e questo significa prestazioni migliori. La velocità di scrittura e lettura su disco è molto più lenta rispetto alla memoria, quindi più informazioni possiamo avere in memoria, migliori saranno le prestazioni che avremo.
  • CPU:forse non ha molto senso dirlo, ma più CPU abbiamo, meglio è. In ogni caso non è il più importante in termini di hardware, ma se riusciamo ad avere una buona CPU la nostra capacità di elaborazione migliorerà e questo avrà un impatto diretto sul nostro database.
  • Disco rigido:abbiamo diversi tipi di dischi che possiamo utilizzare, SCSI, SATA, SAS, IDE. Abbiamo anche dischi a stato solido. Dobbiamo confrontare qualità / prezzo, che dovremmo usare per confrontare la sua velocità. Ma il tipo di disco non è l'unica cosa da considerare, bisogna anche vedere come configurarli. Se vogliamo buone prestazioni, possiamo utilizzare RAID10, mantenendo i WAL su un altro disco al di fuori del RAID. Non è consigliabile utilizzare RAID5 poiché le prestazioni di questo tipo di RAID per i database non sono buone.

Conclusione

Dopo aver preso in considerazione i punti citati in questo blog, possiamo eseguire un benchmark per verificare il comportamento del database.

È anche importante monitorare il nostro database per determinare se stiamo affrontando un problema di prestazioni e per poterlo risolvere il prima possibile. Per questo compito ci sono diversi strumenti come Nagios, ClusterControl o Zabbix, tra gli altri, che ci consentono non solo di monitorare, ma con alcuni di essi ci consente di agire in modo proattivo prima che si verifichi il problema. Con ClusterControl, oltre al monitoraggio, all'amministrazione ea molte altre utilità, possiamo ricevere consigli sulle azioni che possiamo intraprendere quando riceviamo avvisi sulle prestazioni. Questo ci permette di avere un'idea di come risolvere potenziali problemi.

Questo blog non vuole essere una guida esauriente su come migliorare le prestazioni del database. Si spera che dia un quadro più chiaro di ciò che le cose possono diventare importanti e di alcuni parametri di base che possono essere configurati. Non esitare a farci sapere se abbiamo perso qualcosa di importante.