web-dev-qa-db-ja.com

Sum()集約クエリに関するPostgreSQL 9.6のパフォーマンスの問題

次の表があります。

     id                | bigint                      | not null default nextval('shares_id_seq'::regclass)
     poolid            | text                        | not null
     blockheight       | bigint                      | not null
     networkdifficulty | double precision            | not null
     miner             | text                        | not null
     worker            | text                        |
     ipaddress         | text                        | not null
     created           | timestamp without time zone | not null
     useragent         | text                        |
     payoutinfo        | text                        |
     difficulty        | double precision            | not null default 0

Indexes:
        "shares_pkey" PRIMARY KEY, btree (id)
        "idx_shares_pool_block" btree (poolid, blockheight)
        "idx_shares_pool_created" btree (poolid, created)
        "idx_shares_pool_miner" btree (poolid, miner)
        "idx_shares_pool_miner_diff" btree (poolid, miner, difficulty)

次のクエリに非常に時間がかかる理由を理解できません。

explain analyze SELECT SUM(difficulty) FROM shares WHERE poolid = 'xmr1' AND miner = '4BCeEPhodgPMbPWFN1dPwhWXdRX8q4mhhdZdA1dtSMLTLCEYvAj9QXjXAfF7CugEbmfBhgkqHbdgK9b2wKA6nqRZQCgvCDm';
                                                                                QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=4150712.54..4150712.55 rows=1 width=8) (actual time=25490.101..25490.101 rows=1 loops=1)
   ->  Bitmap Heap Scan on shares  (cost=389414.64..4143195.97 rows=3006629 width=8) (actual time=2499.409..24815.011 rows=7445802 loops=1)
         Recheck Cond: ((poolid = 'xmr1'::text) AND (miner = '4BCeEPhodgPMbPWFN1dPwhWXdRX8q4mhhdZdA1dtSMLTLCEYvAj9QXjXAfF7CugEbmfBhgkqHbdgK9b2wKA6nqRZQCgvCDm'::text))
         Rows Removed by Index Recheck: 55232916
         Heap Blocks: exact=149273 lossy=2641988
         ->  Bitmap Index Scan on idx_shares_pool_miner  (cost=0.00..388662.98 rows=3006629 width=0) (actual time=2449.977..2449.977 rows=7445802 loops=1)
               Index Cond: ((poolid = 'xmr1'::text) AND (miner = '4BCeEPhodgPMbPWFN1dPwhWXdRX8q4mhhdZdA1dtSMLTLCEYvAj9QXjXAfF7CugEbmfBhgkqHbdgK9b2wKA6nqRZQCgvCDm'::text))
 Planning time: 0.256 ms
 Execution time: 25490.137 ms
(9 rows)

このシナリオ用にインデックスidx_shares_pool_miner_diffを特別に作成しましたが、まだ使用されていません。 MVCCにより、PostgreSQLはインデックスを使用できませんか?

更新:

ランニング vacuum analyze shares推奨されるように、PostgreSQLは前述のインデックスを使用します。

 Aggregate  (cost=546165.94..546165.95 rows=1 width=8) (actual time=2489.446..2489.447 rows=1 loops=1)
   ->  Index Only Scan using idx_shares_pool_miner_diff on shares  (cost=0.69..537874.79 rows=3316458 width=8) (actual time=0.041..1924.396 rows=7461785 loops=1)
         Index Cond: ((poolid = 'xmr1'::text) AND (miner = '4BCeEPhodgPMbPWFN1dPwhWXdRX8q4mhhdZdA1dtSMLTLCEYvAj9QXjXAfF7CugEbmfBhgkqHbdgK9b2wKA6nqRZQCgvCDm'::text))
         Heap Fetches: 16575
 Planning time: 0.122 ms
 Execution time: 2489.477 ms
(6 rows)

並列ワーカーを使用しなくても、それが可能な限り速いと思います。

インデックスのみのスキャンに関するドキュメントを読んだ後、sharesテーブルへの頻繁な書き込みアクティビティ(> 200挿入/秒)が原因でパフォーマンスが低下し、多くのヒープフェッチが発生しているようです。興味深いことに、ヒープフェッチの数は明らかに増加しているだけで、決して減少していません。

1

テーブルがINSERTのみの場合、合計を(はるかに)速くする方法があります。

単調に増加する値(例ではidまたはcreatedのような)の列があると想定して、 MATERIALZED VIEW (最近の)指定されたしきい値よりも古い合計を事前計算します。次に、最近の追加の合計を追加します。

CREATE MATERIALIZED VIEW shares_summed AS
SELECT poolid, miner, SUM(difficulty) AS sum_diff
FROM   shares
GROUP  BY  poolid, miner
ORDER  BY  poolid, miner;  -- optional, but to optimize some more
WHERE  created <  '2018-01-01 0:0';

(poolid, miner)の組み合わせが多い場合は、UNIQUEインデックスを追加します。 CONCURRENTLYの更新にも必要です。以下を参照してください。

そのテーブルから行をフェッチしても、コストはほとんどかかりません。次に、最近の追加のみを追加します。

SELECT sum(sum_diff) AS total_sum  -- takes care of possible missing rows
FROM  (
   SELECT sum_diff
   FROM   shares_summed
   WHERE  poolid = 'xmr1'
   AND    miner = '4BCeEPhod...'

   UNION ALL
   SELECT SUM(difficulty)
   FROM   shares
   WHERE  poolid = 'xmr1'
   AND    miner  = '4BCeEPhod...'
   AND    created >= '2018-01-01 0:0'
   ) sub;                                                   

時々しきい値を調整するだけで、 REFRESH MVになります。 CONCURRENTLYオプションを使用して、同時実行の問題を回避するのが最適です多数のINSERTs検討してください:

値を覚えて、それに応じてクエリを調整します。別のテーブルに格納する場合があります。

さらに高速にするには、 パーティションテーブル を作成し、毎月(または何でも)後に計算済みの合計で別のパーティションを追加します。これを自動化するために毎月のcronジョブを簡単にスケジュールできます。その場合、しきい値は常に月の初日程度になります。

SELECT sum(sum_diff) AS total_sum
FROM  (
   SELECT SUM(difficulty) AS sum_diff
   FROM   shares_summed_master  -- includes all partitions
   WHERE  poolid = 'xmr1'
   AND    miner = '4BCeEPhod...'

   UNION ALL
   SELECT SUM(difficulty)
   FROM   shares
   WHERE  poolid = 'xmr1'
   AND    miner  = '4BCeEPhod...'
   AND    created >= date_trunc('month', now())  -- careful, current time zone affects it
   ) sub;           

重複がないこと、および事前に計算された合計が最新であることを確認してください。

Postgres 10を使用して、テーブルのパーティション分割が大幅に改善および簡略化されました。最新のポイントリリースを実行してください。Postgres10.2でいくつかのコーナーケースのバグが修正されました。上記のマニュアルへのリンクはバージョン9.6用です。 現在のバージョンはこちら

3