Tối ưu hóa truy vấn 'mới nhất' trong Postgres trên 20 triệu hàng


10

Bảng của tôi trông như sau:

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

các chỉ mục tồn tại trên source_id, dấu thời gian và trên một tổ hợp dấu thời gian và id ( CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST))

Có 20M hàng trong đó (OK, có 120M, nhưng 20M với source_id = 1). Nó có nhiều mục giống nhau timestampvới sự thay đổi observation_timestamp, mô tả một sự kiện valuexảy ra tại timestampbáo cáo hoặc quan sát tại observation_timestamp. ví dụ: Nhiệt độ dự đoán cho 2 giờ chiều mai như dự đoán hôm nay lúc 12 giờ sáng.

Lý tưởng nhất là bảng này làm một vài điều tốt:

  • hàng loạt chèn các mục mới, đôi khi 100K một lần
  • chọn dữ liệu quan sát cho bộ đếm thời gian ("dự đoán nhiệt độ cho tháng 1 đến tháng 3")
  • chọn dữ liệu được quan sát cho các bộ đếm thời gian như được quan sát từ một điểm nhất định ("quan điểm của các dự đoán nhiệt độ cho tháng 1 đến tháng 3 như chúng ta nghĩ vào ngày 1 tháng 11")

Câu hỏi thứ hai là câu hỏi chính.

Dữ liệu trong bảng sẽ trông như sau

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

và một đầu ra của truy vấn sẽ trông như sau (chỉ có hàng của obs_timestamp mới nhất được đại diện)

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

Tôi đã tham khảo một số tài liệu trước đó để tối ưu hóa các truy vấn này, cụ thể là

... với thành công hạn chế.

Tôi đã cân nhắc việc tạo một bảng riêng biệt với timestampnó để dễ dàng tham khảo về sau hơn, nhưng do tính tương đối cao của những người tôi nghi ngờ liệu họ có giúp tôi không - ngoài ra tôi lo ngại rằng nó sẽ cản trở việc thực hiện batch inserting new entries.


Tôi đang xem xét ba truy vấn và tất cả chúng đều cho tôi hiệu suất kém

  • CTE đệ quy với LATITH tham gia
  • Chức năng cửa sổ
  • KHOẢNG CÁCH TRÊN

(Tôi biết rằng họ không hoàn toàn làm điều tương tự vào lúc này, nhưng họ phục vụ như những minh họa tốt về loại truy vấn theo như tôi thấy.)

CTE đệ quy với LATITH tham gia

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;

Hiệu suất:

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)

(chỉ EXPLAIN, EXPLAIN ANALYZEkhông thể hoàn thành, mất> 24 giờ để hoàn thành truy vấn)

Chức năng cửa sổ

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;

Hiệu suất:

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

KHOẢNG CÁCH TRÊN

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

Hiệu suất:

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

Tôi nên cấu trúc dữ liệu của mình như thế nào, có quét không nên ở đó không, nói chung có thể đưa các truy vấn này đến ~ 1 giây (thay vì ~ 120 giây) không?

Có cách nào khác để truy vấn dữ liệu để có được kết quả tôi muốn không?

Nếu không, tôi nên xem xét cơ sở hạ tầng / kiến ​​trúc khác nhau?


Những gì về cơ bản bạn muốn là quét chỉ mục lỏng lẻo hoặc bỏ qua quét. Những người đang đến sớm. Bạn có thể áp dụng các bản vá ngay bây giờ nếu bạn muốn muddle với nó postgresql-archive.org/Index-Skip-Scan-td6025532.html nó chưa đầy một tháng tuổi = P
Evan Carroll

Livin 'trên cạnh @EvanCarroll = P - điều đó có vẻ hơi quá sớm đối với tôi, vì tôi đang sử dụng Postgres trên Azure thậm chí không thể thực hiện được.
Pepijn Schoen

Vui lòng hiển thị EXPLAIN ANALYZE mà không có GIỚI HẠN (vì đó là những gì cần được tối ưu hóa), nhưng với những thay đổi tôi đã đề xuất trong câu trả lời đầu tiên của mình. Nhưng không có GIỚI HẠN, tôi nghĩ bạn đang yêu cầu một lượng công việc không thể thực hiện được trong ~ 1 giây. Có lẽ bạn có thể tính toán trước một số thứ.
jjanes

@jjanes hoàn toàn - cảm ơn bạn đã gợi ý. Bây giờ tôi đã xóa LIMITcâu hỏi và thêm đầu ra với EXPLAIN ANALYZE(chỉ EXPLAINtrên recursivemột phần)
Pepijn Schoen

Câu trả lời:


1

Với truy vấn CTE đệ quy của bạn, cuối cùng ORDER BY (ts).idlà không cần thiết vì CTE tự động tạo chúng theo thứ tự đó. Loại bỏ điều đó sẽ làm cho truy vấn nhanh hơn nhiều, nó có thể dừng sớm hơn là tạo ra 20.180.572 hàng chỉ để ném tất cả trừ 500 đi. Ngoài ra, xây dựng chỉ số trên (source_id, id, timestamp desc nulls last)sẽ cải thiện nó hơn nữa.

Đối với hai truy vấn khác, việc tăng work_mem đủ để bitmap phù hợp với bộ nhớ (để loại bỏ các khối heap mất mát) sẽ giúp ích cho một số người. Nhưng không nhiều như các chỉ mục tùy chỉnh, chẳng hạn như (source_id, "timestamp", observation_timestamp DESC)hoặc tốt hơn cho chỉ quét chỉ mục (source_id, "timestamp", observation_timestamp DESC, value, id).


Cảm ơn bạn đã gợi ý - Tôi chắc chắn sẽ xem xét lập chỉ mục tùy chỉnh như bạn đề xuất. Điều LIMIT 500này có nghĩa là để tôi giới hạn đầu ra, nhưng trong mã sản xuất thì điều này không xảy ra. Tôi sẽ chỉnh sửa bài viết của mình để phản ánh điều đó.
Pepijn Schoen

Trong trường hợp không có GIỚI HẠN, các chỉ mục có thể kém hiệu quả hơn rất nhiều. Nhưng vẫn đáng để thử.
jjanes

Bạn đã đúng - với các LIMITđề xuất và của bạn, hiện đang thực thi là 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)) Nhưng không có LIMITnhư trước. Làm thế nào tôi cũng sẽ tận dụng Index Scantrong trường hợp đó và không phải là Bitmap Index/Heap Scan?
Pepijn Schoen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.