惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园_首页
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
B
Blog RSS Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
Engineering at Meta
Engineering at Meta
量子位
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tailwind CSS Blog
Stack Overflow Blog
Stack Overflow Blog
N
Netflix TechBlog - Medium
T
The Blog of Author Tim Ferriss
U
Unit 42
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
博客园 - 【当耐特】
云风的 BLOG
云风的 BLOG
博客园 - Franky
博客园 - 聂微东

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Оптимизируем JDBC connection pool HikariCP. Прод, ресурсы...
rurikovich · 2026-05-05 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение7 мин

Охват и читатели13K

Туториал

Привет, Хабр!

Это продолжение первой части: там были основы HikariCP, версии, примеры настройки на JVM-языках, Spring Boot и разбор главных параметров пула. Здесь разберём, как считать размер пула, что учитывать в Kubernetes и при нескольких сервисах, как улучшить «типовой» конфиг из начала серии, типовые ошибки, мини-чеклист для прода и ссылки.

Стартуем сразу с п.7, Погнали !

7. Как выбрать размер пула

Известная формула из PostgreSQL wiki и HikariCP wiki

Есть известная формула из PostgreSQL wiki и HikariCP wiki:

connections = (core_count * 2) + effective_spindle_count

core_count - ядра сервера базы, не приложения. Hyper-threading лучше не считать как полноценные ядра.

effective_spindle_count - грубая поправка на дисковую подсистему. Для современных SSD/NVMe часто берут 0 или 1 как стартовое приближение.

Пример:

PostgreSQL: 8 физических ядер, SSD
pool target ~= (8 * 2) + 1 = 17 активных соединений

Звучит мало. Особенно если у вас 500 RPS.

Но соединения к базе это не пользователи и не HTTP-запросы. Если запрос к базе занимает 20 мс, то 10 соединений теоретически могут прокачать сотни операций в секунду. А если запрос занимает 2 секунды, пул на 100 соединений не спасёт. Он просто даст ста людям одновременно страдать внутри базы.

Есть ещё один полезный способ думать: Little's Law

Есть ещё один полезный способ думать: Little’s Law.

нужные соединения ~= RPS * среднее время работы с БД

Если сервис делает 300 DB-операций в секунду, а среднее время удержания соединения 30 мс:

300 * 0.03 = 9 соединений

Добавили запас, проверили p95/p99, прогнали нагрузочный тест. Получили стартовую настройку.

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

  • заняты почти все соединения в пуле;

  • потоки часто ждут свободное соединение;

  • растёт количество ошибок по connection timeout;

  • увеличивается время получения соединения из пула;

  • при этом сама база не перегружена по CPU, IO и блокировкам.

Если pending растёт, а база уже перегружена, увеличение пула сделает хуже. Это классика: приложение начинает давить сильнее ровно в тот момент, когда базе нужна передышка.


8. Несколько приложений и Kubernetes

Размер пула надо считать не только на один процесс, а на всю систему

Один из самых частых продовых сюрпризов:

maximumPoolSize = 32
replicas = 12
итого потенциально 384 соединения к базе

А ещё есть миграции, админки, BI, cron jobs, ручные подключения, read-only сервисы. Потом кто-то удивляется, почему PostgreSQL внезапно выдает too many connections.

Размер пула надо считать не только на один процесс, а на всю систему:

db_connection_budget = 160
service_replicas = 8
pool_per_replica = 160 / 8 = 20
Если используйте HPA (Horizontal Pod Autoscaler), например PgBouncer

Если есть HPA (Horizontal Pod Autoscaler), берите максимальное количество pod’ов, а не комфортное среднее в обеденное время.

И да, иногда правильный ответ не “увеличить HikariCP”, а поставить PgBouncer или другой внешний pooler. Особенно когда много приложений, много коротких запросов и PostgreSQL страдает от числа backend-процессов.

PgBouncer это лёгкий внешний пулер соединений для PostgreSQL. Он стоит между приложениями и базой, принимает много клиентских подключений и переиспользует меньшее число реальных соединений к PostgreSQL.

