ArchitectureA supervised JVM-class runtime — OLTP on seven engines, OLAP on three. AI-native, MCP-native, observable as plain SQL.Read the architecture
Está viendo la edición Perú. Está viendo la edición Colombia. You're viewing the Pakistan edition. Cambiar a la edición global →Cambiar a la edición global →Switch to the global edition →

JDBC Pool V2 — the new in-house connection-pool architecture that replaces our previous design

A new generation of the platform's JDBC connection pool ships, replacing the in-house design we have iterated on for years. The architecture handles thousands of independent (server × database × user) pools simultaneously — the property no generic pool can match — at sub-microsecond per-acquisition latency.

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_user identity 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 as finance_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.
    The physical accounts are non-privileged application users in every mode — app_user on PostgreSQL, axional_app on Oracle, analytics_user on 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.
  • 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 ConcurrentLinkedQueue of 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.

See the feature →

← All posts