[英]Improve PostgresSQL aggregation query performance
我正在从 Postgres 表中聚合数据,查询大约需要 2 秒,我想将其减少到不到一秒。
请在下面找到执行细节:
询问
select
a.search_keyword,
hll_cardinality( hll_union_agg(a.users) ):: int as user_count,
hll_cardinality( hll_union_agg(a.sessions) ):: int as session_count,
sum(a.total) as keyword_count
from
rollup_day a
where
a.created_date between '2018-09-01' and '2019-09-30'
and a.tenant_id = '62850a62-19ac-477d-9cd7-837f3d716885'
group by
a.search_keyword
order by
session_count desc
limit 100;
表元数据
查询计划
Custom Scan (cost=0.00..0.00 rows=0 width=0) (actual time=1722.685..1722.694 rows=100 loops=1)
Task Count: 1
Tasks Shown: All
-> Task
Node: host=localhost port=5454 dbname=postgres
-> Limit (cost=64250.24..64250.49 rows=100 width=42) (actual time=1783.087..1783.106 rows=100 loops=1)
-> Sort (cost=64250.24..64558.81 rows=123430 width=42) (actual time=1783.085..1783.093 rows=100 loops=1)
Sort Key: ((hll_cardinality(hll_union_agg(sessions)))::integer) DESC
Sort Method: top-N heapsort Memory: 33kB
-> GroupAggregate (cost=52933.89..59532.83 rows=123430 width=42) (actual time=905.502..1724.363 rows=212633 loops=1)
Group Key: search_keyword
-> Sort (cost=52933.89..53636.53 rows=281055 width=54) (actual time=905.483..1351.212 rows=280981 loops=1)
Sort Key: search_keyword
Sort Method: external merge Disk: 18496kB
-> Seq Scan on rollup_day a (cost=0.00..17890.22 rows=281055 width=54) (actual time=29.720..112.161 rows=280981 loops=1)
Filter: ((created_date >= '2018-09-01'::date) AND (created_date <= '2019-09-30'::date) AND (tenant_id = '62850a62-19ac-477d-9cd7-837f3d716885'::uuid))
Rows Removed by Filter: 225546
Planning Time: 0.129 ms
Execution Time: 1786.222 ms
Planning Time: 0.103 ms
Execution Time: 1722.718 ms
我试过的
任何帮助将非常感激。
更新
将work_mem设置为 100MB后的查询计划
Custom Scan (cost=0.00..0.00 rows=0 width=0) (actual time=1375.926..1375.935 rows=100 loops=1)
Task Count: 1
Tasks Shown: All
-> Task
Node: host=localhost port=5454 dbname=postgres
-> Limit (cost=48348.85..48349.10 rows=100 width=42) (actual time=1307.072..1307.093 rows=100 loops=1)
-> Sort (cost=48348.85..48633.55 rows=113880 width=42) (actual time=1307.071..1307.080 rows=100 loops=1)
Sort Key: (sum(total)) DESC
Sort Method: top-N heapsort Memory: 35kB
-> GroupAggregate (cost=38285.79..43996.44 rows=113880 width=42) (actual time=941.504..1261.177 rows=172945 loops=1)
Group Key: search_keyword
-> Sort (cost=38285.79..38858.52 rows=229092 width=54) (actual time=941.484..963.061 rows=227261 loops=1)
Sort Key: search_keyword
Sort Method: quicksort Memory: 32982kB
-> Seq Scan on rollup_day_104290 a (cost=0.00..17890.22 rows=229092 width=54) (actual time=38.803..104.350 rows=227261 loops=1)
Filter: ((created_date >= '2019-01-01'::date) AND (created_date <= '2019-12-30'::date) AND (tenant_id = '62850a62-19ac-477d-9cd7-837f3d716885'::uuid))
Rows Removed by Filter: 279266
Planning Time: 0.131 ms
Execution Time: 1308.814 ms
Planning Time: 0.112 ms
Execution Time: 1375.961 ms
更新 2
在created_date创建索引并将work_mem 增加到 120MB 后
create index date_idx on rollup_day(created_date);
总行数为: 12,124,608
查询计划是:
Custom Scan (cost=0.00..0.00 rows=0 width=0) (actual time=2635.530..2635.540 rows=100 loops=1)
Task Count: 1
Tasks Shown: All
-> Task
Node: host=localhost port=9702 dbname=postgres
-> Limit (cost=73545.19..73545.44 rows=100 width=51) (actual time=2755.849..2755.873 rows=100 loops=1)
-> Sort (cost=73545.19..73911.25 rows=146424 width=51) (actual time=2755.847..2755.858 rows=100 loops=1)
Sort Key: (sum(total)) DESC
Sort Method: top-N heapsort Memory: 35kB
-> GroupAggregate (cost=59173.97..67948.97 rows=146424 width=51) (actual time=2014.260..2670.732 rows=296537 loops=1)
Group Key: search_keyword
-> Sort (cost=59173.97..60196.85 rows=409152 width=55) (actual time=2013.885..2064.775 rows=410618 loops=1)
Sort Key: search_keyword
Sort Method: quicksort Memory: 61381kB
-> Index Scan using date_idx_102913 on rollup_day_102913 a (cost=0.42..21036.35 rows=409152 width=55) (actual time=0.026..183.370 rows=410618 loops=1)
Index Cond: ((created_date >= '2018-01-01'::date) AND (created_date <= '2018-12-31'::date))
Filter: (tenant_id = '12850a62-19ac-477d-9cd7-837f3d716885'::uuid)
Planning Time: 0.135 ms
Execution Time: 2760.667 ms
Planning Time: 0.090 ms
Execution Time: 2635.568 ms
您应该尝试使用更高的work_mem
设置,直到获得内存中排序。 当然,只有当您的机器有足够的内存时,您才能对内存大方。
如果您使用物化视图或第二个表和原始表上的触发器来存储预先聚合的数据,那么将使您的查询更快的方法是,保持另一个表中的总和更新。 我不知道您的数据是否可行,因为我不知道hll_cardinality
和hll_union_agg
是什么。
您是否尝试过覆盖索引,因此优化器将使用索引,而不是进行顺序扫描?
create index covering on rollup_day(tenant_id, created_date, search_keyword, users, sessions, total);
如果 Postgres 11
create index covering on rollup_day(tenant_id, created_date) INCLUDE (search_keyword, users, sessions, total);
但是由于您还对search_keyword
进行了排序/分组, search_keyword
可能:
create index covering on rollup_day(tenant_id, created_date, search_keyword);
create index covering on rollup_day(tenant_id, search_keyword, created_date);
或者 :
create index covering on rollup_day(tenant_id, created_date, search_keyword) INCLUDE (users, sessions, total);
create index covering on rollup_day(tenant_id, search_keyword, created_date) INCLUDE (users, sessions, total);
这些索引之一应该使查询更快。 您应该只添加这些指标之一。
即使它使此查询更快,拥有大索引也会/可能会使您的写入操作变慢(尤其是索引列上的热更新不可用)。 您将使用更多存储空间。
work_mem
大小的提示使用表分区并创建一个复合索引,它将降低总成本:
我亲自尝试和测试过这种情况下的表分区,分区和复合索引的组合吞吐量非常惊人。
可以在创建日期范围内进行分区,然后在日期和租户上组合索引。
希望这可以帮助。
PS:另外,是否可以共享任何相同的测试样本数据?
我的建议是打破选择。 现在,我还将尝试结合此方法在表上设置 2 个索引。 一个在日期上,另一个在 ID 上。 奇怪 ID 的问题之一是,比较需要时间,并且可以在后台将它们视为字符串比较。 这就是为什么要在执行 between 命令之前拆分数据以对数据进行预过滤。 现在 between 命令可以使选择变慢。 在这里,我建议将其分解为 2 个选择和内部连接(我现在内存消耗是一个问题)。
这是我的意思的一个例子。 我希望优化器足够聪明,可以重构您的查询。
SELECT
a.search_keyword,
hll_cardinality( hll_union_agg(a.users) ):: int as user_count,
hll_cardinality( hll_union_agg(a.sessions) ):: int as session_count,
sum(a.total) as keyword_count
FROM
(SELECT
*
FROM
rollup_day a
WHERE
a.tenant_id = '62850a62-19ac-477d-9cd7-837f3d716885') t1
WHERE
a.created_date between '2018-09-01' and '2019-09-30'
group by
a.search_keyword
order by
session_count desc
现在,如果这不起作用,那么您需要更具体的优化。 例如。 总数是否可以等于 0,那么您需要对总数 > 0 的数据进行过滤索引。是否有任何其他条件可以轻松地从选择中排除行。
下一个考虑是创建一行,其中有一个短 ID(而不是 62850a62-19ac-477d-9cd7-837f3d716885 -> 62850),它可以是一个数字,这将使预选变得非常容易并且内存消耗更少。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.