Connection Pooling with HikariCP

Why a Pool

Opening a database connection takes 20 to 100 ms (TCP, TLS, login). A connection pool opens a few connections at startup and lends them to requests. Spring Boot uses HikariCP by default.

Example

A pool is like a counter with N tokens. Here 2 "connections" serve 5 requests; extra requests wait for a free connection. A Semaphore models it:

import java.util.*;
import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Semaphore pool = new Semaphore(2);                 // maximum-pool-size = 2
        ExecutorService requests = Executors.newFixedThreadPool(5);
        List<Future<String>> results = new ArrayList<>();

        for (int i = 1; i <= 5; i++) {
            int id = i;
            results.add(requests.submit(() -> {
                boolean waited = !pool.tryAcquire();
                if (waited && !pool.tryAcquire(2, TimeUnit.SECONDS)) return "request " + id + ": timeout";
                try {
                    Thread.sleep(100);                     // run the SQL query
                    return "request " + id + (waited ? ": waited for a connection" : ": got a connection immediately");
                } finally {
                    pool.release();
                }
            }));
            Thread.sleep(10);                              // requests arrive one after another
        }
        for (Future<String> f : results) System.out.println(f.get());
        requests.shutdown();
    }
}

Output

request 1: got a connection immediately
request 2: got a connection immediately
request 3: waited for a connection
request 4: waited for a connection
request 5: waited for a connection

Tuning Hikari

Example

application.yml

spring:
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:3306/shopease
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 20        # total connections per instance
      minimum-idle: 5
      connection-timeout: 3000     # ms to wait for a free connection, then fail fast
      idle-timeout: 600000
      max-lifetime: 1800000        # below the DB's wait_timeout
      leak-detection-threshold: 20000
      pool-name: shopease-hikari
  • Bigger is not better. A good start: connections = (CPU cores of the DB × 2) + disks, often 10 to 30.
  • Multiply by the number of app instances: 6 pods × 20 = 120 connections. Check the database's max_connections.
  • Watch hikaricp_connections_pending in metrics. If requests often wait, find slow queries before increasing the pool.

Keep Transactions Short

Note: A connection is held for the whole @Transactional method. Never call a slow HTTP API (payment, SMS) inside a transaction: the connection sits idle while you wait, and the pool runs dry. Also set spring.jpa.open-in-view=false so connections are not held while the JSON response is written.