FastAPI+SQLAlchemy+PGBouncer操作中随机出现连接关闭问题求助
我用FastAPI搭配SQLAlchemy和PGBouncer,生产环境中随机出现错误:asyncpg.exceptions.ConnectionDoesNotExistError: connection was closed in the middle of operation,本地无法复现。
试过给SQLAlchemy使用NullPool连接池、将max_overflow设为0、添加pool_pre_ping配置,问题依旧存在。
我的SQLAlchemy配置
engine = create_async_engine( url=db_url, echo=False, pool_size=10, max_overflow=0, pool_timeout=120, pool_pre_ping=True, pool_recycle=500, pool_reset_on_return=None, connect_args={"prepared_statement_cache_size": 0, 'server_settings': {'jit': 'off'}} ) Session = sessionmaker( bind=engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, autocommit=False, )
PGBouncer配置(session模式)
default_pool_size=120 max_client_con=2800 pool_mode=session tcp_keepintvl=30 # 今日新增,正在测试效果
PG数据库的max_connections设置为2000,但应用使用连接时仍会意外断开。
SQLAlchemy会话使用方式
每个请求创建新会话,使用后关闭,上下文管理器代码如下:
@asynccontextmanager async def get_session_context() -> AsyncSession: # type: ignore async with Session() as session: if session is None: raise Exception("Database session is None") try: yield session except Exception as e: LOGGER.error(pprint.pprint(e, indent=4, depth=4)) await session.rollback() raise e finally: await session.close()
我曾怀疑会话使用问题,但资料建议使用后关闭会话。想问:关闭会话会释放连接池里的连接吗?还是其他配置导致连接在使用中被断开?
更新补充日志
2025-01-25 05:24:37.372 UTC [1570] LOG: connection authenticated: identity="postgres" method=scram-sha-256 (/var/lib/postgresql/data/pgdata/pg_hba.conf:128) 2025-01-25 05:24:37.372 UTC [1570] LOG: connection authorized: user=postgres database=mydb 2025-01-25 05:24:37.372 UTC [58] LOG: duration: 0.068 ms 2025-01-25 05:24:37.372 UTC [51] LOG: duration: 0.142 ms 2025-01-25 05:24:37.372 UTC [52] LOG: duration: 0.105 ms 2025-01-25 05:24:37.372 UTC [40] LOG: duration: 0.122 ms 2025-01-25 05:24:37.372 UTC [107] LOG: duration: 0.197 ms 2025-01-25 05:24:37.372 UTC [49] LOG: duration: 0.282 ms 2025-01-25 05:24:37.372 UTC [146] LOG: duration: 0.170 ms 2025-01-25 05:24:37.373 UTC [1327] LOG: duration: 0.343 ms 2025-01-25 05:24:37.373 UTC [46] LOG: duration: 0.545 ms 2025-01-25 05:24:37.373 UTC [1339] LOG: duration: 1.119 ms 2025-01-25 05:24:37.373 UTC [169] LOG: duration: 0.195 ms 2025-01-25 05:24:37.410 UTC [46] LOG: duration: 36.826 ms 2025-01-25 05:24:37.424 UTC [66] LOG: duration: 0.111 ms 2025-01-25 05:24:37.424 UTC [276] LOG: duration: 0.055 ms 2025-01-25 05:24:37.424 UTC [275] LOG: duration: 0.135 ms 2025-01-25 05:24:37.424 UTC [36] LOG: duration: 0.127 ms 2025-01-25 05:24:37.424 UTC [284] LOG: duration: 0.156 ms 2025-01-25 05:24:37.424 UTC [116] LOG: duration: 0.133 ms ... 2025-01-25 05:37:26.250 UTC [1849] LOG: duration: 0.093 ms 2025-01-25 05:37:26.250 UTC [37] LOG: duration: 0.062 ms 2025-01-25 05:37:26.251 UTC [12642] LOG: duration: 0.496 ms 2025-01-25 05:37:26.270 UTC [661] LOG: duration: 0.190 ms 2025-01-25 05:37:26.270 UTC [178] LOG: duration: 0.186 ms 2025-01-25 05:37:26.270 UTC [178] LOG: duration: 0.035 ms 2025-01-25 05:37:26.274 UTC [987] LOG: duration: 67.965 ms 2025-01-25 05:37:26.282 UTC [56] FATAL: terminating connection due to idle-session timeout 2025-01-25 05:37:26.282 UTC [444] FATAL: terminating connection due to idle-session timeout 2025-01-25 05:37:26.282 UTC [56] LOG: disconnection: session time: 0:14:02.738 user=postgres database=mydb host=fd12:4d65:2636::b2:ee06:57a1 port=44082 2025-01-25 05:37:26.282 UTC [444] LOG: disconnection: session time: 0:13:45.227 user=postgres database=mydb host=fd12:4d65:2636::b2:ee06:57a1 port=5263
日志中持续出现上述连接终止信息。
解答
关闭会话是否释放连接池连接?
是。关闭AsyncSession会将底层数据库连接归还给SQLAlchemy连接池,不会直接关闭连接,这是正确的操作,并非问题根源。
问题核心:PostgreSQL主动断开空闲会话
从日志中的FATAL: terminating connection due to idle-session timeout可以明确,PostgreSQL会自动断开空闲时间超过阈值的会话。而SQLAlchemy连接池仍持有这些已失效的连接,当后续请求复用它们时,就会触发ConnectionDoesNotExistError。
配置不匹配导致的问题
你的SQLAlchemy设置了pool_recycle=500(约8分钟),但日志中的连接空闲了14分钟才被PG断开,说明:
- PostgreSQL的空闲会话超时阈值(
idle_session_timeout或idle_in_transaction_session_timeout)大于500秒 - SQLAlchemy的连接回收机制未及时清理即将被PG断开的连接,或
pool_pre_ping在部分场景下未生效
修复方案
对齐SQLAlchemy与PG的超时配置
- 先查询PG的空闲超时参数:
SHOW idle_session_timeout; SHOW idle_in_transaction_session_timeout; - 将SQLAlchemy的
pool_recycle设为比PG超时值小30-60秒,比如PG超时为840秒(14分钟),则pool_recycle设为780秒(13分钟),确保SQLAlchemy在PG断开连接前主动回收并重建连接。
- 先查询PG的空闲超时参数:
优化PGBouncer配置
- 添加
server_idle_timeout,值设为比PG空闲超时小,让PGBouncer主动断开空闲的后端连接:server_idle_timeout=780 - 可选设置
client_idle_timeout,清理长时间空闲的客户端连接,避免占用PGBouncer的连接数。
- 添加
确保
pool_pre_ping生效pool_pre_ping=True会在从连接池取出连接时执行ping操作,检测连接有效性。若连接已失效,会自动重建。结合pool_recycle使用,可最大程度避免使用失效连接。验证会话无泄漏
给会话的创建和关闭添加日志,确认所有会话都进入finally块并被关闭,避免连接池中的连接被长期占用无法回收。
内容的提问来源于stack exchange,提问作者Confidence Yobo

