Подробнее о проекте:
- Фреймворк: FastAPI (v0.104.0)
- Веб-сервер: Uvicorn (v0.23.0) с 24 работниками
- Библиотека базы данных: SQLAlchemy (v2.0.21)
- База данных: PostgreSQL (через Docker)
Код: Выделить всё
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. Кроме того, я использую сеанс с ограниченной областью действия, поэтому для каждого потока поддерживается один сеанс. Однако проблема остается.
Я перепробовал все, что мог придумать, в том числе обратился за помощью к инструментам искусственного интеллекта. К сожалению, ничего не помогло.
Дополнительная информация:
Я запустил клон своего приложения без обработки запросов, и вот статистика соединений. примерно через 14 часов:
Код: Выделить всё
p_db=# SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;
state | count
--------+-------
| 5
active | 1
idle | 24
(3 rows)
Однако в моем основном приложении, где единственное соединение, связанное с PostgreSQL, операция — вход в систему, вот статистика:
Код: Выделить всё
p_db=# SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;
state | count
--------+-------
| 5
active | 1
idle | 89
Итак, количество простаивающих подключений достигло максимума , и теперь я понимаю следующее:
Я говорил это раньше и скажу еще раз — тем не менее, из-за этой ошибки мой вход в систему (ну, повторная попытка) работает. Как?
(psycopg2.OperationalError) подключение к серверу в «postgres», порт 5432 не удалось: FATAL: извините, уже слишком много клиентов
Я даже отправляю 1000 запросов к клону своего приложения, но количество простаивающих соединений остается на уровне 24!
Обновить
я даже удалю пул соединений и ничего не меняется
Я не знаю, как я могу изолировать эту проблему больше, чем это!
Результатом этого запроса была та же длина, что и у моего неактивного соединения, но вся строка была то же самое, поэтому я просто скопирую сюда несколько из них!
Код: Выделить всё
p_db=# SELECT pid, state, query, backend_start, query_start, client_addr
FROM pg_stat_activity
WHERE state = 'idle';
pid | state | query | backend_start | query_start | client_addr
-----+-------+----------+-------------------------------+-------------------------------+-------------
35 | idle | ROLLBACK | 2025-01-17 10:08:44.755878+00 | 2025-01-17 10:31:27.250991+00 | 172.30.30.6
36 | idle | ROLLBACK | 2025-01-17 10:08:44.77771+00 | 2025-01-17 10:37:32.826234+00 | 172.30.30.6
37 | idle | ROLLBACK | 2025-01-17 10:08:45.007471+00 | 2025-01-17 10:38:24.326404+00 | 172.30.30.6
38 | idle | ROLLBACK | 2025-01-17 10:08:45.064301+00 | 2025-01-17 10:22:07.79541+00 | 172.30.30.6
39 | idle | ROLLBACK | 2025-01-17 10:08:45.139814+00 | 2025-01-17 10:22:04.841665+00 | 172.30.30.6
40 | idle | ROLLBACK | 2025-01-17 10:08:45.146128+00 | 2025-01-17 10:37:32.806969+00 | 172.30.30.6
41 | idle | ROLLBACK | 2025-01-17 10:08:45.190972+00 | 2025-01-17 10:37:30.432942+00 | 172.30.30.6
44 | idle | ROLLBACK | 2025-01-17 10:08:45.206284+00 | 2025-01-17 10:37:30.130094+00 | 172.30.30.6
42 | idle | ROLLBACK | 2025-01-17 10:08:45.205131+00 | 2025-01-17 10:37:32.81468+00 | 172.30.30.6
Подробнее здесь: https://stackoverflow.com/questions/793 ... postgresql
Мобильная версия