Улучшение PostgresSQL производительности запросов агрегации - PullRequest
6 голосов
/ 20 января 2020

Я собираю данные из таблицы 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 и create_date

enter image description here


План запроса

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 и create_date , но поскольку данные огромны, они всегда выполняют сканирование последовательностей, а не сканирование индексов для фильтров. Я прочитал об этом и обнаружил, что механизм запросов Postgres переключается на сканирование последовательности, если возвращаемые данные составляют> 5-10% от общего числа строк. Пожалуйста, перейдите по ссылке для получения дополнительной ссылки .
  2. Я увеличил work_mem до 100MB , но это лишь немного улучшило производительность.

Буду признателен за любую помощь.


Обновление

План запроса после установки work_mem в 100 МБ

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

После создания индекса для create_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

Ответы [ 4 ]

5 голосов
/ 20 января 2020

Вы должны экспериментировать с более высокими настройками work_mem, пока не получите сортировку в памяти. Конечно, вы можете щедро использовать память только в том случае, если на вашем компьютере ее достаточно.

Что ускорит ваш запрос, если вы сохраните предварительно агрегированные данные, используя материализованное представление или вторую таблицу и триггер на исходной таблице, который обновляет суммы в другой таблице. Я не знаю, возможно ли это с вашими данными, так как я не знаю, что такое hll_cardinality и hll_union_agg.

2 голосов
/ 01 февраля 2020

Вы пробовали Покрытие индексов , чтобы оптимизатор использовал индекс, а не выполнял последовательное сканирование?

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, возможно:

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

Один из этих индексов должен ускорить запрос. Вы должны добавить только один из этих индексов.

Даже если это ускоряет этот запрос, наличие больших индексов будет / может замедлить ваши операции записи (особенно обновления HOT недоступны для индексированных столбцов ). И вы будете использовать больше памяти.

1 голос
/ 03 февраля 2020

используйте разделы таблицы и создайте составной индекс, который снизит общую стоимость как:

  • это сэкономит вам огромные затраты на сканирование.
  • разделы будут разделять данные и будет очень полезен в будущих операциях очистки.
  • Я лично пробовал и тестировал разделы таблиц в таких случаях, и пропускная способность поразительна благодаря комбинации разделов и составных индексов.

  • Секционирование может быть выполнено в диапазоне созданной даты, а затем составных индексов по дате и арендатору.

  • Помните, что у вас всегда может быть составной индекс с условием, если в вашем запросе очень строго определено требование c. Таким образом, данные будут отсортированы уже в индексе, что также сэкономит огромные затраты на операции сортировки.

Надеюсь, это поможет.

PS: Также есть возможность поделиться какие-либо данные тестового образца для того же?

0 голосов
/ 02 февраля 2020

Мое предложение было бы разбить выбор. Теперь, что я хотел бы попробовать в сочетании с этим, чтобы настроить 2 индекса на столе. Один на Даты другой на удостоверение личности. Одна из проблем со странными идентификаторами заключается в том, что для сравнения требуется время, и их можно рассматривать как сравнение строк в фоновом режиме. Вот почему разбить, чтобы предварительно фильтровать данные перед выполнением команды между. Теперь команда Между может сделать медленный выбор. Здесь я хотел бы предложить разбить его на 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

Теперь, если это не сработает, вам нужно больше уточнений c. Например. Если итоговое значение может быть равно 0, тогда вам понадобится отфильтрованный индекс для данных, где итоговое значение> 0. Существуют ли какие-либо другие критерии, облегчающие исключение строк из выбора.

Следующее рассмотрение будет создать строку с коротким идентификатором (вместо 62850a62-19a c -477d-9cd7-837f3d716885 -> 62850), который может быть числом и который сделает предварительный выбор очень простым и потребляет меньше памяти.

...