Проще говоря, HikariCP управляет соединениями внутри одного JVM-приложения, а PgBouncer помогает ограничить общее количество соединений к базе на уровне инфраструктуры.


9. Что бы я поменял в настройке из первой части статьи

Стартовая конфигурация была такая:

Фрагмент стартовой конфигурации
val config = new HikariConfig()
config.setJdbcUrl(url)
config.setUsername(user)
config.setPassword(password)
config.setMaximumPoolSize(connectionPoolMaxSize)
config.setLeakDetectionThreshold(1000)
config.setAutoCommit(true)
sslRootCert.foreach(config.addDataSourceProperty("sslrootcert", _))

new HikariDataSource(config)

И maxSize = 32.

Я бы сделал несколько вещей.

Обновил версию HikariCP

Если проект всё ещё на HikariCP 3.4.5, это 2020 год. Для JVM-проекта в 2026 это уже техдолг.

Но обновление зависит от Java:

  • Java 11+ - смотреть HikariCP 7.x или актуальную поддерживаемую линию фреймворка.

  • Java 8 - не прыгать на 7.x, а сначала разобраться с совместимой веткой и планом миграции JVM.

  • Java 21 + virtual threads - обязательно нагрузочное тестирование, особенно если соединения часто создаются и закрываются.

Убрал или исправил leak detection

1000 мс выглядит как ошибка. Я бы сделал так:

if (leakDetectionEnabled) {
  config.setLeakDetectionThreshold(10_000)
}

И включал бы это через env/config только на время диагностики. Для постоянного прода лучше метрики плюс алерты на pending, timeout, usage.

Задал poolName

Без имени пула метрики и логи хуже читаются.

config.setPoolName("app-postgres-main")

Название должно быть обезличенным, но понятным: сервис, база, роль. Если есть read-only пул, он должен называться иначе.

Явно настроил таймаут ожидания соединения

30 секунд по умолчанию я бы не оставлял.

config.setConnectionTimeout(5000)
config.setValidationTimeout(2000)

Для API 5 секунд это уже много. Для background jobs можно задать отдельно.

Настроил maxLifetime и keepaliveTime

Например:

config.setMaxLifetime(25 * 60 * 1000)
config.setKeepaliveTime(2 * 60 * 1000)

Но эти числа нельзя копировать без проверки. Надо смотреть реальные таймауты PostgreSQL, cloud DB, firewall, NAT, proxy, PgBouncer, service mesh. maxLifetime должен быть чуть меньше внешнего лимита.

Подумал над minimumIdle

Если сервис online и нагрузка более-менее постоянная, я бы начал с fixed-size:

config.setMaximumPoolSize(poolSize)
config.setMinimumIdle(poolSize)

Если у сервиса бывают длинные периоды без нагрузки, а потом резкие всплески работы, можно оставить динамический пул, но тогда надо понимать latency на создание новых соединений.

Подключил метрики

Для Spring Boot это Actuator/Micrometer. Для ручной конфигурации:

config.setMetricRegistry(meterRegistry);

или JMX:

config.setRegisterMbeans(true);

Я бы не выпускал сервис в прод без графиков:

  • active connections;

  • idle connections;

  • pending threads;

  • connection acquire time;

  • connection usage time;

  • connection timeout count.

Пересчитал maxSize = 32

Не говорю, что 32 плохо. Может быть отлично. Но должно быть понятно, откуда взялось это число.

Минимальный sanity-check:

max_db_connections_for_service / max_replicas

Потом сверка с базой:

(db_cores * 2) + effective_spindle_count

Потом нужен нагрузочный тест. Без него сложно понять, подходит ли выбранный размер пула.


10. Пример более полного конфига

В HOCON это могло бы выглядеть так:

Пример более полного HOCON-конфига
db.jdbc {
  dataSource {
    url = ${?DATASOURCE_URL}
    user = ${?DB_USER}
    password = ${?DB_PASSWD}
  }

  connectionPool {
    poolName = "app-postgres-main"
    maxSize = 16
    minIdle = 16
    connectionTimeoutMs = 5000
    validationTimeoutMs = 2000
    maxLifetimeMs = 1500000
    keepaliveTimeMs = 120000
    leakDetectionThresholdMs = 0
    registerMbeans = true
  }
}

