Ottimizzazione delle query su un intervallo di timestamp (una colonna)


8

Sto usando Postgres 9.3 tramite Heroku.

Ho una tabella, "traffico", con record 1M + che ha molti inserti e aggiornamenti ogni giorno. Devo eseguire le operazioni SUM su questa tabella in diversi intervalli di tempo e tali chiamate possono richiedere fino a 40 secondi e mi piacerebbe ricevere suggerimenti su come migliorarlo.

Ho il seguente indice in atto su questa tabella:

CREATE INDEX idx_traffic_partner_only ON traffic (dt_created) WHERE campaign_id IS NULL AND uuid_self <> uuid_partner;

Ecco un'istruzione SELECT di esempio:

SELECT SUM("clicks") AS clicks, SUM("impressions") AS impressions
FROM "traffic"
WHERE "uuid_self" != "uuid_partner"
AND "campaign_id" is NULL
AND "dt_created" >= 'Sun, 29 Mar 2015 00:00:00 +0000'
AND "dt_created" <= 'Mon, 27 Apr 2015 23:59:59 +0000' 

E questo è l'ANALISI DI SPIEGAZIONE:

Aggregate  (cost=21625.91..21625.92 rows=1 width=16) (actual time=41804.754..41804.754 rows=1 loops=1)
  ->  Index Scan using idx_traffic_partner_only on traffic  (cost=0.09..20085.11 rows=308159 width=16) (actual time=1.409..41617.976 rows=302392 loops=1)
      Index Cond: ((dt_created >= '2015-03-29'::date) AND (dt_created <= '2015-04-27'::date))
Total runtime: 41804.893 ms

http://explain.depesz.com/s/gGA

Questa domanda è molto simile a un'altra su SE, ma quella utilizzava un indice su due intervalli di data / ora di colonna e il pianificatore di indice per quella query aveva delle stime molto lontane. Il suggerimento principale era quello di creare un indice multi-colonna ordinato, ma per gli indici a colonna singola che non ha molto effetto. Gli altri suggerimenti erano di utilizzare gli indici CLUSTER / pg_repack e GIST, ma non li ho ancora provati, poiché mi piacerebbe vedere se esiste una soluzione migliore utilizzando gli indici regolari.

Ottimizzazione delle query su un intervallo di timestamp (due colonne)

Per riferimento, ho provato i seguenti indici, che non sono stati utilizzati dal DB:

INDEX idx_traffic_2 ON traffic (campaign_id, uuid_self, uuid_partner, dt_created);
INDEX idx_traffic_3 ON traffic (dt_created);
INDEX idx_traffic_4 ON traffic (uuid_self);
INDEX idx_traffic_5 ON traffic (uuid_partner);

EDIT : Ran EXPLAIN (ANALYZE, VERBOSE, COSTI, BUFFER) e questi erano risultati:

Aggregate  (cost=20538.62..20538.62 rows=1 width=8) (actual time=526.778..526.778 rows=1 loops=1)
  Output: sum(clicks), sum(impressions)
  Buffers: shared hit=47783 read=29803 dirtied=4
  I/O Timings: read=184.936
  ->  Index Scan using idx_traffic_partner_only on public.traffic  (cost=0.09..20224.74 rows=313881 width=8) (actual time=0.049..431.501 rows=302405 loops=1)
      Output: id, uuid_self, uuid_partner, impressions, clicks, dt_created... (other fields redacted)
      Index Cond: ((traffic.dt_created >= '2015-03-29'::date) AND (traffic.dt_created <= '2015-04-27'::date))
      Buffers: shared hit=47783 read=29803 dirtied=4
      I/O Timings: read=184.936
Total runtime: 526.881 ms

http://explain.depesz.com/s/7Gu6

Definizione della tabella:

CREATE TABLE traffic (
    id              serial,
    uuid_self       uuid not null,
    uuid_partner    uuid not null,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
)

id è la chiave primaria e uuid_self, uuid_partner e campaign_id sono tutte chiavi esterne. Il campo dt_updated viene aggiornato con una funzione postgres.


explain (buffers, analyze, verbose) ...potrebbe fare più luce.
Craig Ringer,

Un'informazione essenziale manca qui: l'esatta definizione della tabella di traffic. Inoltre: perché il secondo EXPLAINmostra un calo da 42 secondi a 0,5 secondi? La prima è stata eseguita con cold cache?
Erwin Brandstetter,

Ho appena aggiunto la definizione della tabella alla domanda. Sì, i 42 a 0,5 secondi erano probabilmente dovuti a una cache fredda, ma poiché ci sono così tanti aggiornamenti, questo sarebbe probabilmente un evento abbastanza comune. Ho appena eseguito EXPLAIN ANALYZE di nuovo e questa volta ci sono voluti 56 secondi. L'ho eseguito ancora una volta ed è sceso a .4s.
Evan Appleby,

È sicuro supporre che ci sia un vincolo PK su id? Qualche altro vincolo? Vedo due colonne che possono essere NULL. Qual è la percentuale di valori NULL in ciascuno? Cosa ottieni per questo? SELECT count(*) AS ct, count(campaign_id)/ count(*) AS camp_pct, count(dt_updated)/count(*) AS upd_pct FROM traffic;
Erwin Brandstetter,

Sì, ID ha un vincolo PK e uuid_self, uuid_partner e campaign_id hanno vincoli FK. Campaign_id è 99% + NULL e dt_updated è 0% NULL.
Evan Appleby

Risposte:


3

