Warm-up

Exhaust a pool on purpose

10 minjunior015 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • Connections needed is arrival rate times hold time, and nothing else
  • Hold time decides everything, and it covers more than query execution
  • Exhaustion fails at acquisition, before the database is involved
  • A remote call inside the connection's lifetime is how pools actually die

Starter

Starter.javaOpen in playground
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

/**
 * Warm-up: make a pool run out, then work out why.
 *
 * A Semaphore is a connection pool with the interesting parts kept and the
 * JDBC removed. Every thread here must be a daemon, so a stuck request cannot
 * hold the JVM open.
 */
public class Starter {

    public static void main(String[] args) throws Exception {

        // TODO 1: Little's Law first, on paper. A service takes 1000 requests
        // per second and each holds a connection for 5 ms. How many
        // connections does it need? Now recompute for a 200 ms hold.
        //
        //     connections = requestsPerSecond * (holdMillis / 1000)

        // TODO 2: build the pool. A Semaphore(size, true) plus a method that
        // tryAcquire(timeout), sleeps for holdMillis, and releases in a
        // finally. Count served, timed out, and peak in use.

        // TODO 3: fire 50 requests at a pool of 10, each holding for 100 ms,
        // with a 250 ms acquisition timeout.
        //
        // Predict served and refused BEFORE running it. The arithmetic is
        // exact: how many 100 ms rounds fit inside 250 ms?

        // TODO 4: check peak in use. It cannot exceed the pool size — confirm
        // that, because it is what makes the pool a concurrency limit rather
        // than a capacity.

        // TODO 5: the failures in TODO 3 never reached a database. Say what
        // exception a real HikariCP throws here, and why that is a different
        // problem from a slow query even though the latency graph looks the
        // same.

        // TODO 6: now the important one. Keep everything identical but hold
        // the connection for 300 ms instead of 100 — as if a 200 ms remote
        // call happened inside it. Compare served counts.
        //
        // Then say which endpoints in a real service are affected. The answer
        // is not "the one that calls the remote service".

        // TODO 7: raise the pool to 50 and re-run TODO 6. Everything is served.
        // Say why that is not the fix, and what it costs the database.

        // TODO 8: instead, raise the acquisition timeout to 60 seconds and
        // re-run TODO 3. Nothing is refused. Say what has actually changed
        // from a user's point of view, and which of the two you would rather
        // deploy.
    }
}

Run it locally:

cd exercises/java/db-performance/connection-pool-sizing/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You computed pool size from a rate and a hold time and checked it against a run
  • You exhausted a real pool and counted the acquisition failures
  • You predicted the served/refused split from the timeout and hold time before running it
  • You showed the same pool serving far fewer requests when a remote call is held inside it

← Back to How do you size a database connection pool?