FastAPI + Mangum on AWS Lambda: SQLAlchemy connection pool lifecycle during container reuse
0 reputation · 25 Apr 2024, 21:49 UTC
0 reputation · 25 Apr 2024, 21:49 UTC
A FastAPI application runs on AWS Lambda via the Mangum ASGI adapter with an async SQLAlchemy engine connected to Aurora Serverless v2. The engine is created at module level and its connection pool persists across warm Lambda invocations because Mangum reuses the same application instance.
Ensure the first request after an idle period (cold container reuse) succeeds without "MySQL server has gone away" errors, while keeping per-invocation latency and database connection overhead low for a low-traffic workload.
SQLAlchemy's pool_pre_ping validates connections before checkout, yet Aurora's wait_timeout (default 300 seconds for Serverless v2) can expire idle pooled connections during Lambda's scale-to-zero pauses. Setting pool_recycle below the database timeout prevents stale checkouts but does not eliminate the validation round-trip on the first request after reuse. An alternative is creating a fresh engine with NullPool inside each handler invocation, which guarantees clean connections at the cost of a new TCP/SSL handshake per request. Background tasks scheduled after the response may be truncated when the container freezes, so they cannot reliably clean up or refresh pools.
Which pool configuration—module-level engine with tuned pool_recycle and pool_pre_ping, per-invocation NullPool engine, or offloading pooling to RDS Proxy—provides the most predictable latency and cost profile for sporadic traffic? How should the FastAPI lifespan shutdown hook be structured (or omitted) given that Lambda may terminate containers without invoking it? Does enabling provisioned concurrency meaningfully change the pool-staleness risk compared to pure on-demand scaling?
34775 reputation · 26 Apr 2024, 00:13 UTC
Use one module-level async SQLAlchemy engine with pool_pre_ping=True and pool_recycle set below your Aurora wait_timeout. Do not create a new NullPool engine inside each handler for sporadic traffic, and do not rely on FastAPI lifespan shutdown to dispose the pool. Provisioned concurrency reduces cold starts but does not remove the need for pre_ping and recycle.
After an idle pause longer than wait_timeout, Aurora may close a pooled connection. The next warm Lambda invocation checks out that dead connection unless SQLAlchemy validates it first. pool_pre_ping catches the stale connection with one validation query; pool_recycle only shrinks the stale window and cannot prevent a death between recycle and checkout.
pool_pre_ping validates a connection before checkout; pool_recycle disposes connections older than the configured seconds.wait_timeout defaults to 300 seconds. Verify this in your DB parameter group before choosing pool_recycle.from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
engine = create_async_engine(
DATABASE_URL,
pool_pre_ping=True,
pool_recycle=240, # set below your Aurora wait_timeout; 240 is an example only
pool_size=5,
max_overflow=10,
)
session_factory = async_sessionmaker(engine, expire_on_commit=False)
finally. Do not call await engine.dispose() per request or in shutdown.wait_timeout in the Aurora parameter group. If it is not 300 seconds, set pool_recycle at least 20–60 seconds below it.wait_timeout. Watch for MySQL server has gone away and elevated checkout latency from pre_ping.Provisioned concurrency keeps environments initialized and shortens idle gaps, which lowers the chance that a pooled connection sits idle past wait_timeout. It does not eliminate staleness if idle periods still exceed the timeout, so keep pool_pre_ping and pool_recycle. Enable it only if the hourly idle cost is justified by fewer cold starts.
RDS Proxy can mask stale connections and smooth connection spikes, but it adds a proxy hop and separate cost. For low-traffic sporadic workloads, a tuned module-level pool is usually cheaper and simpler.
What is wait_timeout in your Aurora DB parameter group? The exact pool_recycle value depends on it; do not use 240 or 280 without confirming the server timeout.
wait_timeout against the warm-request baseline.NullPool, measure new-connection rate and TCP/SSL handshake time in CloudWatch Logs or X-Ray; expect higher latency than the tuned pool.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.