HikariCP pool tuning untuk Postgres
Tune HikariCP pool size, timeout, dan leak detection untuk Postgres production. Formula sizing yang sering kelewat.
Dipublikasikan 8 Juli 2026
HikariCP default config tidak optimal untuk production. Pool 10 connection dengan 50 instance Spring Boot = 500 connection ke Postgres, padahal Postgres max biasanya 100. App akan crash diam-diam saat traffic naik. Snippet ini config production-ready + monitoring metrics.
Kode
# application.yml
spring:
datasource:
url: jdbc:postgresql://db.tangerang.id:5432/tokopedia?reWriteBatchedInserts=true&sslmode=require
username: ${DB_USER}
password: ${DB_PASSWORD}
driver-class-name: org.postgresql.Driver
hikari:
# ==========================================
# POOL SIZE — paling penting
# ==========================================
# Formula: connections = (cpu_cores * 2) + effective_disk_count
# Untuk app modern dengan SSD: cpu * 2 + 1 cukup
# Kalau 4 cpu instance: 4 * 2 + 1 = 9 → bulatkan 10
maximum-pool-size: 20
minimum-idle: 5 # Idle minimum supaya warm-up tidak nyiksa
# ==========================================
# TIMEOUT — bagaimana app behave saat pool exhausted
# ==========================================
# Max wait untuk dapat connection — fail fast kalau pool kepenuhan
connection-timeout: 3000 # 3 detik
# Berapa lama connection idle sebelum di-close
# Trade-off: pendek = nyaman ke DB, panjang = warm pool
idle-timeout: 600000 # 10 menit
# Connection lifetime maksimum — workaround firewall NAT idle
# Cloud Postgres (RDS, Cloud SQL) biasanya kasih 30 menit idle limit
max-lifetime: 1800000 # 30 menit (< server's idle_in_transaction)
# Test query — verify connection valid sebelum kasih ke app
validation-timeout: 3000
connection-test-query: "SELECT 1" # Optional untuk pgjdbc-ng
# ==========================================
# LEAK DETECTION — penting di staging
# ==========================================
# Warning kalau connection dipinjam > 30 detik (kemungkinan leak)
leak-detection-threshold: 30000
# ==========================================
# PROPERTIES Postgres-specific
# ==========================================
data-source-properties:
# Server-side prepared statement caching
prepareThreshold: 5
preparedStatementCacheQueries: 256
preparedStatementCacheSizeMiB: 5
# TCP keepalive — detect connection mati lebih cepat
tcpKeepAlive: true
# Application name muncul di pg_stat_activity — debug friendly
ApplicationName: tokopedia-api
# Schema search path
currentSchema: public,audit
# Pool name untuk debug
pool-name: tokopedia-hikari
jpa:
properties:
hibernate:
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
// HikariMetricsConfig.java — expose metric ke Prometheus
package id.kodekarawaci.config;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.annotation.PostConstruct;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HikariMetricsConfig {
@Autowired private DataSource dataSource;
@Autowired private MeterRegistry meterRegistry;
@PostConstruct
public void registerMetrics() {
if (dataSource instanceof HikariDataSource hikari) {
hikari.setMetricsTrackerFactory(new MicrometerMetricsTrackerFactory(meterRegistry));
}
}
}
// LeakWarningHandler.java — capture leak warning di log
package id.kodekarawaci.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import com.zaxxer.hikari.HikariDataSource;
@Component
public class HikariLeakConfig implements BeanPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(HikariLeakConfig.class);
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof HikariDataSource hikari) {
LOG.info("HikariCP pool initialized: maxPoolSize={}, minIdle={}, connectionTimeout={}ms",
hikari.getMaximumPoolSize(),
hikari.getMinimumIdle(),
hikari.getConnectionTimeout());
}
return bean;
}
}
Pemakaian
# Cek metric pool di Prometheus
curl http://localhost:8080/actuator/prometheus | grep hikari
# hikaricp_connections_active{pool="tokopedia-hikari"} 8
# hikaricp_connections_idle{pool="tokopedia-hikari"} 12
# hikaricp_connections_pending{pool="tokopedia-hikari"} 0
# hikaricp_connections_acquire_seconds_sum{pool="tokopedia-hikari"} 1.23
# Query Prometheus — pool utilization
hikaricp_connections_active{pool="tokopedia-hikari"}
/ hikaricp_connections_max{pool="tokopedia-hikari"} * 100
# Alert kalau pending > 0 selama > 5 menit (pool exhaustion)
hikaricp_connections_pending{pool="tokopedia-hikari"} > 0
-- Cek di Postgres side — ada berapa connection dari app
SELECT
application_name,
state,
COUNT(*) AS conn_count,
MAX(state_change) AS last_state_change
FROM pg_stat_activity
WHERE application_name LIKE 'tokopedia%'
GROUP BY application_name, state;
-- application_name | state | conn_count
-- tokopedia-api | active | 8
-- tokopedia-api | idle | 12
-- tokopedia-api | idle in transaction | 0 -- ini wajib 0
# Test leak detection di staging
# Set leak-detection-threshold: 5000 (5 detik untuk testing)
# Trigger leak: query tanpa try-with-resources
# Log akan show:
# WARN [HikariPool] tokopedia-hikari - Connection leak detection triggered
# for connection org.postgresql.jdbc.PgConnection@xxx, stack trace follows
# java.lang.Exception
# at OrderService.findOrder(OrderService.java:42)
Kapan dipakai
- Production Spring Boot dengan throughput > 100 req/s.
- Multi-instance deployment — perlu hitung total connection capacity.
- Aplikasi yang punya batch job + web request bareng (potential resource war).
- Setelah migrate ke cloud DB (RDS, Cloud SQL) yang punya idle timeout.
Catatan
- Total connection = pool_size × instance_count — kalau punya 10 instance dengan pool 30, total 300 connection ke Postgres. Cek
max_connectionsPostgres (default 100). - maximumPoolSize ≠ minimumIdle — pool grow on demand sampai max, shrink ke min saat idle. Set min minimum 5-10 supaya warm-up cepat.
- max-lifetime < server idle timeout — Cloud Postgres punya 30 menit idle timeout. Set HikariCP 25-28 menit supaya rotate dulu sebelum DB tutup paksa.
- leak-detection-threshold wajib di staging — catch leak code lebih awal. Production set ke 60 detik supaya tidak false positive di long query.
- prepareThreshold Postgres-specific — query yang dipanggil > N kali akan di-prepare server-side. Hemat parsing.
- idle in transaction harus 0 di Postgres. Kalau lebih, ada code yang lupa commit/rollback — leak slot.
- Connection vs CPU — lebih banyak connection bukan berarti lebih cepat. DB satu thread per connection — terlalu banyak bikin contention.
“Pool size optimal” dependent on workload. Profile dulu dengan k6 / wrk, cek hikaricp_connections_pending. Kalau > 0 saat load normal, tambah pool size. Kalau selalu 0, kurangi (hemat resource).
# tags
hikaricpconnection-poolpostgresperformancejava
Ditulis oleh Asti Larasati · 8 Juli 2026