Ottimizzazione di una query "più recente" in Postgres su 20 milioni di righe


10

La mia tabella ha il seguente aspetto:

    Column             |    Type           |    
-----------------------+-------------------+
 id                    | integer           | 
 source_id             | integer           | 
 timestamp             | integer           | 
 observation_timestamp | integer           | 
 value                 | double precision  | 

esistono indici su source_id, timestamp e su una combinazione di timestamp e id ( CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST))

Ci sono 20 M righe (OK, ci sono 120 M, ma 20 M con source_id = 1). Ha molte voci per lo stesso timestampcon variazione observation_timestamp, che descrivono un valueaccaduto a timestampsegnalato o osservato a observation_timestamp. es. La temperatura prevista per domani alle 14:00 come previsto oggi alle 12:00.

Idealmente questa tabella fa bene alcune cose:

  • inserimento batch di nuove voci, a volte 100 K alla volta
  • selezione dei dati osservati per i timer ("quali sono le previsioni di temperatura per gennaio fino a marzo")
  • selezionando i dati osservati per i timer come osservati da un certo punto ("qual è la vista delle previsioni di temperatura per gennaio fino a marzo come pensavamo il 1 ° novembre")

Il secondo è quello centrale per questa domanda.

I dati nella tabella sarebbero i seguenti

id  source_id   timestamp   observation_timestamp   value
1   1           1531084900  1531083900              9999
2   1           1531084900  1531082900              1111
3   1           1531085900  1531083900              8888
4   1           1531085900  1531082900              7777
5   1           1531086900  1531082900              5555

