2015-12-14 21 views
7

ho le seguenti tabelle:Come posso aumentare la velocità della mia istruzione select Postgres?

CREATE TABLE views (
    view_id bigint NOT NULL, 
    usr_id bigint, 
    ip inet, 
    referer_id bigint, 
    country_id integer, 
    validated smallint, 
    completed smallint, 
    value numeric 
); 

ALTER TABLE ONLY views 
    ADD CONSTRAINT "Views_pkey" PRIMARY KEY (view_id); 

CREATE TABLE country (
    country_id integer NOT NULL, 
    country character varying(2) 
); 

ALTER TABLE ONLY country 
    ADD CONSTRAINT country_pkey PRIMARY KEY (country_id); 

CREATE TABLE file_id_view_id (
    file_id bigint, 
    view_id bigint, 
    created_ts timestamp without time zone 
); 

CREATE TABLE file_owner (
    file_id bigint NOT NULL, 
    owner_id bigint 
); 

ALTER TABLE ONLY file_owner 
     ADD CONSTRAINT owner_table_pkey PRIMARY KEY (file_id); 

CREATE TABLE referer (
    referer_id bigint NOT NULL, 
    referer character varying(255) 
); 

ALTER TABLE ONLY referer 
    ADD CONSTRAINT referer_pkey PRIMARY KEY (referer_id); 

La tabella views e file_id_view_id hanno circa 340M righe ogni. Ogni ora aumenteranno entrambe di righe 600K.

La tabella file_owner ha 75K righe e aumenterà ogni ora mediante righe.

La tabella country ha righe e cambia raramente.

La tabella referer ha righe e modifiche raramente.

Il mio obiettivo è quello di essere in grado di eseguire una ricerca del tipo:

SELECT Count(ft.*)      AS total_views, 
     (Count(ft.*) - SUM(ft.valid)) AS invalid_views, 
     SUM(ft.valid)     AS valid_views, 
     SUM(ft.values)     AS VALUES, 
     ft.day       AS day, 
     (CASE 
      WHEN r.referer IS NULL THEN 'Unknown' 
      ELSE r.referer 
     END)       AS referer, 
     (CASE 
      WHEN c.country IS NULL THEN 'Unknown' 
      ELSE c.country 
     END)       AS country 
FROM country c 
     right join (referer r 
        right join (SELECT v.validated AS valid, 
             v.value  AS VALUES, 
             vf.day  AS day, 
             vf.view_id AS view_id, 
             v.referer_id AS referer_id, 
             v.country_id AS country_id 
           FROM VIEWS v, 
             (SELECT view_id, 
fivi.created_ts :: timestamp :: DATE AS 
day 
FROM file_id_view_id fivi 
join (SELECT file_id 
     FROM file_owner 
     WHERE owner_id = 75 
     GROUP BY file_id) fo 
    ON (fo.file_id = fivi.file_id) 
WHERE (fivi.created_ts BETWEEN 
    '2015-11-01' AND '2015-12-01') 
GROUP BY view_id, 
    day) vf 
WHERE v.view_id = vf.view_id) ft 
ON (ft.referer_id = r.referer_id)) 
ON (ft.country_id = c.country_id) 
GROUP BY day, 
      referer, 
      country; 

produrre:

total_views | invalid_views | valid_views | values | day  |  referer  | country 
------------+---------------+-------------+--------+------------+-----------------+--------- 

Quando si esegue tale query con EXPLAIN ANALYZE quanto segue viene prodotto:

