В настоящее время я столкнулся с проблемой утечки соединения в моем проекте FastAPI. Соединения не закрываются должным образом и остаются в состоянии ожидания. Несмотря на мои попытки тщательно управлять подключениями, количество простаивающих подключений продолжает расти, что в конечном итоге приводит к проблемам при достижении максимального предела подключений.
Подробности проекта:
Framework: FastAPI (v0.104.0)
Веб-сервер: Uvicorn (v0.23.0) с 24 работниками
Библиотека базы данных: SQLAlchemy (v2.0.21)
База данных: PostgreSQL (через Docker)
Конфигурация PostgreSQL в docker-compose.yml:
Код: Выделить всё
postgres:
image: postgres:16
container_name: postgres_db
ports:
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 300s
timeout: 5s
retries: 3
Код: Выделить всё
DATABASE_URL = os.environ.get("DATABASE_URL")
engine = create_engine(
DATABASE_URL,
pool_size=24, # Number of connections in the pool
max_overflow=12, # Additional connections allowed beyond the pool size
pool_timeout=30, # Wait time for a connection before timeout
pool_recycle=1800, # Recycle connections after 1800 seconds (30 minutes)
pool_pre_ping=True, # Ensure connections are valid before using
)
Код: Выделить всё
class DatabaseManager:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super(DatabaseManager, cls).__new__(cls)
cls._instance.engine = engine
cls._instance.session_maker = scoped_session(
sessionmaker(autocommit=False, autoflush=False, bind=cls._instance.engine)
)
return cls._instance
def get_session(self):
session = self._instance.session_maker()
try:
yield session
finally:
session.close()
self._instance.session_maker.remove()
Код: Выделить всё
@user_router.post("/login")
def login_user(
request: Request,
login_data: LoginRequest,
usermanager=Depends(UserManager),
db_session: Session = Depends(DatabaseManager().get_session),
):
return usermanager.login_user(request, login_data, db_session)
Код: Выделить всё
def login_user(self, request, login_data, db_session: Session):
try:
user = self.authenticate_user(request, login_data, db_session)
if not user:
raise HTTPException(status_code=401, detail="Incorrect username or password")
if user.is_active:
tokens = self.create_tokens_for_user(user)
return tokens
else:
raise HTTPException(status_code=401, detail="User is inactive")
except HTTPException as e:
self.log_exception("login", e.status_code, e.detail, traceback.format_exc())
raise e
except Exception as e:
try:
self.log_exception("login retry", 500, str(e), traceback.format_exc())
db_session = next(DatabaseManager().get_session())
user = self.authenticate_user(request, login_data, db_session)
if user and user.is_active:
return self.create_tokens_for_user(user)
finally:
db_session.close()
raise HTTPException(status_code=500, detail="An error occurred while logging in")
Я отслеживаю соединения PostgreSQL с помощью следующего запроса внутри контейнера:
Код: Выделить всё
SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;
Код: Выделить всё
state | count
-------+-------
| 5
active | 1
idle | 73
Хотя я ожидаю максимум 24 простаивающих соединения (из-за размера пула), это число продолжает расти и достигает максимума лимит подключений.
Даже до того, как количество простаивающих подключений достигнет максимума,
иногда я получаю эту ошибку
Код: Выделить всё
sqlalchemy.exc.InvalidRequestError: This session is provisioning a new connection;
concurrent operations are not permitted
Код: Выделить всё
psycopg2.OperationalError: connection to server at "postgres", port 5432 failed:
FATAL: sorry, too many clients already
Даже когда количество простаивающих соединений достигает максимума, логика повтора в функции login_user, похоже, работает правильно.
/>Я гарантирую, что все сеансы закрываются правильно (например, с помощью наконец), если я не использую внедрение зависимостей FastAPI. Кроме того, я использую сеанс с ограниченной областью действия, поэтому для каждого потока поддерживается один сеанс. Однако проблема остается.
Я застрял в этом вопросе уже неделю и перепробовал все, что мог придумать, в том числе обратился за помощью ко всем инструментам искусственного интеллекта, которые смог найти. К сожалению, ничего не помогло. Вот почему я пришел сюда, надеясь на помощь неискусственного интеллекта. Будем очень признательны за любые советы или идеи по решению этой проблемы. Спасибо!
Подробнее здесь: https://stackoverflow.com/questions/793 ... postgresql
Мобильная версия