web-dev-qa-db-ja.com

Materializeを使用したネストされたループが原因でpostgresql 9.5でクエリが非常に遅くなる

Postgres 9.5に分析データベースがあります。制約 partitioning を利用して、イベントを月ごとのテーブルに分割するテーブル継承を使用しています。基本(親)テーブルには行が含まれていません。

私は比較的単純なクエリを持っていますが、プランナは、説明からわかるように、ネストされたループ内で具体化する完全に狂った計画を考えています。

クエリ

select count(person_id) as thecount from (
    select distinct A.person_id from event_page as O 
    join alias as A on (O.person_alias = A.alias) 
    where O.timestamp_ between '2017-4-28 04:00:00' and '2017-4-29 03:59:59.999' 
    and O.location_Host = 'www.foo.com' AND O.location_path= '/ca/sale' 
) as alias_3494697

説明: https://explain.depesz.com/s/IoO

私の理論では、制約パーティション化を使用したテーブルの継承により、プランナが何らかの形で動作しなくなっていると考えられます。ベーステーブルを4月の月次テーブルに置き換えると、パフォーマンスの良い健全な計画が得られます。

https://explain.depesz.com/s/Gpb

注上記のクエリとのわずかな違い-ベーステーブルの代わりに子テーブルを使用しています(つまり、継承はありません)。

select count(person_id) as thecount from (
    select distinct A.person_id from event_page_2017_4 as O 
    join alias as A on (O.person_alias = A.alias) 
    where O.timestamp_ between '2017-4-28 04:00:00' and '2017-4-29 03:59:59.999' 
    and O.location_Host = 'www.foo.com' AND O.location_path= '/ca/sale' 
) as alias_3494697

すべての統計は、真空分析を介して最新です。関連するスキーマは次のとおりです。

CREATE TABLE event_page (
    id                      CHAR(24) PRIMARY KEY,
    timestamp_              TIMESTAMP NOT NULL,
    person_alias            VARCHAR(128) NOT NULL,
    visitor_id              VARCHAR(128) NOT NULL,
    session_id              VARCHAR(24) NOT NULL,
    ip_address              VARCHAR(64) ,
    user_agent              VARCHAR(256) ,
    operating_system        VARCHAR(64) ,
    device_type             VARCHAR(64) ,
    browser                 VARCHAR(64) ,
    browser_major           VARCHAR(64) ,
    page_title              VARCHAR(1024) ,
    location_Host           VARCHAR(256) ,
    location_path           VARCHAR(1024) ,
    location_query          VARCHAR(1024) ,
    location_fragment       VARCHAR(1024) ,
    referrer_Host           VARCHAR(256) ,
    referrer_path           VARCHAR(1024) ,
    referrer_query          VARCHAR(1024) ,
    referrer_fragment       VARCHAR(1024) ,
        duration                INT
);



--the monthly tables all look like this example for April 2017
CREATE TABLE event_page_2017_4
(LIKE event_page including defaults including constraints including indexes,
CONSTRAINT page_2017_4_part CHECK ( timestamp_ >= '2017-4-01'::timestamp AND timestamp_ < '2017-05-01'::timestamp )
)
INHERITS (event_page);


--indexes are the sames on each of the monthly tables
CREATE INDEX ep_2017_4_location
  ON event_page_2017_4
  USING btree
  (location_Host varchar_pattern_ops, location_path varchar_pattern_ops, location_query varchar_pattern_ops, location_fragment varchar_pattern_ops, timestamp_, person_alias, session_id);

CREATE INDEX ep_2017_4_ts
  ON event_page_2017_4
  USING btree
  (timestamp_, person_alias, session_id);

CREATE INDEX ep_2017_4_a_ses_ts 
  ON event_page_2017_4
  USING btree
  (person_alias, session_id, timestamp_); 


--the alias tables and two indexes
CREATE TABLE alias (
    person_id               CHAR(24),
    alias                   VARCHAR(128) NOT NULL,
    first_seen              TIMESTAMP,
    soft                    BOOLEAN
);

ALTER TABLE ALIAS ADD CONSTRAINT alias_alias_pkey PRIMARY KEY(alias);
CREATE INDEX alias_a_p ON alias (alias, person_id);
CREATE INDEX alias_p_a ON alias (person_id, alias);

