Опирается на правила:
PG-CP-001…PG-CP-086из PostgreSQL Style Guide → раздел HikariCP и connection pooling.
Важно знать
- PostgreSQL = процесс ОС на каждое соединение (~10MB + context switching).
- Формула Wooldridge:
((core_count × 2) + spindles). Для SSD =2N+1≈ 10-20.- «Больше = больше throughput» — миф. Context-switch + lock contention растут квадратично.
max_connectionsPG (default 100) — бюджет на все инстансы всех сервисов.- HikariCP:
max = min-idle(не ramp-up),connection-timeout: 3s,max-lifetime: 30m.leak-detection-threshold: 60s— бесплатный мониторинг утечек.- PgBouncer — для много инстансов / много сервисов / serverless.
- PgBouncer transaction mode оптимален, но:
prepareThreshold: 0,SET LOCAL, нетLISTEN/NOTIFY.- Read-replica — отдельный DataSource + HikariCP, routing через
readOnly = true.
PostgreSQL держит один процесс ОС на каждое соединение. UCP формулирует — какой размер пула, как мониторить, когда добавить PgBouncer.
Формула Wooldridge
PG-CP-001..003:
connections = ((core_count × 2) + effective_spindle_count)
Для SSD-серверов с N CPU-ядер оптимум 2N+1 ≈ 10-20.
«Больше соединений = больше throughput» — миф:
- Context-switch между процессами PG имеет цену.
- Lock contention растёт квадратично.
- При пуле 20 на 8-core throughput может быть выше, чем при 100.
Целевой размер: 10-20 per instance.
max_connections PG (default 100) распределяется между всеми инстансами всех сервисов. На 10 инстансов × 20 соединений = 200, уже превышает default. Решение — увеличить max_connections (до 300-500 разумно) или PgBouncer.
HikariCP
PG-CP-010..015:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 20 # = maximum — нет ramp-up
connection-timeout: 3000 # 3s ждать соединение
idle-timeout: 600000 # 10 мин
max-lifetime: 1800000 # 30 мин — обновлять соединения
leak-detection-threshold: 60000 # 60 сек — алёрт на утечку
validation-timeout: 5000
auto-commit: false
maximum = minimum-idle
Пул всегда полный. Динамика «то 5, то 20» создаёт latency-всплески при ramp-up на пиках.
connection-timeout 3 сек
Лучше упасть, чем держать поток. На веб-фронте таймаут запроса 30 сек, но 3 сек ожидания пула — индикатор перегрузки, нужна реакция.
max-lifetime 30 мин
Каждое соединение через 30 мин пересоздаётся. Защита от:
- Утечек памяти PG (накопленный
work_mem, prepared statements). - «Тихой» поломки соединений за load balancer-ами.
Должен быть меньше server-side idle_in_transaction_session_timeout и LB-таймаутов.
leak-detection-threshold 60 сек
HikariCP логирует stack trace, если соединение не вернулось в пул за минуту. Признак:
- Забытый
close(). @Transactionalвокруг долгого HTTP-вызова.- Висящая транзакция.
Не отключай! Бесплатный мониторинг.
auto-commit
PG-CP-020..021:
auto-commit: false в Spring Boot — обычно правильно. Spring управляет TX через @Transactional.
auto-commit: true требует доп. round-trip к БД на каждый borrow/return — Spring ставит autocommit в false перед @Transactional и обратно после. На high-throughput заметная latency.
Diagnostic
PG-CP-030..033:
management:
metrics:
enable:
hikaricp: true
Метрики:
hikaricp.connections.active— занято.hikaricp.connections.idle— свободно.hikaricp.connections.pending— потоки в очереди.> 0стабильно — пул мал.hikaricp.connections.usage— гистограмма «сколько секунд держали». p99 > секунды — долгие TX.hikaricp.connections.timeout— счётчикconnection-timeout.
Алёрты:
pending > 0минуту → увеличить пул или сократить TX.timeout> 0 → утечка или недостаток пула.
PgBouncer
PG-CP-040..047:
Оправдан когда:
- Много инстансов одного сервиса (десятки).
- Много разных сервисов на один кластер.
- Serverless / lambda — частые коротко-живущие воркеры.
- Нужно ограничить общее число соединений к PG.
Режимы pooling
| Режим | Когда возвращается | Что не работает |
|---|---|---|
session (default) | После disconnect клиента | Никаких ограничений |
transaction | После каждого COMMIT/ROLLBACK | Server-side prepared, SET без LOCAL, advisory locks, LISTEN/NOTIFY |
statement | После каждого SQL | Транзакции из >1 statement |
transaction — обычный выбор. Максимальная утилизация при минимуме ограничений.
Ограничения transaction-mode
На transaction mode не работают server-side prepared statements:
spring:
datasource:
hikari:
data-source-properties:
prepareThreshold: 0 # отключить server-side prepared
Или PgBouncer 1.21+ умеет prepared-statements caching сам.
SET команды (без LOCAL) теряются после COMMIT. Используй SET LOCAL.
LISTEN/NOTIFY — connection не сохраняется между транзакциями. Отдельный пул в session mode или другой механизм (Kafka, Redis pub/sub).
Размер пулов с PgBouncer
PG-CP-050:
app_pool : pgbouncer_to_pg = 5:1 или больше
Например, HikariCP пул 50 per instance, PgBouncer держит к PG 20 real-соединений. 50 потоков могут одновременно «думать», к PG идёт максимум 20.
[databases]
mydb = host=postgres-master port=5432 dbname=mydb pool_mode=transaction
[pgbouncer]
pool_mode = transaction
default_pool_size = 20
max_client_conn = 1000
reserve_pool_size = 5
Read-replica
PG-CP-060..061: отдельный пул.
@Configuration
public class DataSourceConfig {
@Bean @Primary
@ConfigurationProperties("spring.datasource.master")
public HikariConfig masterConfig() { return new HikariConfig(); }
@Bean
@ConfigurationProperties("spring.datasource.replica")
public HikariConfig replicaConfig() { return new HikariConfig(); }
@Bean
public DataSource routingDataSource(DataSource master, DataSource replica) {
var routing = new TransactionRoutingDataSource();
routing.setTargetDataSources(Map.of(
DataSourceType.READ_WRITE, master,
DataSourceType.READ_ONLY, replica
));
routing.setDefaultTargetDataSource(master);
return routing;
}
}
AbstractRoutingDataSource выбирает по TransactionSynchronizationManager.isCurrentTransactionReadOnly() — @Transactional(readOnly = true) идёт на реплику.
Replication lag — обычно ms, под нагрузкой может расти до секунд. Не для read-after-write сценариев («только что создал, сразу читаю»).
Чек-лист
- [ ]
maximum-pool-size = minimum-idle = 10-20. - [ ]
connection-timeout = 3s. - [ ]
max-lifetime = 30 мин(меньше LB-таймаута). - [ ]
leak-detection-threshold = 60sвключён. - [ ] HikariCP-метрики в Micrometer.
- [ ] Алёрт на
connections.pending > 0минуту. - [ ] Алёрт на
connections.timeout > 0. - [ ]
max_connectionsPG ≥ суммы всех пулов. - [ ] PgBouncer:
transactionmode (или явное обоснованиеsession). - [ ] PgBouncer + JDBC:
prepareThreshold = 0или PG 1.21+. - [ ] Read-replica: отдельный пул + routing через
readOnly = true.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
maximum-pool-size: 200 | PG-CP-080 | формула Wooldridge |
| Разные пулы на одну БД для одного app | PG-CP-081 | один DataSource |
@Transactional вокруг HTTP | PG-CP-082 | вне TX |
Отключение leak-detection | PG-CP-083 | не скрывать |
auto-commit: true с @Transactional | PG-CP-084 | false |
PgBouncer session без причины | PG-CP-085 | transaction |
PgBouncer transaction + server-side prepared | PG-CP-086 | prepareThreshold: 0 |
| Read-replica для read-after-write | PG-CP-061 | master для consistency |
Куда дальше
- PG → HikariCP — нормативные формулировки.
- Spring @Transactional —
REQUIRES_NEWудваивает пул. - Уровни изоляции —
readOnlyrouting. - Блокировки —
lock_timeout. - WAL — long transactions.
- Observability → metrics — HikariCP экспорт.