在Postgres中优化2000万行的“最新”查询


10

我的表如下所示:

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

索引存在于source_id,timestamp以及timestamp和id(CREATE INDEX timeseries_id_timestamp_combo_idx ON timeseries (id, timeseries DESC NULLS LAST))的组合上

其中有2000万行(好的,有120M,但是有source_id = 1的20M)。它有许多相同的条目,timestamp且有所不同observation_timestamp,描述了一个value发生在timestamp报告或观察到的事件observation_timestamp。例如,明天下午2点预测的温度与今天上午12点预测的温度相同。

理想情况下,此表可以很好地完成一些工作:

  • 批量插入新条目,有时一次插入10万
  • 选择观察到的时间范围内的数据(“一月到三月的温度预测是多少”)
  • 选择从某个点观察到的时间范围内观察到的数据(“如我们在11月1日所想到的,从1月到3月的温度预测如何看待”)

第二个是这个问题的核心。

表中的数据如下所示

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

查询的输出将类似于以下内容(仅表示了最新observation_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


我正在查看三个查询,它们都给我不好的表现

  • 带有LATERAL联接的递归CTE
  • 视窗功能
  • 区别开

(我知道他们目前还没有做同样的事情,但就我所知,它们可以很好地说明查询的类型。)

带有LATERAL联接的递归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;

性能:

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)

(仅EXPLAINEXPLAIN 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

我应该如何构造我的数据,是否存在不应该存在的扫描,通常可以将这些查询的时间改为〜1s(而不是〜120s)吗?

是否有其他查询数据以获取所需结果的方式?

如果没有,我应该看什么不同的基础架构/体系结构?


本质上您想要的是松散索引扫描或跳过扫描。那些即将到来。如果您想
Evan Carroll

Livin'处于边缘@EvanCarroll = P-考虑到我在Azure上使用Postgres甚至不可行,对我来说似乎还为时过早。
Pepijn Schoen

请显示没有限制的EXPLAIN ANALYZE计划(因为这是需要优化的),但要显示我在第一个答案中建议的更改。但是如果没有限制,我认为您要求在大约1秒钟内完成不可能的工作。也许您可以预先计算一些东西。
jjanes

绝对@jjanes-谢谢您的建议。我LIMIT现在从问题中删除了,并添加了输出EXPLAIN ANALYZE(尽管仅EXPLAINrecursive部分上)
Pepijn Schoen '18

Answers:


1

对于您的递归CTE查询,最终的查询ORDER BY (ts).id是不必要的,因为CTE自动按该顺序创建它们。删除该行可以使查询快得多,它可以提早停止,而不是生成20180572行,仅将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

在没有LIMIT的情况下,索引的效果可能会差很多。但是仍然值得一试。
jjanes

您是正确的-根据LIMIT和的建议,当前执行为356.482 msIndex 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.