我有一个使用FastAPI和SQLAlchemy构建的Web应用程序,它在本地使用Docker正常工作,但在DigitalOcean托管的Postgres数据库上查询时出现错误:
(psycopg2.OperationalError) server closed the connection unexpectedly\n\tThis probably means the server terminated abnormally\n\tbefore or while processing the request.\n\n(详细信息请参阅此错误: http://sqlalche.me/e/14/e3q8)"}
之前我在使用Flask时也遇到过这个错误,解决方法是将引擎选项pool_pre_ping=True
设置为正确的值,并将我的群集/ droplet IP添加到数据库的受信任来源中。但是,看起来使用FastAPI还不够。我还能做什么来顺利执行查询?
背景
- Python 3.9
- DigitalOcean托管的Postgres 13
- psycopg==2.8.6,但也尝试了2.8.5(在我使用Flask的类似情况下100%成功)和2.7.4以防万一
- 我已经设置了
pool_pre_ping=True
- 我使用
session.get_bind().pool._pre_ping
检查每个请求之前是否真正设置为True
,实际上是True
- 我使用
- 我检查了我的群集节点的IP是否在受信任的DB来源中
- 我使用一个中间件在FastAPI端点内访问我的db会话,就像这样:
class DBMiddleware:
def __init__(self, app, sqlalchemy_uri):
self.app = app
self.sqlalchemy_uri = sqlalchemy_uri
self.engine = None
async def __call__(self, scope: Scope, receive: Receive, send: Send):
if scope['type'] not in ['http', 'websocket']:
await self.app(scope, receive, send)
return
if not self.engine:
self.engine = create_engine(self.sqlalchemy_uri, pool_pre_ping=True, pool_recycle=3600)
session = Session(autoflush=False, autocommit=False, bind=self.engine)
scope['db'] = session
await self.app(scope, receive, send)
session.close()
def get_db(request: Request):
return request.scope.get('db')
...
@app.on_event('startup')
async def startup():
...
app.add_middleware(DBMiddleware, sqlalchemy_uri=config.SQLALCHEMY_DATABASE_URI)
@router.post('/endpoint')
async def endpoint(db: Session = Depends(get_db)):
...
- 我尝试使用全局定义的引擎和会话上下文(仅用于检查),但仍然有相同的行为,因此看起来中间件不是问题
- 没有来自Postgres方面的有用日志
- 我还尝试将应用程序查询更改为
db.execute('SELECT 1')
以防出现一些奇怪的超时或其他情况 - 仍然是相同的 - 我通常阅读了很多关于psycopg2的类似问题,但关于FastAPI的问题很少,例如这个和那个以及官方文档。
经过所有尝试,问题仍然存在。我对async
Python并不太熟悉,因此可以怀疑问题可能在连接共享方式或其他方面(但目前只使用一个worker)。
更新
我尝试切换到asyncpg
(文档:https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html)。也在本地工作,但在DigitalOcean上查询挂起,我收到以下错误:
[Errno 104] Connection reset by peer
看起来原因是一样的,但是对于asyncpg错误看起来不同。
还尝试在DigitalOcean上创建连接池并连接到它 - 仍然是相同的错误。
async
是没有意义的。我尝试了去掉async
但结果还是一样。同时,在本地环境下两种情况都可以正常工作,并且使用asyncpg时应该使用async
,在本地环境下也能按预期工作。问题只出现在DO托管的数据库中。看起来数据库设置不同(但我无法获取托管数据库配置)。此类似问题中99%的情况只需设置pool_pre_ping
即可解决问题,并且在Flask应用程序中使用相同的设置已经解决了DO托管数据库的问题。 - Max