Due cose che sono molto strane qui:

  1. La query seleziona 300k righe da una tabella con 1M + righe. Per il 30% (o qualcosa di più del 5% - dipende dalle dimensioni della riga e da altri fattori), in genere non paga per usare un indice. Dovremmo vedere una scansione sequenziale .

    L'eccezione sarebbe rappresentata dalle scansioni solo indice, che non vedo qui. L'indice a più colonne suggerito da @Craig sarebbe l'opzione migliore se si ottengono scansioni solo dell'indice. Con molti aggiornamenti come hai menzionato, questo potrebbe non funzionare, nel qual caso stai meglio senza le colonne aggiuntive e solo l'indice che hai già. Potresti essere in grado di farlo funzionare con impostazioni di vuoto automatico più aggressive per la tabella. È possibile regolare i parametri per le singole tabelle.

  2. Mentre Postgres utilizzerà l'indice, mi aspetto certamente una scansione dell'indice bitmap per quelle righe, non una semplice scansione dell'indice, che di solito è la scelta migliore per una bassa percentuale di righe. Non appena Postgres prevede più hit per pagina di dati (a giudicare dalle sue statistiche sulla tabella), in genere passa a una scansione dell'indice bitmap.

A giudicare da ciò, sospetterei che le tue impostazioni di costo siano inadeguate (e forse anche le statistiche della tabella). Potresti aver impostato random_page_coste / o troppo basso , rispetto a . Segui i link e leggi il manuale.cpu_index_tuple_cost seq_page_cost

Si adatterebbe anche all'osservazione che la cold cache è un grande fattore, come abbiamo elaborato nei commenti. O stai accedendo a (parti di) tabelle che nessuno ha toccato da molto tempo o stai eseguendo un sistema di test in cui la cache non è popolata (ancora)?
Altrimenti non hai abbastanza RAM disponibile per memorizzare nella cache la maggior parte dei dati rilevanti nel tuo DB. Di conseguenza, l'accesso casuale è molto più costoso dell'accesso sequenziale quando i dati risiedono nella cache. A seconda della situazione effettiva, potrebbe essere necessario adeguarsi per ottenere piani di query migliori.

Un altro fattore deve essere menzionato per la risposta lenta alla prima lettura: bit di suggerimento . Leggi i dettagli nel Postgres Wiki e questa domanda correlata:

Oppure la tabella è estremamente gonfia , nel qual caso una scansione dell'indice avrebbe senso e farei riferimento aCLUSTER / pg_repacknella mia precedente risposta che hai citato. (O semplicementeVACUUM FULL)e indagare sulleVACUUMimpostazioni. Questi sono importanti conmany inserts and updates every day.

A seconda dei UPDATEmotivi, considera anche un valore FILLFACTORinferiore a 100. Se aggiorni principalmente solo le righe appena aggiunte, imposta il valore più basso FILLFACTER dopo aver compattato la tabella, in modo che solo le nuove pagine mantengano un po 'di spazio per gli aggiornamenti.

Schema

campaign_idè 99% + NULL ed dt_updatedè 0% NULL.

Regola leggermente la sequenza delle colonne, per risparmiare 8 byte per riga (nel 99% dei casi in cui campaign_idè NULL):

CREATE TABLE traffic (
    uuid_self       uuid not null REFERENCES ... ,
    uuid_partner    uuid not null REFERENCES ... ,
    id              serial PRIMARY KEY,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
);

Spiegazione dettagliata e collegamenti ad altro:

Misurare:


Grazie per il suggerimento Al momento mi affido all'aspirapolvere automatico impostato tramite Heroku e la tabella del traffico viene aspirata quasi ogni giorno. Esaminerò di più la modifica delle statistiche della tabella e del Fattore di riempimento e l'utilizzo di pg_repack e il report indietro.
Evan Appleby,

2

Mi sembra che tu stia interrogando molti dati in un grande indice, quindi è lento. Niente di grave lì.

Se utilizzi PostgreSQL 9.3 o 9.4, potresti provare a vedere se riesci a ottenere una scansione solo dell'indice trasformandola in un indice di copertura di sorta.

CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created, clicks, impressions)
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

PostgreSQL non ha indici di copertura reali o supporto per termini di indice che sono solo valori, non parte dell'albero b, quindi questo è più lento e più costoso di quanto potrebbe essere con quelle caratteristiche. Potrebbe essere comunque una vittoria su una semplice scansione dell'indice se il vuoto viene eseguito abbastanza spesso da mantenere aggiornata la mappa di visibilità.


Idealmente PostgreSQL supporterebbe i campi di dati ausiliari in un indice come in MS-SQL Server ( questa sintassi NON FUNZIONA in PostgreSQL ):

-- This will not work in PostgreSQL (at least 9.5)
-- it's an example of what I wish did work. Don't
-- comment to say it doesn't work.
--
CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created)
INCLUDING (clicks, impressions) -- auxillary data columns
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

Grazie per il suggerimento Ho provato l'indice di copertura e il DB lo ha ignorato e ha ancora utilizzato l'altro indice. Consiglieresti di rimuovere l'altro indice e di utilizzare solo l'indice di copertura (o, in alternativa, di utilizzare solo più indici di copertura per ogni situazione che lo richiede)? Ho anche aggiunto EXPLAIN (ANALYZE, VERBOSE, COSTI, BUFFER) alla domanda originale.
Evan Appleby

Dispari. Forse il pianificatore non è abbastanza intelligente da scegliere una scansione solo dell'indice se vede più di un aggregato, ma avrei pensato che potesse farlo. Prova a giocare con i parametri di costo ( random_page_costecc.). Inoltre, a scopo di test, vedere solo se set enable_indexscan = offe set enable_seqscan = offquindi rieseguire forza una scansione di soli indici e, in tal caso, quali sono le sue stime dei costi di spiegazione dell'analisi.
Craig Ringer,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.