И код:

Пример более полной конфигурации
val config = new HikariConfig()
config.setJdbcUrl(jdbc.url)
config.setUsername(jdbc.user)
config.setPassword(jdbc.password)

config.setPoolName(pool.poolName)
config.setMaximumPoolSize(pool.maxSize)
config.setMinimumIdle(pool.minIdle)
config.setConnectionTimeout(pool.connectionTimeoutMs)
config.setValidationTimeout(pool.validationTimeoutMs)
config.setMaxLifetime(pool.maxLifetimeMs)
config.setKeepaliveTime(pool.keepaliveTimeMs)
config.setLeakDetectionThreshold(pool.leakDetectionThresholdMs)
config.setRegisterMbeans(pool.registerMbeans)
config.setAutoCommit(true)

sslRootCert.foreach(config.addDataSourceProperty("sslrootcert", _))

new HikariDataSource(config)

Опять же, это не “идеальный конфиг для всех”. Просто в нём видно, какие настройки выбраны и зачем.


11. Типовые ошибки

Слишком большой пул

Пул на 100 соединений выглядит солидно только до первого серьёзного инцидента. Потом выясняется, что база тратит слишком много ресурсов на переключение между задачами и ожидание блокировок.

Один конфиг для API и batch

API хочет fail fast. Batch может подождать. API держит соединение миллисекунды. Batch может держать секунды или минуты. Разные профили нагрузки иногда требуют разных пулов или хотя бы разных таймаутов.

Не закрывать соединения

В Java это try-with-resources.

try (Connection connection = dataSource.getConnection()) {
    // SQL work
}

В Spring - нормальные транзакции и отсутствие ручного удержания connection где попало.

В функциональных JVM-стеках - Resource, ZLayer и другие managed lifecycle-подходы. В Clojure - with-open, если берёте connection руками.

Полагаться только на connectionTestQuery

Для нормальных JDBC4-драйверов HikariCP умеет использовать Connection.isValid(). Ручной SELECT 1 часто не нужен. Иногда нужен, если драйвер старый или странный, но это уже исключение.

Не знать таймауты инфраструктуры

База, firewall, NAT gateway, cloud proxy, PgBouncer, service mesh - всё это может закрывать неактивные соединения по своим правилам. Если Hikari узнаёт о смерти соединения последним, пользователи получают ошибки.


12. Мини-чеклист для прода

Мини-чеклист для прода

Перед тем как считать настройку HikariCP законченной, я бы прошёлся по такому списку:

  • версия HikariCP совместима с Java и фреймворком;

  • maximumPoolSize посчитан на все реплики, а не на один процесс;

  • minimumIdle выбран осознанно: fixed-size или dynamic;

  • connectionTimeout подходит под SLA сервиса;

  • maxLifetime меньше внешних connection timeout’ов;

  • keepaliveTime включён, если инфраструктура режет idle-соединения;

  • leak detection выключен или включается для дебага;

  • есть poolName;

  • есть метрики и алерты;

  • был нагрузочный тест после изменения размера пула;

  • read-only и write-пулы различаются в метриках и логах.


13. Ссылки


Вместо вывода

HikariCP хорош тем, что его легко подключить. И опасен тем же самым.

Поставил зависимость, написал maximumPoolSize = 32, сервис стартует, графики зелёные. Потом добавились реплики, вырос p95, база переехала за прокси, в соседнем сервисе включили batch, и старый “нормальный” конфиг стал миной.

Я бы относился к пулу соединений не как к ускорителю или кэшу, а как к ограничителю нагрузки на базу: сколько одновременных запросов к БД мы разрешаем приложению прямо сейчас.

И если это ограничение выбрано осознанно, базе проще работать стабильно под нагрузкой.


Если вам близки темы разработки, рефакторинга, архитектуры и стартапов буду рад видеть вас в моём Telegram-канале.

Успешных вам релизов!