20M satırlarındaki Postgres'te 'en son' sorguyu optimize etme


10

Masam şu şekilde görünüyor:

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

dizinler kaynak_kimliği, zaman damgası ve zaman damgası ile id ( CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST)) birleşiminde bulunur

İçinde 20M satır var (Tamam, 120M var, ancak source_id = 1 ile 20M var). Aynı durum için bildirilen veya gözlemlenen bir olayı tanımlayan timestampçeşitli girişlere sahiptir . Örneğin sıcaklık, bugün saat 12'de tahmin edildiği gibi yarın 14:00 için tahmin edildi.observation_timestampvaluetimestampobservation_timestamp

İdeal olarak bu tablo birkaç şeyi iyi yapar:

  • toplu yeni girişler ekleme, bazen bir seferde 100K
  • zaman dilimleri için gözlemlenen verileri seçme ("Ocak-Mart arası sıcaklık tahminleri nedir")
  • belirli bir noktadan gözlemlendiği şekilde zaman dilimleri için gözlemlenen verilerin seçilmesi ("1 Kasım'da düşündüğümüz gibi Ocak-Mart arası sıcaklık tahminlerinin görünümü nedir")

İkincisi, bu sorunun merkezinde yer alan soru.

Tablodaki veriler aşağıdaki gibi görünecektir

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

ve sorgunun çıktısı aşağıdaki gibi görünecektir (yalnızca en son gözlem_timeseni temsil edilen satır)

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

Bu sorguları optimize etmek için daha önce bazı materyallere danıştım, yani

... sınırlı bir başarı ile.

timestampİçinde ayrı bir tablo oluşturmayı düşündüm , bu yüzden yanal referans yapmak daha kolay, ancak nispeten yüksek kardinalite nedeniyle bana yardım edip etmeyeceklerinden şüphe duyuyorum - ayrıca başarmayı engelleyeceğinden endişe duyuyorum batch inserting new entries.


Üç soruya bakıyorum ve hepsi bana kötü performans veriyor

  • LATERAL birleştirmeli özyinelemeli CTE
  • Pencere işlevi
  • DISTINCT AÇIK

(Şu anda aynı şeyi yapmadıklarının farkındayım, ancak gördüğüm kadarıyla sorgulama türünün iyi örneklerini oluşturuyorlar.)

LATERAL birleştirmeli özyinelemeli CTE

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;

Verim:

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)

(yalnızca EXPLAIN, EXPLAIN ANALYZEtamamlanamadı, sorguyu tamamlamak için 24 saatten fazla sürdü)

Pencere işlevi

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;

Verim:

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 AÇIK

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

Verim:

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

Verilerimi nasıl yapılandırmalıyım, orada olmaması gereken taramalar var mı, bu sorguları genellikle ~ 1'lere (~ 120'ler yerine) almak mümkün mü?

İstediğim sonuçları elde etmek için verileri sorgulamanın farklı bir yolu var mı?

Değilse, hangi farklı altyapıya / mimariye bakmalıyım?


Temelde istediğiniz şey gevşek dizin taraması veya atlamalı taramadır. Yakında gelecekler. Eğer yama ile karıştırmak istiyorsanız şimdi yama uygulayabilirsiniz postgresql-archive.org/Index-Skip-Scan-td6025532.html ancak bir ay önce = P
Evan Carroll

Kenarda yaşıyorum @EvanCarroll = P - Azure'da Postgres'i bile yapamadığımı düşünürsek benim için biraz erken görünüyor.
Pepijn Schoen

Lütfen LIMIT'leri olmayan EXPLAIN ANALYZE planlarını gösteriniz (optimize edilmesi gereken şey budur), ancak ilk cevabımda önerdiğim değişikliklerle. Ancak LIMIT'ler olmadan, sanırım ~ 1'lerde imkansız bir iş yapmak istiyorsunuz. Belki bazı şeyleri önceden hesaplayabilirsiniz.
jjanes

@jjanes kesinlikle - öneri için teşekkür ederim. Ben kaldırdık LIMITşimdi sorudan ve baskı üzerine eklenen EXPLAIN ANALYZE(sadece EXPLAINüzerinde recursivekısmen olsa da)
Pepijn Schoen

Yanıtlar:


1

Özyinelemeli CTE sorgunuzda, ORDER BY (ts).idCTE bunları otomatik olarak bu sırayla oluşturduğundan final gereksizdir. Sorguyu çok daha hızlı hale getirmesi kaldırıldığında, yalnızca 500 dışında tümünü atmak için 20.180.572 satır oluşturmak yerine erken durabilir. Ayrıca, endeksin üzerine inşa edilmesi dizini (source_id, id, timestamp desc nulls last)daha da geliştirmelidir.

Diğer iki sorgu için, bitmap'lerin belleğe sığması için yeterli iş_memini arttırmak (kayıplı yığın bloklarından kurtulmak için) bazılarına yardımcı olacaktır. Ancak, (source_id, "timestamp", observation_timestamp DESC)yalnızca dizin taramaları için veya daha iyi gibi özel dizinler kadar değil (source_id, "timestamp", observation_timestamp DESC, value, id).


Öneri için teşekkür ederim - kesinlikle önerdiğiniz gibi özel indekslemeye bakacağım. LIMIT 500Beni çıktıyı sınırlamak için yapılmıştı, ancak üretim kodunda bu olmaz. Gönderiyi bunu yansıtacak şekilde düzenleyeceğim.
Pepijn Schoen

LIMIT yokluğunda, dizinler çok daha az etkili olabilir. Ama yine de denemeye değer.
jjanes

Haklısın - LIMITve önerilerinizle, şu anda yürütme 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)) Ama LIMITdaha önce olmadığı gibi. Index ScanBu durumda da nasıl kaldırabilirim Bitmap Index/Heap Scan?
Pepijn Schoen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.