タイムスタンプの範囲(1列)でのクエリの最適化


8

HerokuでPostgres 9.3を使用しています。

毎日多くの挿入と更新を行う100万件以上のレコードを含む「トラフィック」テーブルがあります。このテーブル全体でさまざまな時間範囲でSUM操作を実行する必要があります。これらの呼び出しには最大40秒かかる可能性があり、それを改善する方法に関する提案を聞きたいです。

このテーブルには次のインデックスが設定されています。

CREATE INDEX idx_traffic_partner_only ON traffic (dt_created) WHERE campaign_id IS NULL AND uuid_self <> uuid_partner;

SELECTステートメントの例を次に示します。

SELECT SUM("clicks") AS clicks, SUM("impressions") AS impressions
FROM "traffic"
WHERE "uuid_self" != "uuid_partner"
AND "campaign_id" is NULL
AND "dt_created" >= 'Sun, 29 Mar 2015 00:00:00 +0000'
AND "dt_created" <= 'Mon, 27 Apr 2015 23:59:59 +0000' 

そして、これはEXPLAIN ANALYZEです:

Aggregate  (cost=21625.91..21625.92 rows=1 width=16) (actual time=41804.754..41804.754 rows=1 loops=1)
  ->  Index Scan using idx_traffic_partner_only on traffic  (cost=0.09..20085.11 rows=308159 width=16) (actual time=1.409..41617.976 rows=302392 loops=1)
      Index Cond: ((dt_created >= '2015-03-29'::date) AND (dt_created <= '2015-04-27'::date))
Total runtime: 41804.893 ms

http://explain.depesz.com/s/gGA

この質問はSEの別の質問と非常に似ていますが、1つの質問は2つの列のタイムスタンプ範囲にわたってインデックスを使用し、そのクエリのインデックスプランナーはかなりずれた見積もりを持っていました。主な提案は、並べ替えられた複数列のインデックスを作成することでしたが、単一列のインデックスの場合はあまり効果がありません。他の提案はCLUSTER / pg_repackとGISTインデックスを使用することでしたが、通常のインデックスを使用するより良い解決策があるかどうかを確認したいので、まだ試していません。

タイムスタンプの範囲(2列)でのクエリの最適化

参考までに、DBでは使用されていない次のインデックスを試してみました。

INDEX idx_traffic_2 ON traffic (campaign_id, uuid_self, uuid_partner, dt_created);
INDEX idx_traffic_3 ON traffic (dt_created);
INDEX idx_traffic_4 ON traffic (uuid_self);
INDEX idx_traffic_5 ON traffic (uuid_partner);

編集:EXPLAIN(分析、詳細、コスト、バッファ)を実行し、これらは結果でした:

Aggregate  (cost=20538.62..20538.62 rows=1 width=8) (actual time=526.778..526.778 rows=1 loops=1)
  Output: sum(clicks), sum(impressions)
  Buffers: shared hit=47783 read=29803 dirtied=4
  I/O Timings: read=184.936
  ->  Index Scan using idx_traffic_partner_only on public.traffic  (cost=0.09..20224.74 rows=313881 width=8) (actual time=0.049..431.501 rows=302405 loops=1)
      Output: id, uuid_self, uuid_partner, impressions, clicks, dt_created... (other fields redacted)
      Index Cond: ((traffic.dt_created >= '2015-03-29'::date) AND (traffic.dt_created <= '2015-04-27'::date))
      Buffers: shared hit=47783 read=29803 dirtied=4
      I/O Timings: read=184.936
Total runtime: 526.881 ms

http://explain.depesz.com/s/7Gu6

テーブル定義:

CREATE TABLE traffic (
    id              serial,
    uuid_self       uuid not null,
    uuid_partner    uuid not null,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
)

idは主キーで、uuid_self、uuid_partner、campaign_idはすべて外部キーです。dt_updatedフィールドはpostgres関数で更新されます。


explain (buffers, analyze, verbose) ...もっと光を当てるかもしれません。
クレイグリンガー

ここで欠落している重要な情報の1つは、の正確なテーブル定義ですtraffic。また、2番目EXPLAINのグラフで42秒から0.5秒に低下したのはなぜですか。最初の実行はコールドキャッシュでしたか?
Erwin Brandstetter 2015

質問にテーブル定義を追加しました。はい、42秒から0.5秒はおそらくコールドキャッシュによるものですが、更新が非常に多いため、これはかなり一般的なことです。EXPLAIN ANALYZEをもう一度実行したところ、今回は56秒かかりました。私はもう一度それを実行し、それは.4sに下がりました。
エヴァンAppleby

にPK制約があると仮定しても安全idです。他の制約はありますか?NULLになる可能性のある2つの列が表示されます。それぞれのNULL値のパーセンテージは何ですか?何を手に入れますか?SELECT count(*) AS ct, count(campaign_id)/ count(*) AS camp_pct, count(dt_updated)/count(*) AS upd_pct FROM traffic;
Erwin Brandstetter 2015

うん、IDにはPK制約があり、uuid_self、uuid_partner、campaign_idにはFK制約があります。Campaign_idは99%+ NULLで、dt_updatedは0%NULLです。
エヴァンAppleby

回答:


3

