Measured on 0.3.0 with PostgresMetrics() attached to an AsyncSessionManager with PoolSettings(size=2, max_overflow=2, timeout=1.0), eight workers running SELECT pg_sleep(:s) in a loop, sampled every half second from the Prometheus registry next to the application's own count of sqlalchemy.exc.TimeoutError raised by the pool:
3.0 s in_use=4/4 checkouts/s= 72 checkout wait= 55 ms timeouts_total=0 errors_total=0 requests failed on pool timeout=0
... the database slows down: queries take 1.0 s instead of 0.05 s
5.0 s in_use=4/4 checkouts/s= 8 checkout wait= 1007 ms timeouts_total=0 errors_total=0 requests failed on pool timeout=3
7.0 s in_use=4/4 checkouts/s= 8 checkout wait= 1006 ms timeouts_total=0 errors_total=0 requests failed on pool timeout=6
8.0 s in_use=4/4 checkouts/s= 8 checkout wait= 1007 ms timeouts_total=0 errors_total=0 requests failed on pool timeout=7
Two things in that table.
postgres_db_connection_timeouts_total is documented as "Number of connection checkout timeouts" and it stayed at zero through seven of them. attach_metrics counts timeouts from the engine's handle_error event, which fires for DBAPI errors during execution; a QueuePool checkout timeout is raised by pool.connect() before any DBAPI call and never reaches that listener. So the one timeout a pool actually produces under load is the one the counter does not count.
postgres_db_connection_checkout_duration_seconds is the time between the checkout and checkin events, which is how long a connection was held, and the column above confirms it: 55 ms while queries took 50 ms, a second while they took a second. That is a useful number (it is the query duration seen from the pool), but its name says checkout, the guide's monitoring advice reads it as the time a caller waited for a connection, and the wait is the metric that predicts a pool outage. The wait is not exposed at all.
What I think it needs: a real checkout-wait histogram (time spent inside pool.connect() before a connection is handed out) and a timeout counter that increments when that wait ends in TimeoutError. SQLAlchemy has no pool event for "checkout requested", so the instrumentation has to sit around the acquisition: the kit already owns the pool class through resolve_pool_class / register_pool_class, and an instrumented AsyncAdaptedQueuePool subclass that times _do_get and counts its TimeoutError would cover both without touching callers; timing session.connection() in get_session would be the alternative. The existing histogram should keep its data and get a name that says what it measures (held duration), with a deprecation note in the agents page's metrics paragraph, the monitoring section of the configuration guide, and the PgBouncer guide, which quotes it.
Lab: pool_metrics_lab.py in https://github.com/bedrock-python/bedrock-python.github.io/tree/docs/production-python-series/docs/blog/lab/2026-09-07-sqlalchemy-pool-metrics.
Measured on 0.3.0 with
PostgresMetrics()attached to anAsyncSessionManagerwithPoolSettings(size=2, max_overflow=2, timeout=1.0), eight workers runningSELECT pg_sleep(:s)in a loop, sampled every half second from the Prometheus registry next to the application's own count ofsqlalchemy.exc.TimeoutErrorraised by the pool:Two things in that table.
postgres_db_connection_timeouts_totalis documented as "Number of connection checkout timeouts" and it stayed at zero through seven of them.attach_metricscounts timeouts from the engine'shandle_errorevent, which fires for DBAPI errors during execution; aQueuePoolcheckout timeout is raised bypool.connect()before any DBAPI call and never reaches that listener. So the one timeout a pool actually produces under load is the one the counter does not count.postgres_db_connection_checkout_duration_secondsis the time between thecheckoutandcheckinevents, which is how long a connection was held, and the column above confirms it: 55 ms while queries took 50 ms, a second while they took a second. That is a useful number (it is the query duration seen from the pool), but its name says checkout, the guide's monitoring advice reads it as the time a caller waited for a connection, and the wait is the metric that predicts a pool outage. The wait is not exposed at all.What I think it needs: a real checkout-wait histogram (time spent inside
pool.connect()before a connection is handed out) and a timeout counter that increments when that wait ends inTimeoutError. SQLAlchemy has no pool event for "checkout requested", so the instrumentation has to sit around the acquisition: the kit already owns the pool class throughresolve_pool_class/register_pool_class, and an instrumentedAsyncAdaptedQueuePoolsubclass that times_do_getand counts itsTimeoutErrorwould cover both without touching callers; timingsession.connection()inget_sessionwould be the alternative. The existing histogram should keep its data and get a name that says what it measures (held duration), with a deprecation note in the agents page's metrics paragraph, the monitoring section of the configuration guide, and the PgBouncer guide, which quotes it.Lab:
pool_metrics_lab.pyin https://github.com/bedrock-python/bedrock-python.github.io/tree/docs/production-python-series/docs/blog/lab/2026-09-07-sqlalchemy-pool-metrics.