FastAPI — (psycopg2.OperationalError) сервер неожиданно закрыл соединение

#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:

Это может быть несколько общий ответ, но эти шаги помогут локализовать проблему:

  1. Попробуйте подключиться через SSH, чтобы быть уверенным, что ваши запросы работают, а причина не указана в базе данных.
  2. Проверьте номера портов для подключений, чтобы убедиться, что они соответствуют настройкам uvicorn, Digital Ocean и скриптам python.
  3. Попробуйте временно разрешить доступ с любого IP — адреса-возможно, какая-то странная маршрутизация не позволит вам