hotamul의 개발 이야기

[Flask] (psycopg2.OperationalError) server closed the connection unexpectedly. This probably means the server terminated abnormally before or while processing the request. 본문

Dev./Flask

[Flask] (psycopg2.OperationalError) server closed the connection unexpectedly. This probably means the server terminated abnormally before or while processing the request.

hotamul 2022. 8. 12. 20:24

Flask를 사용해서 API 서버를 구축하게 되었는데 local에서는 발생하지 않았다가 운영 서버에 배포하고 나서부터 발생했던 psycopg2.OperationError 해결 방법을 공유하고자 한다.

psycopg2.OperationError

File "/usr/local/lib/python3.8.9/site-packages/sqlalchemy/sql/elements.py", line 287, in _execute_on_connection
    Return connection._execute_clauseelement(self, multiparams, params)
  File "/usr/local/lib/python3.8.9/site-packages/sqlalchemy/engine/base.py", line 1107, in _execute_clauseelement
    Distilled_params,
  File "/usr/local/lib/python3.8.9/site-packages/sqlalchemy/engine/base.py", line 1248, in _execute_context
    e, statement, parameters, cursor, context
  File "/usr/local/lib/python3.8.9/site-packages/sqlalchemy/engine/base.py", line 1466, in _handle_dbapi_exception
    Util.raise_from_cause(sqlalchemy_exception, exc_info)
  File "/usr/local/lib/python3.8.9/site-packages/sqlalchemy/util/compat.py", line 383, in raise_from_cause
    Reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/usr/local/lib/python3.8.9/site-packages/sqlalchemy/engine/base.py", line 1244, in _execute_context
    Cursor, statement, parameters, context
  File "/usr/local/lib/python3.8.9/site-packages/sqlalchemy/engine/default.py", line 552, in do_execute
    Cursor.execute(statement, parameters)
OperationalError: (psycopg2.OperationalError) server closed the connection unexpectedly
    This probably means the server terminated abnormally
    before or while processing the request.

배포를 마치고 퇴근한 뒤 아침에 서버에서 잘 실행되고 있나 확인하기 위해서 GET, POST 등등 여러 request를 실행해보니 Internal Server Error가 발생하는 것을 확인했다.


그래서 해당 운영서버에 로그를 뒤져보니 위와 같은 에러가 발생한 것을 확인할 수 있었다.


뭐가 잘못된 걸까하고 SQLAlchemy의 연결이 언제 끊어지는지 찾아보던 중 SQLAlchemy의 연결 풀링 이해하기 해당 글을 보게 되었고 해결책을 찾을 수 있었다.


문제는 SQLAlchemyQueuePool 생애주기

큐 풀이 처음부터 연결을 미리 만드는 것은 아닙니다. 일단 0개로 시작합니다.


오랜 시간 동안 요청이 없게 되면 SQLAlchemy QueuePool이 연결이 없어지게 되고 새로 request가 발생하면 첫 번째 request에서는 위와 같은 psycopg2.OperationError가 발생하게 되는 것이었다.


해결방법은 여러가지가 있을 수 있겠지만 (정책적으로 Connection refresh 주기 설정, Connection Pool minimun 설정 등등...) 해당 문제는 서버 운영에 Critical한 문제라고 생각해 조금은 보수적인 방법을 선택했다. (SQLAlchemy 공식 문서에서는 Pessimistic하다고 하던데 같은 의미인지는 잘 모르겠다.)

(참고: SQLAlchemy - disconnect handling) SQLAlchemy engine_option에서 pool_pre_ping 옵션을 True로 설정하면 문제를 해결할 수 있다.


"Pre ping" 기능은 일반적으로 풀에서 연결이 체크아웃될 때마다 "SELECT 1"에 해당하는 SQL을 내보냅니다. "연결 해제" 상황으로 감지된 오류가 발생하면 연결이 즉시 재활용되고 현재 시간보다 오래된 다른 풀링된 연결은 모두 무효화되어 다음에 체크아웃할 때도 재활용됩니다. 사용하기 전에.


(참고: Flask-SQLAlchemy Configuration) SQLALCHEMY_ENGINE_OPTIONS을 아래와 같이 key value 값으로 설정해 주면 된다.

class Config:
    ...
    SQLALCHEMY_ENGINE_OPTIONS = {
        "pool_pre_ping": True
    }
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    ...
}
Comments