e un output della query sarebbe simile al seguente (rappresentata solo la riga dell'ultima osservazione_timestamp)

id  source_id   timestamp   observation_timestamp   value
1   1           1531084900  1531083900              9999
3   1           1531085900  1531083900              8888
5   1           1531086900  1531082900              5555

Ho già consultato del materiale prima di ottimizzare queste domande, vale a dire

... con scarso successo.

Ho preso in considerazione la possibilità di creare una tabella separata con timestampesso in modo che sia più facile fare riferimento lateralmente, ma a causa della cardinalità relativamente alta di coloro che dubito che mi aiuteranno - inoltre, sono preoccupato che ostacolerà il raggiungimento batch inserting new entries.


Sto esaminando tre domande e tutte mi danno cattive prestazioni

  • CTE ricorsivo con join LATERAL
  • Funzione finestra
  • DISTINCT ON

(Sono consapevole che al momento non fanno esattamente la stessa cosa, ma servono come buone illustrazioni del tipo di query a mio avviso.)

CTE ricorsivo con join LATERAL

WITH RECURSIVE cte AS (
    (
        SELECT ts
        FROM timeseries ts
        WHERE source_id = 1
        ORDER BY id, "timestamp" DESC NULLS LAST
        LIMIT 1
    )
    UNION ALL
    SELECT (
        SELECT ts1
        FROM timeseries ts1
        WHERE id > (c.ts).id
        AND source_id = 1
        ORDER BY id, "timestamp" DESC NULLS LAST
        LIMIT 1
    )
    FROM cte c
    WHERE (c.ts).id IS NOT NULL
)
SELECT (ts).*
FROM cte
WHERE (ts).id IS NOT NULL
ORDER BY (ts).id;

Prestazione:

Sort  (cost=164999681.98..164999682.23 rows=100 width=28)
  Sort Key: ((cte.ts).id)
  CTE cte
    ->  Recursive Union  (cost=1653078.24..164999676.64 rows=101 width=52)
          ->  Subquery Scan on *SELECT* 1  (cost=1653078.24..1653078.26 rows=1 width=52)
                ->  Limit  (cost=1653078.24..1653078.25 rows=1 width=60)
                      ->  Sort  (cost=1653078.24..1702109.00 rows=19612304 width=60)
                            Sort Key: ts.id, ts.timestamp DESC NULLS LAST
                            ->  Bitmap Heap Scan on timeseries ts  (cost=372587.92..1555016.72 rows=19612304 width=60)
                                  Recheck Cond: (source_id = 1)
                                  ->  Bitmap Index Scan on ix_timeseries_source_id  (cost=0.00..367684.85 rows=19612304 width=0)
                                        Index Cond: (source_id = 1)
          ->  WorkTable Scan on cte c  (cost=0.00..16334659.64 rows=10 width=32)
                Filter: ((ts).id IS NOT NULL)
                SubPlan 1
                  ->  Limit  (cost=1633465.94..1633465.94 rows=1 width=60)
                        ->  Sort  (cost=1633465.94..1649809.53 rows=6537435 width=60)
                              Sort Key: ts1.id, ts1.timestamp DESC NULLS LAST
                              ->  Bitmap Heap Scan on timeseries ts1  (cost=369319.21..1600778.77 rows=6537435 width=60)
                                    Recheck Cond: (source_id = 1)
                                    Filter: (id > (c.ts).id)
                                    ->  Bitmap Index Scan on ix_timeseries_source_id  (cost=0.00..367684.85 rows=19612304 width=0)
                                          Index Cond: (source_id = 1)
  ->  CTE Scan on cte  (cost=0.00..2.02 rows=100 width=28)
        Filter: ((ts).id IS NOT NULL)

(solo EXPLAIN, EXPLAIN ANALYZEimpossibile completare, sono state impiegate> 24 ore per completare la query)

Funzione finestra

WITH summary AS (
  SELECT ts.id, ts.source_id, ts.value,
    ROW_NUMBER() OVER(PARTITION BY ts.timestamp ORDER BY ts.observation_timestamp DESC) AS rn
  FROM timeseries ts
  WHERE source_id = 1
)
SELECT s.*
FROM summary s
WHERE s.rn = 1;

Prestazione:

CTE Scan on summary s  (cost=5530627.97..5971995.66 rows=98082 width=24) (actual time=150368.441..226331.286 rows=88404 loops=1)
  Filter: (rn = 1)
  Rows Removed by Filter: 20673704
  CTE summary
    ->  WindowAgg  (cost=5138301.13..5530627.97 rows=19616342 width=32) (actual time=150368.429..171189.504 rows=20762108 loops=1)
          ->  Sort  (cost=5138301.13..5187341.98 rows=19616342 width=24) (actual time=150368.405..165390.033 rows=20762108 loops=1)
                Sort Key: ts.timestamp, ts.observation_timestamp DESC
                Sort Method: external merge  Disk: 689752kB
                ->  Bitmap Heap Scan on timeseries ts  (cost=372675.22..1555347.49 rows=19616342 width=24) (actual time=2767.542..50399.741 rows=20762108 loops=1)
                      Recheck Cond: (source_id = 1)
                      Rows Removed by Index Recheck: 217784
                      Heap Blocks: exact=48415 lossy=106652
                      ->  Bitmap Index Scan on ix_timeseries_source_id  (cost=0.00..367771.13 rows=19616342 width=0) (actual time=2757.245..2757.245 rows=20762630 loops=1)
                            Index Cond: (source_id = 1)
Planning time: 0.186 ms
Execution time: 234883.090 ms

DISTINCT ON

SELECT DISTINCT ON (timestamp) *
FROM timeseries
WHERE source_id = 1
ORDER BY timestamp, observation_timestamp DESC;

Prestazione:

Unique  (cost=5339449.63..5437531.34 rows=15991 width=28) (actual time=112653.438..121397.944 rows=88404 loops=1)
  ->  Sort  (cost=5339449.63..5388490.48 rows=19616342 width=28) (actual time=112653.437..120175.512 rows=20762108 loops=1)
        Sort Key: timestamp, observation_timestamp DESC
        Sort Method: external merge  Disk: 770888kB
        ->  Bitmap Heap Scan on timeseries  (cost=372675.22..1555347.49 rows=19616342 width=28) (actual time=2091.585..56109.942 rows=20762108 loops=1)
              Recheck Cond: (source_id = 1)
              Rows Removed by Index Recheck: 217784
              Heap Blocks: exact=48415 lossy=106652
              ->  Bitmap Index Scan on ix_timeseries_source_id  (cost=0.00..367771.13 rows=19616342 width=0) (actual time=2080.054..2080.054 rows=20762630 loops=1)
                    Index Cond: (source_id = 1)
Planning time: 0.132 ms
Execution time: 161651.006 ms

Come devo strutturare i miei dati, ci sono scansioni che non dovrebbero esserci, è generalmente possibile portare queste query a ~ 1s (invece di ~ 120s)?

Esiste un modo diverso di interrogare i dati per ottenere i risultati desiderati?

In caso contrario, quale diversa infrastruttura / architettura dovrei guardare?


Ciò che essenzialmente vuoi è una scansione a indice lento o saltare la scansione. Quelli stanno arrivando presto. Ora puoi applicare la patch se vuoi confonderla con postgresql-archive.org/Index-Skip-Scan-td6025532.html ha appena un mese = P
Evan Carroll

Livin 'on the edge @EvanCarroll = P - mi sembra un po' troppo presto per me, considerando che sto usando Postgres su Azure nemmeno fattibile.
Pepijn Schoen,

Si prega di mostrare i piani EXPLAIN ANALYZE senza i LIMITI (poiché è ciò che deve essere ottimizzato), ma con le modifiche che ho raccomandato nella mia prima risposta. Ma senza i LIMITI, penso che tu stia chiedendo una quantità impossibile di lavoro da fare in ~ 1s. Forse puoi pre-calcolare alcune cose.
jjanes,

@jjanes assolutamente - grazie per il suggerimento. Ho rimosso il LIMITdalla domanda ora e ho aggiunto l'output con EXPLAIN ANALYZE(solo EXPLAINnella recursiveparte però)
Pepijn Schoen,

Risposte:


1

Con la tua query CTE ricorsiva, il finale ORDER BY (ts).idnon è necessario poiché il CTE li crea automaticamente in quell'ordine. La rimozione che dovrebbe rendere la query molto più veloce, può interrompere in anticipo piuttosto che generare 20.180.572 righe solo per buttarne via tutte tranne 500. Inoltre, la creazione dell'indice (source_id, id, timestamp desc nulls last)dovrebbe migliorarlo ulteriormente.

Per le altre due query, aumentando abbastanza work_mem che le bitmap si adattano alla memoria (per sbarazzarsi di blocchi heap con perdita) sarebbe di aiuto. Ma non tanto quanto gli indici personalizzati, come (source_id, "timestamp", observation_timestamp DESC)o meglio ancora per le scansioni solo dell'indice (source_id, "timestamp", observation_timestamp DESC, value, id).


Grazie per il suggerimento: esaminerò sicuramente l'indicizzazione personalizzata come suggerisci tu. L' LIMIT 500era per me per limitare l'output, ma nel codice di produzione questo non accade. Modificherò il mio post per riflettere quello.
Pepijn Schoen, l'

In assenza del LIMIT, gli indici potrebbero essere molto meno efficaci. Ma vale comunque la pena provare.
jjanes,

Hai ragione - con i LIMITtuoi suggerimenti e, attualmente l'esecuzione è 356.482 ms( Index Scan using ix_timeseries_source_id_timestamp_observation_timestamp on timeseries (cost=0.57..62573201.42 rows=18333374 width=28) (actual time=174.098..356.097 rows=2995 loops=1)) Ma senza LIMITè come prima. Come potrei sfruttare anche il Index Scanin quel caso e non il Bitmap Index/Heap Scan?
Pepijn Schoen,
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.