#python #postgresql #sqlalchemy #digital-ocean #fastapi
Вопрос:
У меня есть веб-приложение, созданное с помощью FastAPI и SQLAlchemy, и оно нормально работает локально с Docker, но запрос бд завершается ошибкой в DigitalOcean с размещенной базой данных Postgres:
(psycopg2.OperationalError) сервер неожиданно закрыл соединениеnT Это, вероятно, означает, что сервер аварийно завершил работуnt до или во время обработки запроса.nn(Предыстория этой ошибки по адресу: http://sqlalche.me/e/14/e3q8)»}
У меня уже была эта ошибка во время работы с Flask, и проблема заключалась в том, что мне пришлось установить опцию движка pool_pre_ping=True
и добавить свой IP-адрес кластера/капли в надежные источники базы данных. Но, похоже, с FastAPI этого недостаточно. Что еще я могу сделать для успешного выполнения запросов?
Фон
- Python 3.9
- DigitalOcean разместил Postgres 13
- psycopg==2.8.6, но также попробовал 2.8.5 (что на 100% сработало в аналогичном случае с колбой для меня) и 2.7.4 на всякий случай
- Я установил
pool_pre_ping=True
- Я проверил, что он действительно установлен
True
прямо перед использованием запросаsession.get_bind().pool._pre_ping
, и на самом деле это такTrue
- Я проверил, что он действительно установлен
- Я проверил, что IP-адреса узлов моего кластера находятся в надежных источниках БД
- Я запускаю приложение с помощью gunicorn, используя одного
uvicorn.workers.UvicornH11Worker
рабочего - Я использую промежуточное программное обеспечение для доступа к сеансу БД внутри точек доступа FastAPI, как это:
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, поэтому могу подозревать, что проблема может быть в том, как используется общий доступ к соединению или что-то в этом роде (но в данный момент я использую только одного работника).
Обновить
Я попытался переключиться на asyncpg
(документы: https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html). Также работает локально, но на DigitalOcean запрос зависает, и я получаю следующую ошибку:
[Errno 104] Connection reset by peer
Похоже, причина та же, но ошибка выглядит по-другому для asyncpg.
Также попытался создать пул соединений на DigitalOcean и подключиться к нему — все та же ошибка.
Комментарии:
1. В чем причина использования здесь асинхронности? Функционирует ли код, если он не вызывается асинхронно? Учитывая, что асинхронные функции теоретически могут запускаться в любом порядке относительно друг друга, я бы подумал, что это наиболее вероятная причина здесь.
2. Да, согласно документам fastapi, это не имеет смысла для примера синхронизации. Я попытался удалить
async
, но все равно то же самое. Кроме того, он отлично работает локально в обоих случаях, и использование asyncpg должно бытьasync
, и он также работает локально, как и ожидалось. Проблемы возникают только с размещенной базой данных DO. Похоже, настройки бд разные (но я не могу получить конфигурацию размещенной БД). Также в 99% подобных проблем просто установкаpool_pre_ping
исправляет проблему, и она исправила ее для меня также в той же настройке для размещенной базы данных DO, но когда вместо этого было приложение Flask.
Ответ №1:
Вы пытались добавить какие-либо connect_args в свой sqlalchemy create_engine? Эти аргументы должны позволять вам поддерживать соединение с вашей базой данных.
Вот список различных параметров подключения libpq, которые могут быть полезны.
create_engine(self.sqlalchemy_uri,
pool_pre_ping=True,
pool_recycle=3600, # this line might not be needed
connect_args={
"keepalives": 1,
"keepalives_idle": 30,
"keepalives_interval": 10,
"keepalives_count": 5,
}
)
Стоит отметить, что любой, кто использует psycopg, также может использовать эти параметры подключения libpq.
Ответ №2:
Это может быть несколько общий ответ, но эти шаги помогут локализовать проблему:
- Попробуйте подключиться через SSH, чтобы быть уверенным, что ваши запросы работают, а причина не указана в базе данных.
- Проверьте номера портов для подключений, чтобы убедиться, что они соответствуют настройкам uvicorn, Digital Ocean и скриптам python.
- Попробуйте временно разрешить доступ с любого IP — адреса-возможно, какая-то странная маршрутизация не позволит вам