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にアップグレードすると問題は解消しました
いくつかの観察:
遅いクエリのクエリプランは次のとおりです。
Event_page oのシーケンススキャン(cost = 10,000,000,000.00 .. 10,000,000,000.00 rows = 1 width = 274)
... _SET enable_seqscan = off;
_で実行したことを示します。そして、それはあなたのPostgresバージョンがシーケンシャルスキャン以外の方法を積極的に見つけることができなかったことを意味します。
constraint_exclusion のように有効にしましたか マニュアルはここに助言します :
- constraint_exclusion 構成パラメーターが_
postgresql.conf
_で無効になっていないことを確認します。その場合、クエリは必要に応じて最適化されません。
クエリの日の境界がパーティション分割と同期していません。 (しかし、それは与えられたクエリにすぐには影響しません。コメントを参照してください。)CHECK
制約は次のように解釈します。
_CHECK ( timestamp_ >= '2017-4-01'::timestamp AND timestamp_ < '2017-05-01'::timestamp )
_
timestamp
とtimestamptz
、および/またはタイムゾーンが混同されている可能性があります。または、翻訳で何かが失われ、質問がその点で誤解を招く可能性があります。
どちらの方法でも、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:00対04:00(または悪03:59:59.999)-そして最初にtimestamptz
を使用するかもしれません。
alias
のインデックスのみのスキャンが表示されます。
ヒープフェッチ:1918543
...非常に高いです。 visibility map に問題がある場合とない場合があります。 (参照: 可視性マップの更新 )テーブルで_VACUUM FULL
_を実行しましたか? (比較 plpgsql-performanceのこのスレッド 。)単純なVACUUM
を試してください。これにより、可視性マップが適切に更新されます。しかし、あなたは_enable_seqscan = off
_で実行しているように見えるので、それをすべて言うのは難しいです。ここで他のすべてのものと組み合わせてこれがどのように機能するかわかりません。
Postgres 9.6(特に)のVACUUM
にはさまざまな改良が加えられており、これが違いを説明している可能性があります。または、9.6で確認したのは偶然であり、他の要因によるものです。
_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
_にあるのはなぜですか?
データ型CHAR(24)
をテーブルのPKとして使用しますが、これはほぼ間違いなく悪い選択です。 serial
またはbigserial
列、あるいはuuid
を検討してください。数値型は小さくて高速であり、照合規則やバイト長の変化などの負担がありません。