注:9.6.1にアップグレードすると問題は解消しました

2
maxTrialfire

いくつかの観察:

1。

遅いクエリのクエリプランは次のとおりです。

Event_page oのシーケンススキャン(cost = 10,000,000,000.00 .. 10,000,000,000.00 rows = 1 width = 274)

... _SET enable_seqscan = off;_で実行したことを示します。そして、それはあなたのPostgresバージョンがシーケンシャルスキャン以外の方法を積極的に見つけることができなかったことを意味します。

2。

constraint_exclusion のように有効にしましたか マニュアルはここに助言します

  1. constraint_exclusion 構成パラメーターが_postgresql.conf_で無効になっていないことを確認します。その場合、クエリは必要に応じて最適化されません。

3。

クエリの日の境界がパーティション分割と同期していません。 (しかし、それは与えられたクエリにすぐには影響しません。コメントを参照してください。)
CHECK制約は次のように解釈します。

_CHECK ( timestamp_ >= '2017-4-01'::timestamp AND timestamp_ < '2017-05-01'::timestamp )
_

timestamptimestamptz、および/またはタイムゾーンが混同されている可能性があります。または、翻訳で何かが失われ、質問がその点で誤解を招く可能性があります。

どちらの方法でも、WHERE句の式は、(日の境界が同期していたとしても)卑劣なコーナーケースを開きます。

_where O.timestamp_ between '2017-4-28 04:00:00' and '2017-4-29 03:59:59.999' _

Postgresのタイムスタンプは、小数点以下6桁を許可する8バイト整数として実装されます。 _'2017-4-29 03:59:59.9995'_を含む行は期待どおりに動作しません。詳細:

さらに、これは制約の除外ではうまく機能しません。必要以上のパーティションを読み取る必要があります。

代わりに使用:

_WHERE o.timestamp_ >= '2017-4-28 04:00' -- include lower bound
AND   o.timestamp_ <  '2017-4-29 04:00' -- exclude upper bound
_

クエリと同期してパーティションで「日」を取得したい場合があります-00:0004:00(または悪03:59:59.999)-そして最初にtimestamptzを使用するかもしれません。

4。

aliasのインデックスのみのスキャンが表示されます。

ヒープフェッチ:1918543

...非常に高いです。 visibility map に問題がある場合とない場合があります。 (参照: 可視性マップの更新 )テーブルで_VACUUM FULL_を実行しましたか? (比較 plpgsql-performanceのこのスレッド 。)単純なVACUUMを試してください。これにより、可視性マップが適切に更新されます。しかし、あなたは_enable_seqscan = off_で実行しているように見えるので、それをすべて言うのは難しいです。ここで他のすべてのものと組み合わせてこれがどのように機能するかわかりません。

Postgres 9.6(特に)のVACUUMにはさまざまな改良が加えられており、これが違いを説明している可能性があります。または、9.6で確認したのは偶然であり、他の要因によるものです。

5。

_alias.alias_がuniqueである場合(および参照整合性を想定できる場合)、クエリを簡略化できます。テーブルaliasにまったく参加しなくても、count(DISTINCT o.person_alias)だけです。お気に入り:

_SELECT count(DISTINCT o.person_alias) AS thecount
FROM   event_page o
WHERE  o.timestamp_ >= '2017-4-28 04:00' -- include lower bound
AND    o.timestamp_ <  '2017-4-29 04:00' -- exclude upper bound
AND    o.location_Host = 'www.foo.com'
AND    o.location_path = '/ca/sale';
_

(または、サブクエリを使用したように使用すると、count(DISTINCT ...)よりも高速になる場合があります。)

_alias.alias_が一意でない場合、クエリが間違っている/あいまいである可能性があります。

最初に_person_alias_ではなく_event_page_がテーブル_person_id_にあるのはなぜですか?

6。

データ型CHAR(24)をテーブルのPKとして使用しますが、これはほぼ間違いなく悪い選択です。 serialまたはbigserial列、あるいはuuidを検討してください。数値型は小さくて高速であり、照合規則やバイト長の変化などの負担がありません。

3