GroupAggregate (cost=38893491.99..40443007.61 rows=182295955 width=52) (actual time=183725.696..205882.889 rows=172 loops=1) 
    Group Key: ((fivi.created_ts)::date), r.referer, c.country 
    -> Sort (cost=38893491.99..38984639.97 rows=182295955 width=52) (actual time=183725.655..200899.098 rows=8390217 loops=1) 
     Sort Key: ((fivi.created_ts)::date), r.referer, c.country 
     Sort Method: external merge Disk: 420192kB 
     -> Hash Left Join (cost=16340128.88..24989809.75 rows=182295955 width=52) (actual time=23399.900..104337.332 rows=8390217 loops=1) 
       Hash Cond: (v.country_id = c.country_id) 
       -> Hash Left Join (cost=16340125.36..24800637.72 rows=182295955 width=49) (actual time=23399.782..102534.655 rows=8390217 loops=1) 
        Hash Cond: (v.referer_id = r.referer_id) 
        -> Merge Join (cost=16340033.52..24051874.62 rows=182295955 width=29) (actual time=23397.410..99955.000 rows=8390217 loops=1) 
          Merge Cond: (fivi.view_id = v.view_id) 
          -> Group (cost=16340033.41..16716038.36 rows=182295955 width=16) (actual time=23397.298..30454.444 rows=8390217 loops=1) 
           Group Key: fivi.view_id, ((fivi.created_ts)::date) 
           -> Sort (cost=16340033.41..16434985.73 rows=189904653 width=16) (actual time=23397.294..28165.729 rows=8390217 loops=1) 
             Sort Key: fivi.view_id, ((fivi.created_ts)::date) 
             Sort Method: external merge Disk: 180392kB 
             -> Nested Loop (cost=6530.43..8799350.01 rows=189904653 width=16) (actual time=63.123..15131.956 rows=8390217 loops=1) 
              -> HashAggregate (cost=6530.31..6659.62 rows=43104 width=8) (actual time=62.983..90.331 rows=43887 loops=1) 
                Group Key: file_owner.file_id 
                -> Bitmap Heap Scan on file_owner (cost=342.90..6508.76 rows=43104 width=8) (actual time=5.407..50.779 rows=43887 loops=1) 
                 Recheck Cond: (owner_id = 75) 
                 Heap Blocks: exact=5904 
                 -> Bitmap Index Scan on owner_id_index (cost=0.00..340.74 rows=43104 width=0) (actual time=4.327..4.327 rows=45576 loops=1) 
                   Index Cond: (owner_id = 75) 
              -> Index Scan using file_id_view_id_indexing on file_id_view_id fivi (cost=0.11..188.56 rows=4406 width=24) (actual time=0.122..0.306 rows=191 loops=43887) 
                Index Cond: (file_id = file_owner.file_id) 
                Filter: ((created_ts >= '2015-11-01 00:00:00'::timestamp without time zone) AND (created_ts <= '2015-12-01 00:00:00'::timestamp without time zone)) 
                Rows Removed by Filter: 184 
          -> Index Scan using "Views_pkey" on views v (cost=0.11..5981433.17 rows=338958763 width=25) (actual time=0.088..46804.757 rows=213018702 loops=1) 
        -> Hash (cost=68.77..68.77 rows=6591 width=28) (actual time=2.344..2.344 rows=6495 loops=1) 
          Buckets: 1024 Batches: 1 Memory Usage: 410kB 
          -> Seq Scan on referer r (cost=0.00..68.77 rows=6591 width=28) (actual time=0.006..1.156 rows=6495 loops=1) 
       -> Hash (cost=2.70..2.70 rows=233 width=7) (actual time=0.078..0.078 rows=233 loops=1) 
        Buckets: 1024 Batches: 1 Memory Usage: 10kB 
        -> Seq Scan on country c (cost=0.00..2.70 rows=233 width=7) (actual time=0.005..0.042 rows=233 loops=1) 
Planning time: 1.015 ms 
Execution time: 206034.660 ms 
(37 rows) 

Pianificare su explain.depesz.com: http://explain.depesz.com/s/OiN

206s tempo di esecuzione.

Alcune cose da notare,

PostgreSQL Version 9,4

ho regolato la configurazione come segue:

  1. shared_buffers = 30GB
  2. work_mem = 32MB
  3. random_page_cost = 2.0
  4. cpu_tuple_cost = 0.0030
  5. cpu_index_tuple_cost = 0.0010
  6. cpu_operator_cost = 0.0005
  7. effective_cache_size = 52 GB

esistono attualmente i seguenti indici:

  1. CREATE INDEX country_index su Country UTILIZZO btree (paese);
  2. CREATE INDICE created_ts_index ON file_id_view_id UTILIZZO btree (created_ts);
  3. CREATE INDEX file_id_created_ts_index ON file_id_view_id UTILIZZO btree (created_ts, file_id);
  4. CREATE INDEX file_id_view_id_indexing ON file_id_view_id UTILIZZO btree (id_file);
  5. CREATE INDEX owner_id_file_id_index ON file_owner USING btree (id_file, id_proprietà);
  6. CREATE INDEX owner_id_index ON file_owner USING btree (owner_id);
  7. CREATE INDEX referer_index ON referer UTILIZZO btree (referer);

La query precedente stava usando un id proprietario che è stato raccolto conservativamente, alcune query possono provocare 1/3 della tabella file_id_view_id essendo unite con vista.

La modifica della struttura dati è una ultima località. In questa fase un tale cambiamento deve essere dovuto a serie preoccupazioni.

Il db può essere considerato come letto solo se necessario, i dati in fase di scrittura vengono eseguiti su base oraria e viene dato ampio spazio a Postgres dopo ogni scrittura. Al momento attuale durante un 600 K ogni ora scrive il db restituisce 1100 (ciò è dovuto ad altri motivi oltre al costo degli inserti). C'è molto spazio per aggiungere ulteriori indici se aumentasse la velocità di lettura, la velocità di lettura è la priorità.

Le specifiche hardware sono:

CPU: http://ark.intel.com/products/83356/Intel-Xeon-Processor-E5-2630-v3-20M-Cache-2_40-GHz

RAM: 128GB

STOCCAGGIO: 1.5TB PCIE SSD

Come posso ottimizzare sia il mio database o la query in modo che io possa recuperare le informazioni che mi servono fuori dal db in un ragionevole lasso di tempo?

