เพิ่มประสิทธิภาพการค้นหา 'ล่าสุด' ใน Postgres บนแถว 20M


10

ตารางของฉันมีลักษณะดังนี้:

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

มีดัชนีอยู่ใน source_id, การประทับเวลาและในคอมโบของการประทับเวลาและรหัส ( CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST))

มีแถวอยู่ 20M (ตกลงมี 120M แต่ 20M กับ source_id = 1) แต่ก็มีหลายรายการสำหรับเดียวกันtimestampที่แตกต่างกันobservation_timestampซึ่งอธิบายvalueที่เกิดขึ้นในรายงานหรือข้อสังเกตที่timestamp observation_timestampเช่นอุณหภูมิที่คาดการณ์ไว้สำหรับวันพรุ่งนี้ 14.00 น. ตามที่คาดการณ์ในวันนี้เวลา 12.00 น.

โดยที่ตารางนี้ทำสิ่งต่าง ๆ ได้ดี:

  • ชุดการแทรกรายการใหม่บางครั้ง 100K ในเวลา
  • การเลือกข้อมูลที่สังเกตได้สำหรับช่วงจับเวลา ("การคาดการณ์อุณหภูมิสำหรับเดือนมกราคมถึงมีนาคม" คืออะไร)
  • การเลือกข้อมูลที่สังเกตสำหรับการจับเวลาในขณะที่สังเกตจากจุดหนึ่ง ("มุมมองของการคาดการณ์อุณหภูมิสำหรับเดือนมกราคมถึงมีนาคมตามที่เราคิดไว้เมื่อวันที่ 1 พฤศจิกายน")

คนที่สองคือคนที่เป็นศูนย์กลางของคำถามนี้

ข้อมูลในตารางจะมีลักษณะดังนี้

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

และผลลัพธ์ของแบบสอบถามจะมีลักษณะดังต่อไปนี้ (เฉพาะแถวของการสังเกตการณ์ล่าสุด _timestamp ที่แสดง)

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

ฉันได้ตรวจสอบเนื้อหาก่อนหน้านี้แล้วเพื่อเพิ่มประสิทธิภาพการค้นหาเหล่านี้ ได้แก่

... ด้วยความสำเร็จที่ จำกัด

ฉันได้พิจารณาการสร้างตารางแยกต่างหากtimestampในนั้นเพื่อให้ง่ายต่อการอ้างอิงในภายหลัง แต่เนื่องจากความbatch inserting new entriesสำคัญเชิงหัวใจที่ค่อนข้างสูงของผู้ที่ฉันสงสัยว่าพวกเขาจะช่วยฉัน - นอกจากนี้ฉันกังวลว่ามันจะขัดขวางไม่ให้สำเร็จ


ฉันกำลังค้นหาข้อความค้นหาสามข้อและพวกเขาทั้งหมดให้ประสิทธิภาพที่ไม่ดี

  • CTE แบบเรียกซ้ำพร้อมการเข้าร่วม LATERAL ครั้ง
  • ฟังก์ชั่นหน้าต่าง
  • ปิดกั้น

(ฉันรู้ว่าพวกเขาไม่ได้ทำสิ่งเดียวกันในตอนนี้ แต่พวกเขาทำหน้าที่เป็นตัวอย่างที่ดีของประเภทการสืบค้นเท่าที่ฉันเห็น)

CTE แบบเรียกซ้ำพร้อมการเข้าร่วม 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;

ประสิทธิภาพ:

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)

(เท่านั้นEXPLAIN, EXPLAIN ANALYZEไม่สมบูรณ์, ใช้เวลา> 24 ชั่วโมงเพื่อทำแบบสอบถามให้สมบูรณ์)

ฟังก์ชั่นหน้าต่าง

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;

ประสิทธิภาพ:

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

ปิดกั้น

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

ประสิทธิภาพ:

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

ฉันจะจัดโครงสร้างข้อมูลของฉันได้อย่างไรมีการสแกนที่ไม่ควรอยู่ที่นั่นเป็นไปได้หรือไม่ที่จะได้รับข้อความค้นหาเหล่านี้เป็น ~ 1 วินาที (แทนที่จะเป็น ~ 120 วินาที)

