Настройка Connection Pooling (PgBouncer/ProxySQL) для веб-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка Connection Pooling (PgBouncer/ProxySQL) для веб-приложения
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Настройка Connection Pooling (PgBouncer/ProxySQL) для веб-приложения

Без пулинга соединений каждый HTTP-запрос к PostgreSQL или MySQL открывает отдельное TCP-соединение. Установка соединения занимает 5–50 мс и требует выделения ~5–10 MB памяти на стороне базы. При 500 одновременных запросах это 500 соединений, каждое из которых держит фоновый процесс в PostgreSQL. Решение — connection pooler перед базой.

Когда это нужно

Симптомы проблемы с соединениями: max_connections в PostgreSQL достигает лимита (по умолчанию 100), приложение получает ошибку FATAL: remaining connection slots are reserved, время ответа базы растёт нелинейно при нагрузке. Для MySQL/MariaDB аналогично — Too many connections.

Пулинг нужен при пиковом числе воркеров приложения > 50 и при использовании PHP-FPM, Gunicorn, Unicorn — то есть там, где каждый процесс держит своё соединение.

PgBouncer для PostgreSQL

PgBouncer — легковесный прокси (один процесс, ~2 MB RAM) с тремя режимами пулинга.

Session mode — одно соединение с БД на сессию клиента. Почти нет выигрыша, но полная совместимость с prepared statements и SET.

Transaction mode — соединение занимается только на время транзакции. Самый распространённый вариант. Несовместим с SET вне транзакций и LISTEN/NOTIFY.

Statement mode — соединение возвращается после каждого запроса. Несовместим с транзакциями, применяется редко.

Установка на Ubuntu:

apt install pgbouncer

Конфигурация /etc/pgbouncer/pgbouncer.ini:

[databases]
myapp = host=127.0.0.1 port=5432 dbname=myapp

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 600
client_idle_timeout = 0
log_connections = 0
log_disconnections = 0

Файл /etc/pgbouncer/userlist.txt — хранит MD5-хэши паролей:

# Генерация хэша: echo -n "passwordusername" | md5sum
echo -n "mysecretmyapp" | md5sum
# Результат вставляем в userlist.txt:
"myapp" "md5<hash>"

При default_pool_size = 20 PgBouncer держит максимум 20 реальных соединений к PostgreSQL, при этом принимая до 1000 клиентских. Подбирать default_pool_size нужно под конкретную нагрузку — формула: (количество ядер CPU сервера БД) * 2 + количество дисков.

Проверка статуса через psql:

psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer
SHOW POOLS;
SHOW STATS;
SHOW CLIENTS;
SHOW SERVERS;

Подключение приложения через PgBouncer

В Laravel .env:

DB_HOST=127.0.0.1
DB_PORT=6432
DB_DATABASE=myapp
DB_USERNAME=myapp
DB_PASSWORD=mysecret

Важный момент для Laravel в transaction mode: отключить prepared statements. В config/database.php:

'pgsql' => [
    'driver' => 'pgsql',
    'options' => [
        PDO::ATTR_EMULATE_PREPARES => true,
    ],
],

Для Django — аналогично, использовать CONN_MAX_AGE = 0 при transaction pooling, чтобы Django не пытался держать персистентные соединения, которые конфликтуют с пулером.

ProxySQL для MySQL/MariaDB

ProxySQL — значительно мощнее PgBouncer: умеет роутинг запросов, read/write split, автоматическое переключение при падении мастера.

Установка:

# Ubuntu/Debian
wget https://github.com/sysown/proxysql/releases/download/v2.6.3/proxysql_2.6.3-ubuntu22_amd64.deb
dpkg -i proxysql_2.6.3-ubuntu22_amd64.deb
systemctl start proxysql

Конфигурация через admin-интерфейс (порт 6032):

mysql -h 127.0.0.1 -P 6032 -u admin -padmin

Добавление MySQL серверов:

INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (0, '127.0.0.1', 3306);
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (1, '192.168.1.10', 3306); -- replica

LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;

Настройка пользователя:

INSERT INTO mysql_users(username, password, default_hostgroup)
VALUES ('myapp', 'mysecret', 0);

LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;

Read/write split через query rules:

INSERT INTO mysql_query_rules(rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, '^SELECT.*FOR UPDATE', 0, 1);

INSERT INTO mysql_query_rules(rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (2, 1, '^SELECT', 1, 1);

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;

Все SELECT (кроме SELECT ... FOR UPDATE) пойдут на реплику в hostgroup 1, всё остальное — на мастер в hostgroup 0.

Размер пула соединений в ProxySQL задаётся через mysql_variables:

SET mysql-max_connections = 2048;
SET mysql-max_transaction_idle_time = 5000;
SET mysql-connection_max_age_ms = 0;
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;

Мониторинг

Для PgBouncer — метрики через pgbouncer_exporter для Prometheus:

docker run -d \
  -e DATA_SOURCE_NAME="postgresql://pgbouncer:[email protected]:6432/pgbouncer" \
  -p 9127:9127 \
  prometheuscommunity/pgbouncer-exporter

Ключевые метрики: pgbouncer_pools_cl_waiting (клиенты в очереди — должно быть 0), pgbouncer_pools_sv_idle (свободные серверные соединения).

Для ProxySQL — встроенный stats schema:

SELECT * FROM stats.stats_mysql_connection_pool;
SELECT * FROM stats.stats_mysql_query_digest ORDER BY sum_time DESC LIMIT 10;

Типичные проблемы

Prepared statements в transaction mode — наиболее частая проблема с PgBouncer. Приложение создаёт prepared statement в одном соединении, а выполняет в другом. Решение: эмулировать prepared statements на уровне драйвера (PDO EMULATE_PREPARES) или перейти в session mode.

pg_temp схемы и временные таблицы — не работают в transaction mode, так как привязаны к сессии. Использовать CTE или постоянные таблицы с уникальными именами.

Длинные транзакции — при transaction pooling соединение занято всё время транзакции. Длинные транзакции (>30 секунд) уменьшают эффективность пула так же, как и без пулинга.

search_path и SET команды — в transaction mode сбрасываются после транзакции. Если приложение устанавливает search_path через SET, нужно использовать server_reset_query в PgBouncer или передавать схему явно в URL подключения.

Сроки

Базовая настройка PgBouncer с тестированием — 1 рабочий день. ProxySQL с read/write split и настройкой мониторинга — 2–3 дня. Если нужна интеграция с существующим приложением и устранение проблем с prepared statements — плюс 1 день на отладку.