Cosa posso fare per ottimizzare il mio attuale design?

Credo che Postgres e l'hardware su cui è in esecuzione abbiano la capacità di funzionare molto meglio di quanto sia attualmente.

UPDATE

ho provato:

  1. analizzare le tabelle, non ha influenzato le prestazioni.
  2. Aumentare il work_mem, questo ha comportato un aumento della velocità a 116 s.
  3. Dipendendo dal pianificatore di query di Postgres evitando i sottoselezionamenti, questa performance ha effetti negativi.
  4. Dividi look prima della mano, questo non ha apparentemente alcun effetto positivo/negativo.

Qualcuno ha qualche tabella di ristrutturazione dell'esperienza così grande? È fattibile? Ci vorranno giorni, ore (stimando ovviamente)?

Sto considerando la de-normalizzazione del database, dal momento che in questo metodo verrà utilizzato solo il riferimento. La mia unica preoccupazione è che se le righe da 100M dovessero essere chiamate da una tabella con un owner_id indicizzato, sarebbe abbastanza veloce o dovrei ancora affrontare gli stessi problemi di prestazioni? Oserei andare in un modo, quindi tornare indietro.

L'altra soluzione che sto cercando è @ ivan.panasuik suggerimento, raggruppare tutti i dati del giorno in un'altra tabella, poiché una volta che il giorno è passato, tali informazioni sono costanti e non devono essere modificate o aggiornate. Tuttavia, non sono sicuro di come implementarlo senza problemi - dovrei avere interrogazioni che eseguono i dati mentre gli inserimenti sono in attesa e catturare i giorni il più velocemente possibile? Da quel momento in poi hai un trigger?

+2

Le stime non sono proprio così accurate.Hai analizzato le tabelle coinvolte? Hai anche due tipi piuttosto grandi che sono fatti su disco. Puoi provare ad aumentare drasticamente work_mem per quella query_, ad es. 'set work_mem = '512MB'' o anche' set work_mem =' 1GB'' –

+0

Ero sotto l'impressione Postgres analizzerà automaticamente le tabelle, dovrei farlo anche manualmente? Quando dici _quella query_ vuoi dire che esiste un modo specifico per impostare work_mem per una singola query? –

+0

It _should_ lo fa automaticamente, ma a volte (ad es. Dopo un carico iniziale) non calcia abbastanza velocemente. Le affermazioni che ho mostrato eseguendo _before_ la tua query cambieranno 'work_mem' per la sessione corrente: http://www.postgresql.org/docs/current/static/sql-set.html –

risposta

2

La velocità del database in genere non è l'hardware, ma quanto bene si utilizzano l'intelligenza e le funzionalità del motore stesso.

  1. Cercare di evitare le sottosezioni, specialmente quando si gestiscono molti dati. Questi spesso non possono essere ottimizzati dal pianificatore di query. Nella maggior parte dei casi dovresti essere in grado di convertire sottoselezioni semplici in JOIN o anche separare le ricerche di database in anticipo, se necessario.

  2. Partizionare le tabelle - PostgreSQL non lo fa in modo nativo (una specie di) ma se si accede molto spesso solo ai dati recenti è possibile rimuovere un sacco di lavoro spostando i dati di archivio fuori dal modo.

  3. Considera una strategia di data warehousing: quando ti occupi di quella quantità di dati, dovresti prendere in considerazione l'archiviazione di una copia dei dati in modo denormalizzato che è molto veloce da recuperare perché i giusti JOIN sono già stati presi in considerazione. Lo facciamo con Redshift (una derivata di PostgeSQL) in modo che non abbiamo bisogno di fare alcun JOINs durante l'esecuzione di report.

+0

Mi piace il tuo terzo suggerimento per alcune cose su cui sto lavorando ... Mantenendo una cache temporanea di dati completamente esplosi parallelamente ai dati memorizzati in modo efficiente finché non passa una finestra ragionevole. Grazie! –

2
  1. Rimuovi (Count (ft *) -. SUM (ft.valid)) AS invalid_views, dal momento che si dispone già di questi valori e si può calcolare in un secondo momento, durante la visualizzazione dei risultati
  2. Aggiungi indice su file_owner.file_id e controlla che ogni singolo campo che utilizza nella query abbia indice (campi che stai utilizzando in condizioni: dove, gruppo, ecc.)
  3. Non ho analizzato più la query, ma sembra che tu debba dividere eseguire una query in alcune query più piccole (e più veloci) e collegarla utilizzando tabelle temporanee o stored procedure.
  4. Supponendo che il risultato di ieri sia qualcosa che non cambierebbe ... potresti eseguire query con condition day = today() ed evitare di raggruppare di giorno. Risultati di tutti i giorni che è possibile salvare in una tabella separata. Vedo che la maggior parte delle volte funziona raggruppando.

È molto difficile prevedere l'ottimizzazione senza provare e provare .... quindi provare uno alla volta. E buona fortuna.

Problemi correlati