Оптимизация запросов и пул соединений (PgBouncer)

↑ 2.1 PostgreSQL: индексы, транзакции, оптимизация · ← Предыдущая · → Следующая

Обязательно~4 мин чтенияУровень: middle

Зачем это на собесе

Практический навык: методичный поиск узкого места и пул соединений под нагрузкой.

Процесс оптимизации

  1. Найти дорогие запросы: pg_stat_statements (по total_exec_time, mean_exec_time, calls), логи (log_min_duration_statement), APM.
  2. Проанализировать EXPLAIN (ANALYZE, BUFFERS).
  3. Гипотеза: индекс, переписывание запроса, статистика, схема.
  4. Изменить и измерить повторно на реалистичных данных.
  5. Зафиксировать (миграция индекса, тест, мониторинг).
SELECT calls, round(total_exec_time::numeric, 0) AS total_ms, round(mean_exec_time::numeric, 2) AS mean_ms,
       rows, left(query, 80) AS q
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;

Типичные приёмы

ПроблемаРешение
Seq Scan по большой таблицеподходящий индекс (составной, частичный)
N+1 запросов из приложенияJOIN / WHERE id = ANY($1) / пакетная загрузка
Большой OFFSETkeyset-пагинация
SELECT *нужные столбцы, covering-индекс
count(*) по большой таблицеоценка (reltuples), кэш счётчиков, приблизительные значения
OR по разным столбцамUNION ALL, отдельные индексы
Функция в условиифункциональный индекс или переписать
Коррелированный подзапросJOIN / LATERAL / окно
Сортировка на дискеиндекс под ORDER BY, work_mem
Медленная вставкапакетная вставка, COPY, отключить лишние индексы на время загрузки
Тяжёлый отчётmaterialized view, реплика, ClickHouse

Настройки под нагрузку

shared_buffers, work_mem (осторожно), effective_cache_size, random_page_cost (для SSD ~1.1), max_parallel_workers_per_gather, jit (для OLTP часто выключают), default_statistics_target.

Пул соединений

Проблема: процесс на соединение → сотни клиентов превращаются в перегрузку по памяти и переключению контекста. Оптимально держать серверных соединений порядка cores × 2..4.

PgBouncer — лёгкий пулер.

РежимСоединение возвращается в пулОсобенности
sessionпосле закрытия клиентской сессиисовместим со всем, меньше выигрыш
transactionпосле завершения транзакциимаксимальное мультиплексирование
statementпосле каждого операторанет многооператорных транзакций

Ограничения transaction-режима: нельзя полагаться на состояние сессии (SET, LISTEN/NOTIFY, advisory-lock на сессию, временные таблицы, prepared statements — есть поддержка в новых версиях max_prepared_statements).

[databases]
app = host=pg1 port=5432 dbname=app
 
[pgbouncer]
pool_mode = transaction
default_pool_size = 20
max_client_conn = 2000
server_idle_timeout = 600
auth_type = scram-sha-256

Также: Pgpool-II (балансировка, кэш), Odyssey, Supavisor. Пул на стороне приложения (Npgsql, HikariCP) тоже нужен: он уменьшает накладные на установку соединений; но при множестве экземпляров сервиса суммарное число соединений требует внешнего пулера.

В .NET

Npgsql: строка подключения с Maximum Pool Size, Minimum Pool Size, Connection Idle Lifetime; при PgBouncer transaction mode — No Reset On Close=true и осторожность с prepared statements (Max Auto Prepare).

Диагностика соединений

SELECT state, count(*) FROM pg_stat_activity GROUP BY 1;
SHOW max_connections;

Много idle in transaction — утечка или долгие транзакции в приложении.

Вопросы с ответами