繁体   English   中英

提高 PostgresSQL 聚合查询性能

[英]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;

表元数据

  1. 总行数 - 506527
  2. 列上的复合索引: tenant_id 和 created_date

在此处输入图片说明


查询计划

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

我试过的

  1. 我已经尝试过对tenant_id 和created_date使用索引,但是由于数据很大,所以它总是进行序列扫描而不是过滤器的索引扫描。 我读过它并发现,如果返回的数据大于总行数的5-10% ,Postgres 查询引擎会切换到序列扫描。 请点击链接以获取更多参考
  2. 我已经将work_mem增加到100MB,但它只是稍微提高了性能。

任何帮助将非常感激。


更新

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_cardinalityhll_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);

这些索引之一应该使查询更快。 您应该只添加这些指标之一

即使它使此查询更快,拥有大索引也会/可能会使您的写入操作变慢(尤其是索引列上的热更新不可用)。 您将使用更多存储空间。

使用表分区并创建一个复合索引,它将降低总成本:

  • 它将为您节省大量的扫描成本。
  • 分区将隔离数据,并且在未来的清除操作中也非常有帮助。
  • 我亲自尝试和测试过这种情况下的表分区,分区和复合索引的组合吞吐量非常惊人。

  • 可以在创建日期范围内进行分区,然后在日期和租户上组合索引。

  • 请记住,如果对查询中的条件有非常具体的要求,则始终可以拥有一个包含条件的复合索引。 这样,数据将已经在索引中排序,并且也将节省大量的排序操作成本。

希望这可以帮助。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM