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)

そこには20M行あります(OK、120Mありますが、source_id = 1で20Mです)。それは同じのために多くのエントリを持ってtimestamp変化させてobservation_timestamp説明した、valueで発生したtimestamp報告かで観察しますobservation_timestamp。たとえば、今日の午前12時に予測されるように、明日の午後2時に予測される気温。

理想的には、このテーブルはいくつかのことをうまく行います:

  • 新しいエントリのバッチ挿入、時には一度に100K
  • 時間範囲で観測されたデータを選択する(「1月から3月までの気温予測は」)
  • 特定の時点から観測された時間範囲で観測されたデータを選択する(「11月1日に考えたように、1月から3月までの気温予測のビューは何ですか」)

2つ目は、この質問の中心となるものです。

テーブルのデータは次のようになります

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


3つのクエリを調べていますが、すべてクエリのパフォーマンスが低くなっています

  • 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

データをどのように構造化する必要がありますか?存在してはならないスキャンはありますか?これらのクエリを(120秒ではなく)〜1秒にすることは一般的に可能ですか?

必要な結果を得るためにデータをクエリする別の方法はありますか?

そうでない場合、どの異なるインフラストラクチャ/アーキテクチャを検討する必要がありますか?


基本的に必要なのは、ルーズインデックススキャンまたはスキップスキャンです。それらはすぐに来ています。あなたが混乱する場合は、それはとあなたは今、パッチを適用することができますpostgresql-archive.org/Index-Skip-Scan-td6025532.htmlそれがやっと古い= P月だ
エヴァンキャロル

Livin 'on the edge @EvanCarroll = P-私は、AzureでPostgresを使用しているので、実行できないことを考えると、少し早すぎるようです。
Pepijn Schoen

制限なしでEXPLAIN ANALYZEプランを示してください(これが最適化の必要があるためです)が、最初の回答で変更を推奨しました。しかし、LIMITSがないと、1秒以内に不可能な量の作業を実行するよう求めていると思います。多分あなたはいくつかのものを事前計算することができます。
jjanes

@jjanes絶対に-提案をありがとう 私は削除しているLIMIT今、質問から、として出力を追加しましたEXPLAIN ANALYZE(だけEXPLAINrecursive一部が)
Pepijnシェーン

回答:


1

再帰的なCTEクエリではORDER BY (ts).id、CTEがその順序で自動的に作成するため、ファイナルは不要です。これを削除するとクエリがはるかに速くなり、20,180,572行だけを生成して500以外のすべてを破棄するのではなく、早期に停止できます。また、インデックスを構築(source_id, id, timestamp desc nulls last)すると、さらに改善されます。

他の2つのクエリでは、ビットマップがメモリに収まるように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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.