Часть 1.
Часть 2.
Часть 3.
Заключительная часть серии статей. Сегодня отвечу на вопрос, заданный в 1 части: с каким индексом на stage_table такой запрос отработает быстрее всего:
select resource, billing_date, sum(cost)
from stage_table
group by resource, billing_date
Ответ
Индекс неважен и результаты одинаковые, так как планировщик всегда выбирает HashAggregate с Seq Scan (диаграмма 1), потому что любой другой алгоритм агрегирования окажется медленнее.
Объяснение
Убедится в том, что Postgres неспроста использует HashAggregate, можно на примере, если добавить к таблице индекс и принудительно отключить HashAggregate с помощью опции enable_hashagg:
create index idx_billing_data
on billing_data (resource, billing_date)
include (cost);
set enable_hashagg = off;
С включённым HashAggregate видим знакомую картину (диаграмма 2): HashAggregate с Seq Scan для относительно небольших таблиц и Parallel HashAggregate с Seq Scan для крупных таблиц, даже несмотря на наличие индекса.
С отключённым HashAggregate Postgres выбирает GroupAggregate с Index Only Scan или Group Aggregate с Seq Scan, в зависимости от размера таблицы. Наконец-таки индекс используется, но общая производительность всё равно хуже, чем HashAggregate.
Причины следующие:
1. HashAggregate выполняет один последовательный проход по таблице и считает сумму во внутренней хеш‑таблице. Это минимизирует непоследовательный доступ, и данные попадают в кэш CPU. Для нашей задачи – сгруппировать всё содержимое таблицы – это идельный сценарий.
2. Index Only Scan медленнее из-за непоследовательного доступа. Поскольку индекс по умолчанию – это B-Tree, его чтение сопряжено с чтением данных из разных частей памяти. Индексы хороши, когда нужно получить небольшой кусочек таблицы, но не когда нужно обработать всё сразу.
3. GroupAggregate с Seq Scan требует отсортированного входа по ключам, используемым в GROUP BY. Поскольку Seq Scan, который в нашем случае ещё и выполняется параллельно, не гарантирует порядок данных, то планировщик добавляет в план сортировку. Очевидно, это ухудшает время выполнения.
Выводы
Если нужно быстро загрузить большой объём данных в Postgres с использованием промежуточных таблиц, то:
1. Не используйте индексы в промежуточных таблицах, потому что
- вы потратите время на создание индекса;
- индексы замедляют вставку в таблицу;
- индексы не дают преимуществ, если требуется агрегировать данные по всей промежуточной таблице;
2. Используйте UNLOGGED таблицы, вместо TEMP таблиц. Оба типа не используют Write-Ahead Log, но UNLOGGED позволяет распараллеливать запросы SELECT, что может значительно ускорить выполнения запросов. Главное не забыть удалить UNLOGGED таблицу в конце транзакции. Они не удаляются автоматически, как TEMP таблицы.
Важно помнить, что всё вышесказанное верно для рассматриваемого сценария: массовая вставка с последующей агрегацией по всей промежуточной таблице. Для CRUD функционала лучше использовать обычные таблицы и добавлять индексы.

