Connection pooling is one of the load-bearing structural decisions in any enterprise platform : every database query passes through it, every concurrent user contends on it, every observability surface depends on it. After running the platform's previous in-house pool for years, this release ships a new generation — FastObjectPoolV2 — that supersedes the original on every axis : per-acquisition latency, concurrent-pool density, observability depth and operational control. The pool is in-house on every code path — request, administration, maintenance and offline mode all share the same implementation.
Why an in-house pool
External pools — HikariCP, Commons DBCP, Tomcat JDBC — solve one problem well : sharing a single set of connections across many requests to a single database, identified by a single username. That model fits a SaaS application with one tenant per process. It does not fit the platform's model, where every request carries its end-user identity all the way to the database. The platform does not delegate any code path to an external pool ; the in-house design covers the request path, the administration path, the maintenance-mode path and the offline-mode path uniformly.
- One pool per (server × database × user). A pool of 50 connections shared across 50 end-users is not the same as 50 pools of 1 connection each. The platform's row-level security expressions, the audit trail and the database-side
connect_useridentity all derive from the user the connection was opened under. Sharing a connection across users would break all three. This is the property an external pool cannot give us — and the reason we maintain our own. - Thousands of pools per server. A medium-sized deployment carries one pool per active user × per database. On the busiest customers that resolves to 1,000–3,000 simultaneous pools per database server, each one short-lived, each one needing its own lifecycle, statistics and timeout policy. Generic pools assume tens of pools per process, not thousands ; their per-pool overhead (a dedicated thread, a scheduled executor, a metric registry per pool) does not scale to that count on a single JVM.
- Logical-to-physical username mapping — three modes, one model. The pool keys on the logical user (the identity the application sees) ; the connection factory resolves the physical database user (the credential the connection authenticates with). The mapping is configuration, not constraint :
- 1:1. Every logical end-user maps to a dedicated physical account on the database. The database-side
connect_user, the audit trail and the engine's session monitor all see the real person. This is the strongest tracking shape — and the demanding one operationally, because a thousand active users means a thousand simultaneous pools per database server. - Many-to-one (departmental). Multiple logical users share a physical account — everyone in Sales connects as
sales_app, everyone in Finance asfinance_app. The platform's own audit trail still records the logical user on every row ; the database sees the shared account but the application keeps individual attribution. - Mixed. Power users (DBAs, integration accounts, regulated-role users) get 1:1 ; the long tail rolls up to departmental accounts. The pool handles both shapes in the same JVM, sized to whichever mix the customer's compliance model demands.
app_useron PostgreSQL,axional_appon Oracle,analytics_useron Vertica — never DBA or system roles. The choice between 1:1 and departmental is the customer's call ; the pool architecture treats both as first-class. - 1:1. Every logical end-user maps to a dedicated physical account on the database. The database-side
- Administration paths are pools too. Putting a tenant offline, entering maintenance mode, schema-evolution dry-runs and the Database Workbench's ad-hoc connection panel all use the same in-house pool, with different configuration (longer idle TTL, deferred validation, no scavenger pressure). One pool implementation, configured per workload — never a separate external dependency for "admin".
Pool V2 architecture in one diagram
- Tier 1 — thread-local cache (~20–50 ns). Each request thread caches its last-used connection. Hit rate runs 70–90 % on thread-affinity workloads, so the vast majority of acquisitions never touch the shared queue at all.
- Tier 2 — shared free queue (~135–580 ns). A lock-free
ConcurrentLinkedQueueof available connections. Compare-and-swap operations make the fast path block-free under contention. - Tier 3 — async connection creation (~10 ms). When the pool needs to grow, the new connection materialises on the platform's
VirtualThreadExecutor(JEP 444 virtual threads, shipped on the platform since 2025-11) so the acquiring request does not block on TCP setup, TLS handshake and database authentication serially. Each pending connection-establishment is a virtual thread carrying kilobytes of stack rather than the megabytes a platform thread would cost — three thousand pools growing simultaneously do not exhaust the OS thread budget. - Bounded capacity through a semaphore. The pool's maximum size is enforced by a
PoolSemaphore; capacity can be increased at runtime without restart. - Background scavenger. Idle connections beyond the configured TTL and connections that fail liveness validation are reclaimed by a periodic scavenger. The scavenger emits JFR events so the operations team sees scavenger pressure as a first-class metric rather than a log-line.
- Graceful shutdown. A two-phase shutdown waits up to 2 seconds for in-flight work to return its connection before forcing destroy ; the forced phase carries a 5-second-per-object timeout so a hung connection cannot indefinitely block tenant maintenance.
Observability that the request path already pays for
Every acquire, release, scavenge and destroy operation emits a JFR (Java Flight Recorder) event ; JMX exposes the per-pool counters (created, destroyed, borrowed, returned, error-create, error-close, scavenger-removed) as MBean attributes. Operators inspect a thousand-pool deployment through the same JMX dashboards that already cover the JVM and the gRPC microservice fleet — no separate pool-specific monitoring stack.
The legacy v1 pool will continue to run in parallel for the next two release trains while customers migrate their configuration ; v2 is the default for new deployments and the recommended target for existing ones. The companion benchmark post covers the numbers that drove the architectural changes.