ここで非常に奇妙な2つのこと:

  1. クエリは、100万行以上のテーブルから30万行を選択します。30%(または5%を超えるもの-行のサイズやその他の要因によって異なります)の場合、通常はインデックスを使用してもまったく効果がありません。私たちは見るべきシーケンシャルスキャンを

    例外はインデックスのみのスキャンですが、ここには表示されません。@Craigが提案する複数列のインデックスは、インデックスのみのスキャンを行う場合に最適なオプションです。あなたが言ったような多くの更新では、これはうまくいかないかもしれません、その場合、追加の列なしで、そしてあなたがすでに持っているインデックスだけである方が良いでしょう。テーブルに対してより積極的なautovacuum設定を使用すると、それを機能させることができる場合があります。個々のテーブルのパラメータを調整できます。

  2. Postgresはインデックスを使用しようとしている間、私は確かに見ることが期待されるビットマップ・インデックス・スキャンをその多くの行、ためではない通常のためのより良い選択である平野インデックス・スキャン、行の割合。Postgresがデータページごとに複数のヒットを予期すると(テーブルの統計から判断すると)、通常はビットマップインデックススキャンに切り替わります。

そのことから判断すると、コスト設定が不十分である(そしておそらくテーブル統計も)のではないかと思います。に比べて設定が低すぎるrandom_page_costか、設定が低すぎる可能性があります。リンクをたどってマニュアルを読んでください。cpu_index_tuple_cost seq_page_cost

コメントで調べたように、コールドキャッシュが大きな要因であるという観察にも適合します。誰も長い間触れていないテーブル(の一部)にアクセスしているか、キャッシュが(まだ)設定されていないテストシステムで実行していますか?
そうでない場合は、関連するデータのほとんどをDBにキャッシュするのに十分なRAMがありません。その結果、ランダムアクセスは、データがキャッシュに存在する場合、シーケンシャルアクセスよりもはるかにコストがかかります。実際の状況によっては、より良いクエリプランを取得するために調整が必要になる場合があります。

最初の読み取り専用の応答が遅い場合、もう1つの要素、ヒントビットについて言及する必要がありますPostgres Wikiとこの関連質問で詳細を読んでください

または、テーブルが非常に肥大化しています。その場合、インデックススキャンは理にかなっており、引用した以前の回答でCLUSTER/pg_repackを再び参照します。(または単に設定をVACUUM FULL)調査しますVACUUM。これらはで重要many inserts and updates every dayです。

UPDATEパターンによっては、FILLFACTOR100未満も考慮します。ほとんどの場合、新しく追加された行のみを更新する場合は、テーブルを圧縮したFILLFACTER に低い値を設定して、新しいページだけが更新の余地を保つようにします。

スキーマ

campaign_id99%+ NULLでdt_updatedあり、0%NULLです。

列のシーケンスを少し調整して、1行あたり8バイトを節約します(campaign_idがNULL である場合の99%の場合)。

CREATE TABLE traffic (
    uuid_self       uuid not null REFERENCES ... ,
    uuid_partner    uuid not null REFERENCES ... ,
    id              serial PRIMARY KEY,
    impressions     integer NOT NULL DEFAULT 1,
    clicks          integer NOT NULL DEFAULT 0,
    campaign_id     integer,
    dt_created      DATE DEFAULT CURRENT_DATE NOT NULL,
    dt_updated      TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
);

詳細な説明と詳細へのリンク:

測定する:


提案をありがとう。私は現在、Herokuを介して設定された組み込みの自動バキューム機能に依存しており、トラフィックテーブルはほぼ毎日バキュームされます。テーブルの統計とフィルファクターの変更とpg_repackの使用とレポートについて詳しく説明します。
Evan Appleby

2

大きなインデックスで大量のデータをクエリしているように見えるので、遅いです。特に問題はありません。

PostgreSQL 9.3または9.4を使用している場合は、これをソートのカバーインデックスにして、インデックスのみのスキャンを取得できるかどうかを確認することができます。

CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created, clicks, impressions)
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

PostgreSQLには、真のカバーするインデックスや、単なる値であり、Bツリーの一部ではないインデックス用語のサポートがないため、これらの機能を使用する場合よりも低速でコストがかかります。可視性マップを最新の状態に保つのに十分な頻度でバキュームが実行される場合は、プレーンインデックススキャンより優れている可能性があります。


理想的には、PostgreSQLはMS-SQL Serverのようなインデックスで補助データフィールドをサポートしますこの構文はPostgreSQLでは機能しません)。

-- This will not work in PostgreSQL (at least 9.5)
-- it's an example of what I wish did work. Don't
-- comment to say it doesn't work.
--
CREATE INDEX idx_traffic_partner_only 
ON traffic (dt_created)
INCLUDING (clicks, impressions) -- auxillary data columns
WHERE campaign_id IS NULL 
  AND uuid_self <> uuid_partner;

提案をありがとう。カバリングインデックスを試しましたが、DBはそれを無視し、他のインデックスを使用しました。他のインデックスを削除してカバリングインデックスのみを使用することをお勧めしますか(または、それを必要とする状況ごとに複数のカバリングインデックスのみを使用します)?また、元の質問にEXPLAIN(ANALYZE、VERBOSE、COSTS、BUFFERS)を追加しました。
エヴァンAppleby

奇数。たぶんプランナーは、複数の集計が表示される場合、インデックスのみのスキャンを選択するほど賢くないかもしれませんが、私はそれを可能だと思ったでしょう。コストパラメータ(random_page_costなど)で遊んでみてください。また、テスト目的では、インデックスのみのスキャンを強制するかどうかを確認set enable_indexscan = offしてset enable_seqscan = offから再実行することだけを確認し、強制する場合は、explain analyzeからの推定コストを確認します。
クレイグリンガー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.