HikariCP — самый быстрый пул подключений JDBC. Это факт, подтверждённый бенчмарками. Spring Boot использует его по умолчанию, Exposed тоже. На первый взгляд — добавил зависимость, прописал URL базы данных, и всё работает. Но в продакшене «работает» и «работает хорошо» — это разные вещи.
Рассказываю по боли. У нас payment-service начал тормозить под нагрузкой: p99 latency подскочил с 200мс до 4 секунд. Мониторинг показывал, что потоки сервиса ждут подключения из пула. Мы увеличили пул с 10 до 50 — стало хуже. Уменьшили до 5 — тоже хуже. Оказалось, проблема была вообще не в размере пула.
Дефолтные настройки — ловушка
Spring Boot устанавливает maximumPoolSize = 10 по умолчанию. Звучит разумно. Но давайте посчитаем:
10 подключений, 4 потока Tomcat (дефолт) — 40 конкурентных запросов могут обрабатываться одновременно. Но если каждый запрос делает 2–3 запроса в БД, то реально параллельно обслуживаются только 3–5 запросов. Остальные ждут в очереди за подключением.
Формула, которой мы пользуемся сейчас:
optimal_pool_size = ((core_count * 2) + effective_spindle_count)
# Для нашего пода в K8s (2 CPU, SSD):
# optimal = (2 * 2) + 1 = 5
# Но с запасом на пики: 10-15Ключевое понимание: размер пула должен быть примерно равен количеству ядер CPU, умноженному на 2. Больше — не значит лучше. Больше подключений в пуле = больше контекста переключений в PostgreSQL = медленнее каждый запрос.
Наши production-настройки
Вот реальный application.yml, к которому мы пришли после месяцев экспериментов:
spring:
datasource:
url: jdbc:postgresql://pg-primary:5432/payment_db?sslmode=require&tcpKeepAlive=true
username: ${DB_USER}
password: ${DB_PASSWORD}
hikari:
pool-name: payment-hikari
maximum-pool-size: 15
minimum-idle: 5
idle-timeout: 300000 # 5 минут
max-lifetime: 600000 # 10 минут
connection-timeout: 3000 # 3 секунды! Не дефолтные 30
leak-detection-threshold: 60000 # 60 секунд
validation-timeout: 1000 # 1 секунда
jpa:
properties:
hibernate:
connection.provider_disables_autocommit: trueОбратите внимание на connection-timeout: 3000. Дефолт — 30 секунд. 30 секунд ожидания подключения в микросервисе — это вечность. За это время клиент уйдёт по таймауту, балансировщик переключит трафик, появится повторный запрос — и всё это нагнетает нагрузку. 3 секунды — порог, после которого лучше сразу вернуть ошибку и позволить клиенту ретраить.
Leak Detection: друг, который вас бесит
HikariCP умеет детектировать утечки подключений: если подключение взято из пула и не возвращено за N миллисекунд — в лог падает warning с полным stacktrace. Звучит полезно. На практике — мы получили ложные срабатывания на долгих транзакциях.
У нас есть операция «выставить счёт», которая занимает 8–15 секунд: она делает несколько запросов, вызывает внешнее API платёжной системы, обновляет баланс. Это легитимная долгая транзакция, а не утечка.
@Component
class HikariMetricsConfig(
private val meterRegistry: MeterRegistry
) {
@PostConstruct
fun bindHikariMetrics() {
// HikariCP регистрирует метрики автоматически через Micrometer:
// hikaricp.connections.active — в использовании
// hikaricp.connections.idle — свободные
// hikaricp.connections.pending — ждут подключения
// hikaricp.connections.max — лимит пула
// hikaricp.connections.timeout — количество таймаутов
}
}
// Алерт в Grafana: если pending > 0 более 10 секунд — проблема
// Алерт: если timeout rate > 1/мин — пул слишком маленькийKubernetes и networking: тихий убийца
Самая неожиданная проблема — Kubernetes networking. Когда под перезапускается, Kubernetes убивает TCP-соединения, но HikariCP об этом не знает. Поднимается с кучей мёртвых подключений в пуле. Первый запрос после рестарта — exception: connection is closed.
Решение — включить TCP keepalive и правильно настроить max-lifetime:
# PostgreSQL сторона (postgresql.conf):
tcp_keepalives_idle = 60
tcp_keepalives_interval = 10
tcp_keepalives_count = 6
idle_in_transaction_session_timeout = 600000 # 10 мин
# HikariCP сторона:
# max-lifetime ДОЛЖЕН быть меньше idle_in_transaction_session_timeout
hikari:
max-lifetime: 540000 # 9 минут (на минуту меньше PG таймаута)
keepalive-time: 120000 # 2 минутыТакже критически важно: max-lifetime должен быть случайным в пределах 10%, чтобы все подключения не переустанавливались одновременно. HikariCP делает это автоматически.
Мониторинг: что смотреть в Grafana
Вот метрики, которые спасали нам жизнь:
// 1. hikaricp_connections_active — сколько подключений сейчас работает
// 2. hikaricp_connections_idle — сколько свободно в пуле
// 3. hikaricp_connections_pending — сколько потоков ждут подключение
// 4. hikaricp_connections_max — лимит пула
// 5. hikaricp_connections_timeout_total — счётчик таймаутов
// 6. hikaricp_connections_creation_seconds — время создания подключения
// Алерты:
// 1. connections.pending > 0 в течение 30 сек → WARN
// 2. connections.timeout_total rate > 0.5/min → CRITICAL
// 3. connections.active / max > 0.8 → WARN
// 4. connections.creation_seconds p99 > 2s → WARNИтог
HikariCP — отличный пул, но дефолтные настройки опасны для продакшена. Три вещи, которые нужно сделать обязательно: уменьшить connection-timeout до 2–3 секунд, настроить max-lifetime меньше таймаутов PostgreSQL, и вынести метрики пула в мониторинг с алертами. И помните: bigger pool is not better pool.
Пока нет комментариев
Будьте первым, кто оставит комментарий к этой статье