Mengoptimalkan kueri 'terbaru' di Postgres pada baris 20M

10

Tabel saya terlihat sebagai berikut:

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

indeks ada di source_id, timestamp, dan pada kombinasi timestamp dan id ( CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST))

Ada 20M baris di dalamnya (OK, ada 120M, tapi 20M dengan source_id = 1). Ini memiliki banyak entri untuk hal yang sama timestampdengan beragam observation_timestamp, yang menggambarkan suatu kejadian valuedi timestampdilaporkan atau diamati di observation_timestamp. mis. Temperatur diprediksi untuk besok jam 2 siang seperti yang diprediksi hari ini jam 12 pagi.

Idealnya tabel ini melakukan beberapa hal dengan baik:

  • memasukkan entri baru, kadang-kadang 100K dalam satu waktu
  • memilih data yang diamati untuk timerang ("berapa prediksi suhu untuk Januari hingga Maret")
  • memilih data yang diamati untuk bumerang yang diamati dari titik tertentu ("apa pandangan prediksi suhu untuk Januari hingga Maret seperti yang kita pikirkan pada 1 November")

Yang kedua adalah yang menjadi pusat pertanyaan ini.

Data dalam tabel akan terlihat seperti berikut ini

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

dan output dari kueri akan terlihat seperti berikut (hanya baris dari pengamatan_timestamp terbaru yang diwakili)

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

Saya sudah berkonsultasi dengan beberapa materi sebelumnya untuk mengoptimalkan pertanyaan ini, yaitu

... dengan kesuksesan terbatas.

Saya telah mempertimbangkan membuat tabel terpisah dengan timestampdi dalamnya sehingga lebih mudah untuk referensi lateral, tetapi karena kardinalitas yang relatif tinggi dari mereka yang saya ragu apakah mereka akan membantu saya - selain itu saya khawatir itu akan menghalangi untuk menyelesaikannya batch inserting new entries.


Saya melihat tiga pertanyaan, dan semuanya memberi saya kinerja yang buruk

  • CTE rekursif dengan LATERAL bergabung
  • Fungsi jendela
  • HUBUNGI ON

(Saya sadar mereka tidak cukup melakukan hal yang sama saat ini, tetapi mereka berfungsi sebagai ilustrasi yang bagus dari jenis pertanyaan sejauh yang saya lihat.)

CTE rekursif dengan LATERAL bergabung

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;

Kinerja:

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)

(hanya EXPLAIN, EXPLAIN ANALYZEtidak bisa menyelesaikan, butuh> 24 jam untuk menyelesaikan permintaan)

Fungsi jendela

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;

Kinerja:

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

HUBUNGI ON

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

Kinerja:

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

Bagaimana saya harus menyusun data saya, apakah ada pemindaian yang seharusnya tidak ada, apakah secara umum memungkinkan untuk mendapatkan kueri ini hingga ~ 1s (bukannya ~ 120s)?

Apakah ada cara berbeda untuk menanyakan data untuk mendapatkan hasil yang saya inginkan?

Jika tidak, infrastruktur / arsitektur apa yang harus saya perhatikan?

Pepijn Schoen
sumber
Yang Anda inginkan pada dasarnya adalah pemindaian indeks longgar atau lewati pemindaian. Mereka akan segera hadir. Anda dapat menerapkan tambalan itu sekarang jika Anda ingin mengacaukannya postgresql-archive.org/Index-Skip-Scan-td6025532.html itu baru berumur sebulan = P
Evan Carroll
Livin 'on the edge @EvanCarroll = P - yang sepertinya agak terlalu dini untuk saya, mengingat saya menggunakan Postgres di Azure bahkan tidak bisa dilakukan.
Pepijn Schoen
Tolong tunjukkan paket EXPLAIN ANALYZE tanpa LIMIT (karena itulah yang perlu dioptimalkan), tetapi dengan perubahan yang saya rekomendasikan dalam jawaban pertama saya. Tetapi tanpa LIMIT, saya pikir Anda meminta jumlah pekerjaan yang mustahil dilakukan pada ~ 1s. Mungkin Anda bisa melakukan precompute terhadap beberapa hal.
jjanes
@ jjanes absolute - terima kasih atas sarannya. Saya telah menghapus LIMITdari pertanyaan sekarang, dan menambahkan output dengan EXPLAIN ANALYZE(hanya EXPLAINpada recursivebagiannya saja)
Pepijn Schoen

Jawaban:

1

Dengan permintaan CTE rekursif Anda, final ORDER BY (ts).idtidak diperlukan karena CTE secara otomatis membuat mereka dalam urutan itu. Menghapus yang seharusnya membuat kueri lebih cepat, itu bisa berhenti lebih awal daripada menghasilkan 20.180.572 baris hanya untuk membuang semua kecuali 500. Juga, membangun indeks (source_id, id, timestamp desc nulls last)harus ditingkatkan lebih lanjut.

Untuk dua pertanyaan lainnya, meningkatkan work_mem cukup sehingga bitmap masuk dalam memori (untuk menghilangkan blok heap lossy) akan membantu beberapa. Tetapi tidak sebanyak indeks kustom, seperti (source_id, "timestamp", observation_timestamp DESC)atau lebih baik untuk hanya memindai indeks (source_id, "timestamp", observation_timestamp DESC, value, id).

jjanes
sumber
Terima kasih atas sarannya - saya pasti akan melihat pengindeksan kustom seperti yang Anda sarankan. Itu LIMIT 500dimaksudkan bagi saya untuk membatasi output, tetapi dalam kode produksi ini tidak terjadi. Saya akan mengedit posting saya untuk mencerminkan hal itu.
Pepijn Schoen
Dengan tidak adanya LIMIT, indeks mungkin jauh lebih efektif. Namun tetap patut dicoba.
jjanes
Anda benar - dengan LIMITdan saran Anda, eksekusi saat ini adalah 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)) Tetapi tanpa LIMITitu seperti sebelumnya. Bagaimana saya juga memanfaatkan Index Scandalam kasus itu dan bukan Bitmap Index/Heap Scan?
Pepijn Schoen