問題文の
私は、次の定義を持つ表 "event_statistics" があります。集計クエリ
CREATE TABLE public.event_statistics (
id int4 NOT NULL DEFAULT nextval('event_statistics_id_seq'::regclass),
client_id int4 NULL,
session_id int4 NULL,
action_name text NULL,
value text NULL,
product_id int8 NULL,
product_options jsonb NOT NULL DEFAULT '{}'::jsonb,
url text NULL,
url_options jsonb NOT NULL DEFAULT '{}'::jsonb,
visit int4 NULL DEFAULT 0,
date_update timestamptz NULL,
CONSTRAINT event_statistics_pkey PRIMARY KEY (id),
CONSTRAINT event_statistics_client_id_session_id_sessions_client_id_id_for
FOREIGN KEY
(client_id,session_id) REFERENCES <?>() ON DELETE CASCADE ON UPDATE CASCADE
)
WITH (
OIDS=FALSE
) ;
CREATE INDEX regdate ON public.event_statistics (date_update
timestamptz_ops) ;
とテーブル "のクライアントを":私は必要なもの
CREATE TABLE public.clients (
id int4 NOT NULL DEFAULT nextval('clients_id_seq'::regclass),
client_name text NULL,
client_hash text NULL,
CONSTRAINT clients_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
) ;
CREATE INDEX clients_client_name_idx ON public.clients (client_name
text_ops) ;
特定の "date_update"範囲グループ化の "action_name"タイプの "event_statistics"テーブル内のイベントの数を "action_name"と特定のタイムステップおよび特定のクライアントのすべてのものでカウントすることです。
- 現在:
の目標は次のように異なっている必要があり、レポートの日付を選択するオプションとチャートのインターバル時間ステップに依存して、当社のウェブサイト上で自分のダッシュボード上の各クライアントに関連するすべてのイベントのための統計情報を提供することです日 - 1時間あたりのカウント。
- 1日と< = 1ヶ月 - 毎日のカウント。
- 1ヶ月および< = 6ヶ月 - 各週のカウント。
- 6ヶ月〜月。私が何をしたか
:
SELECT t.date, A.actionName, count(E.id)
FROM generate_series(current_date - interval '1 week',now(),interval '1
day') as t(date) cross join
(values
('page_open'),
('product_add'),
('product_buy'),
('product_event'),
('product_favourite'),
('product_open'),
('product_share'),
('session_start')) as A(actionName) left join
(select action_name,date_trunc('day',e.date_update) as dateTime, e.id
from event_statistics as e
where e.client_id = (select id from clients as c where c.client_name =
'client name') and
(date_update between (current_date - interval '1 week') and now())) E
on t.date = E.dateTime and A.actionName = E.action_name
group by A.actionName,t.date
order by A.actionName,t.date;
それは先週のイベントタイプと一日でイベントをカウントするために、10秒以上の時間がかかりすぎます。数週間、数ヶ月、年ごとに異なるグループ間隔(今日の各時間、月の日、週、月)のように、より速く同じ時間を実行できるようにする必要があります。
クエリプラン:
GroupAggregate (cost=171937.16..188106.84 rows=1600 width=44)
Group Key: "*VALUES*".column1, t.date
InitPlan 1 (returns $0)
-> Seq Scan on clients c (cost=0.00..1.07 rows=1 width=4)
Filter: (client_name = 'client name'::text)
-> Merge Left Join (cost=171936.08..183784.31 rows=574060 width=44)
Merge Cond: (("*VALUES*".column1 = e.action_name) AND (t.date =(date_trunc('day'::text, e.date_update))))
-> Sort (cost=628.77..648.77 rows=8000 width=40)
Sort Key: "*VALUES*".column1, t.date
-> Nested Loop (cost=0.02..110.14 rows=8000 width=40)
-> Function Scan on generate_series t (cost=0.02..10.02 rows=1000 width=8)
-> Materialize (cost=0.00..0.14 rows=8 width=32)
-> Values Scan on "*VALUES*" (cost=0.00..0.10 rows=8 width=32)
-> Materialize (cost=171307.32..171881.38 rows=114812 width=24)
-> Sort (cost=171307.32..171594.35 rows=114812 width=24)
Sort Key: e.action_name, (date_trunc('day'::text, e.date_update))
-> Index Scan using regdate on event_statistics e (cost=0.57..159302.49 rows=114812 width=24)
Index Cond: ((date_update > (('now'::cstring)::date - '7 days'::interval)) AND (date_update <= now()))
Filter: (client_id = $0)
「event_statistics」テーブルは、行の50人の以上の何百万を持っており、それが唯一のクライアントが追加され、レコードは変更されませんと一緒に成長します。
私はさまざまなクエリプランとインデックスを試しましたが、より広い期間で集計すると許容速度に達しませんでした。 私はこの問題とStackOverflowの上でこの問題を解決する方法と、いくつかのブログのさまざまな側面を学ぶ一週間を過ごしたが、最良の方法は何かまだわからないました:client_idのか、日付範囲によって
- パーティション
- 前結果テーブルを分離して集計し、それを毎日更新してください(元のテーブルに挿入するか、そのマテリアライズドビューまたはWebサイトから別のアプリケーションをスケジュールするか、ウェブサイトからのリクエストによっても最適です)
- DBスキーマ設計をクライアントごとのスキーマに変更するか、シャーディングを適用する
- 変更サーバーハードウェア(CPU I ntel Xeon E7-4850 2.00GHz、RAM 6GB、それはウェブアプリケーションとDBの両方のホストです)
- Postgres-XL などのOLAP機能を使用した分析用に別のDBを使用していますか?
私はevent_statistics(client_id asc、action_name asc、date_update asc、id)でbtreeインデックスも試しました。また、インデックスのみのスキャンでは高速でしたが、それでも十分ではなく、ディスク容量の使用に関してあまりよくありません。
この問題を解決する最善の方法は何ですか?
更新
要求されたように、explain (analyze, verbose)
コマンドの出力:
GroupAggregate (cost=860934.44..969228.46 rows=1600 width=44) (actual time=52388.678..54671.187 rows=64 loops=1)
Output: t.date, "*VALUES*".column1, count(e.id)
Group Key: "*VALUES*".column1, t.date
InitPlan 1 (returns $0)
-> Seq Scan on public.clients c (cost=0.00..1.07 rows=1 width=4) (actual time=0.058..0.059 rows=1 loops=1)
Output: c.id
Filter: (c.client_name = 'client name'::text)
Rows Removed by Filter: 5
-> Merge Left Join (cost=860933.36..940229.77 rows=3864215 width=44) (actual time=52388.649..54388.698 rows=799737 loops=1)
Output: t.date, "*VALUES*".column1, e.id
Merge Cond: (("*VALUES*".column1 = e.action_name) AND (t.date = (date_trunc('day'::text, e.date_update))))
-> Sort (cost=628.77..648.77 rows=8000 width=40) (actual time=0.190..0.244 rows=64 loops=1)
Output: t.date, "*VALUES*".column1
Sort Key: "*VALUES*".column1, t.date
Sort Method: quicksort Memory: 30kB
-> Nested Loop (cost=0.02..110.14 rows=8000 width=40) (actual time=0.059..0.080 rows=64 loops=1)
Output: t.date, "*VALUES*".column1
-> Function Scan on pg_catalog.generate_series t (cost=0.02..10.02 rows=1000 width=8) (actual time=0.043..0.043 rows=8 loops=1)
Output: t.date
Function Call: generate_series(((('now'::cstring)::date - '7 days'::interval))::timestamp with time zone, now(), '1 day'::interval)
-> Materialize (cost=0.00..0.14 rows=8 width=32) (actual time=0.002..0.003 rows=8 loops=8)
Output: "*VALUES*".column1
-> Values Scan on "*VALUES*" (cost=0.00..0.10 rows=8 width=32) (actual time=0.004..0.005 rows=8 loops=1)
Output: "*VALUES*".column1
-> Materialize (cost=860304.60..864168.81 rows=772843 width=24) (actual time=52388.441..54053.748 rows=799720 loops=1)
Output: e.id, e.date_update, e.action_name, (date_trunc('day'::text, e.date_update))
-> Sort (cost=860304.60..862236.70 rows=772843 width=24) (actual time=52388.432..53703.531 rows=799720 loops=1)
Output: e.id, e.date_update, e.action_name, (date_trunc('day'::text, e.date_update))
Sort Key: e.action_name, (date_trunc('day'::text, e.date_update))
Sort Method: external merge Disk: 39080kB
-> Index Scan using regdate on public.event_statistics e (cost=0.57..753018.26 rows=772843 width=24) (actual time=31.423..44284.363 rows=799720 loops=1)
Output: e.id, e.date_update, e.action_name, date_trunc('day'::text, e.date_update)
Index Cond: ((e.date_update >= (('now'::cstring)::date - '7 days'::interval)) AND (e.date_update <= now()))
Filter: (e.client_id = $0)
Rows Removed by Filter: 2983424
Planning time: 7.278 ms
Execution time: 54708.041 ms
痛み低カーディナリティーのテキスト列action_nameでソートされているように見えます。 (個人的に、私はここで数値のaction_idを好むだろう)また、(func)calendartableと(値)action_name preudo-tablesの両方は、最適化(インデックス、統計)のために使用可能なフックを持っていない、 )テーブル – wildplasser
ヒントありがとうございます。はい、問題は、低速の外部ディスクの並べ替えとすべてのクライアントのデータを読み取ることにあるようです。しかし何らかの理由で私は記事の最後に書いたように、カバーインデックスでもソートの必要性を排除できませんでした。このような索引では、「work_mem」を十分に増やし、メモリー内ソートが使用されたにもかかわらず、「event_statistics」表の読み込みが遅いために十分ではない場合には、はるかに高速です。 – atikeen
IMOサブクエリで事前集計できます。 1600個以上の集合体が生成されることはありません。 – wildplasser