มีวิธีอื่นในการสอบถามข้อมูลเพื่อให้ได้ผลลัพธ์ที่ฉันต้องการหรือไม่

ถ้าไม่ฉันควรดูโครงสร้างพื้นฐาน / สถาปัตยกรรมที่แตกต่างกันอย่างไร


สิ่งที่คุณต้องการคือการสแกนดัชนีแบบหลวมหรือข้ามการสแกน กำลังจะมาเร็ว ๆ นี้ คุณสามารถใช้โปรแกรมแก้ไขได้ทันทีหากคุณต้องการยุ่งกับมันpostgresql-archive.org/Index-Skip-Scan-td6025532.htmlมันแทบจะไม่มีเดือน = P
Evan Carroll

Livin 'on the edge @EvanCarroll = P - ดูเหมือนว่าจะเร็วไปสำหรับฉันเพราะฉันใช้ Postgres บน Azure ซึ่งไม่สามารถทำได้
Pepijn Schoen

โปรดแสดงแผนวิเคราะห์อธิบายโดยไม่มีขีด จำกัด (เนื่องจากเป็นสิ่งที่ต้องเพิ่มประสิทธิภาพ) แต่ด้วยการเปลี่ยนแปลงที่ฉันแนะนำในคำตอบแรกของฉัน แต่ถ้าไม่มีข้อ จำกัด ฉันคิดว่าคุณกำลังของานที่เป็นไปไม่ได้ที่จะทำใน ~ 1 วินาที บางทีคุณสามารถคำนวณบางสิ่งล่วงหน้าได้
jjanes

@jjanes อย่างแน่นอน - ขอบคุณสำหรับคำแนะนำ ฉันได้ลบออกLIMITจากคำถามตอนนี้และเพิ่มผลลัพธ์ด้วยEXPLAIN ANALYZE(เฉพาะEXPLAINในrecursiveส่วนเท่านั้น)
Pepijn Schoen

คำตอบ:


1

ด้วยแบบสอบถาม CTE แบบเรียกซ้ำของคุณสุดท้ายORDER BY (ts).idนั้นไม่จำเป็นเนื่องจาก CTE จะสร้างพวกเขาขึ้นโดยอัตโนมัติตามลำดับนั้น การลบที่ควรทำให้การสืบค้นเร็วขึ้นมากมันสามารถหยุดเร็วกว่าการสร้าง 20,180,572 แถวเท่านั้นที่จะทิ้งทั้งหมด แต่ 500 ออกไป นอกจากนี้การสร้างดัชนี(source_id, id, timestamp desc nulls last)ควรปรับปรุงให้ดียิ่งขึ้น

สำหรับอีกสองแบบสอบถามเพิ่ม work_mem มากพอที่บิตแมปพอดีกับหน่วยความจำ (เพื่อกำจัดบล็อกฮีปที่สูญหาย) จะช่วยได้บ้าง แต่ไม่มากเท่าที่ดัชนีกำหนดเองเช่นหรือดีกว่ายังสำหรับดัชนีเพียงสแกน(source_id, "timestamp", observation_timestamp DESC)(source_id, "timestamp", observation_timestamp DESC, value, id)


ขอบคุณสำหรับคำแนะนำ - ฉันจะดูเป็นดัชนีที่กำหนดเองอย่างที่คุณแนะนำ สิ่งLIMIT 500นี้มีไว้สำหรับฉันที่จะ จำกัด การส่งออก แต่ในรหัสการผลิตนี้จะไม่เกิดขึ้น ฉันจะแก้ไขโพสต์ของฉันเพื่อสะท้อนถึงสิ่งนั้น
Pepijn Schoen

หากไม่มีข้อ จำกัด ดัชนีอาจมีประสิทธิภาพน้อยกว่ามาก แต่ก็ยังคุ้มค่าที่จะลอง
jjanes

คุณถูกต้อง - ด้วยLIMITและข้อเสนอแนะของคุณการดำเนินการในปัจจุบันคือ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)) แต่ไม่LIMITเหมือนเมื่อก่อน ฉันจะใช้ประโยชน์จากIndex Scanในกรณีนั้นได้Bitmap Index/Heap Scanอย่างไรและไม่ใช่
Pepijn Schoen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.