Django не освобождает соединения с базой данных для повторного использования еще долгое время после завершения запроса
Мое приложение django загружает сразу несколько страниц (изображений), например, для отображения документа, и запрашивает базу данных в основном для проверки разрешений. Django поддерживает подключения, но не использует их повторно. В какой-то момент достигается максимальное количество подключений postgresql, и из-за такого поведения они никогда не будут "недоступны". Я добавил CONN_MAX_AGE
из 15 секунд и промежуточный pgbouncer
, но поведение такое же, соединения остаются открытыми даже после 15".
{
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": "pgbouncer","PORT": 6432,"NAME": "...", "USER": "...",
"PASSWORD": "...",
"CONN_MAX_AGE": 15, "CONN_HEALTH_CHECKS": true
}
}
В этом случае настройки указывают на прокси-сервер pgbouncer, но у меня также есть эта проблема без pgbouncer. Вот что я вижу. Я загрузил пару страниц на веб-сайт, и они заполнились pg_stat_activity
.
SELECT
split_part(query, 'WHERE', 2) AS sql,
COUNT(*) AS count
FROM pg_stat_activity
WHERE datname = '...'
GROUP BY query
sql | count |
---|---|
... "page"."page_id" = 12 ... | 1 |
... "page"."page_id" = 65 ... | 1 |
... "page"."page_id" = 23 ... | 1 |
... "page"."page_id" = 78 ... | 1 |
... "page"."page_id" = 32 ... | 1 |
... many more rows ... | ... |
Эти записи сохраняются с точно таким же значением page_id
в течение нескольких часов, даже если в консоли разработки веб-сайта нет трафика, а также когда веб-страница закрыта в браузере. В журналах nginx также нет трафика. Они освобождаются только при уничтожении django. В противном случае эти соединения остаются открытыми на неопределенный срок и не используются повторно, поскольку sql остается неизменным.
Я также пробовал "CONN_MAX_AGE": None
и "CONN_MAX_AGE": 0
, соединения остаются открытыми.
К вашему сведению, конфигурация pgbouncer:
[databases]
...= dbname=printedregistries_... host=... port=5432 user=... password=...
[pgbouncer]
auth_type=scram-sha-256
auth_file=/etc/pgbouncer/users.txt
pool_mode=session
listen_addr=*
listen_port=6432
verbose=3
# Connection limits
max_client_conn=100
default_pool_size=20
min_pool_size=5
reserve_pool_size=5
max_db_connections=50
max_user_connections=50
# Timeouts to prevent connection accumulation
server_idle_timeout=300
server_lifetime=3600
client_idle_timeout=300
client_login_timeout=60
# This is the SSL of the postgres server, not of whats served by pgbouncer.
server_tls_sslmode=require
При затоплении возможны два исхода. Либо query_wait_timeout
с указанными выше настройками, либо connection failed: connection to server at "...", port 6432 failed: ERROR: no more connections allowed (max_client_conn)
, если задано только max_client_conn
. То есть, pgbouncer пытается устранить отсутствие подключений, ожидая, но, конечно, это не будет иметь значения, если они никогда не освободятся.
Как я могу заставить django использовать соединения с базой данных "разумным" образом, например, как я могу избежать того, чтобы соединения оставались открытыми И "заблокированными", "использовались" или "были заняты", даже если запрос давно завершен?
Это я уже пробовал:
- пул загрузки на стороне клиента, загружает максимум 5 страниц одновременно. Не помогло, ограничение в конечном итоге все равно достигнуто.
- pgbouncer intermediate, не помог, pgbouncer также не использует соединения повторно, если они "используются" django.
- django
CONN_MAX_AGE
, кажется, ничего не делает.
После регистрации каждого вызова функции в DatabaseWrapper
Я все еще понятия не имею, почему django блокирует мои соединения, вопреки их документам. Эта проблема вывела меня из себя, и я попросил kilocode исправить ее для меня. Это закрывает базу данных после каждого запроса, проблема устранена, и я использую pgbouncer для объединения своих подключений. Кажется, работает хорошо.
import logging
from django.db import connections
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger('database-connection-cleanup')
class DatabaseConnectionMiddleware(MiddlewareMixin):
"""
Middleware to force database connection cleanup after each request.
This addresses Django runserver's connection management issues.
"""
def process_response(self, request, response):
"""Force close database connections after each request."""
try:
# Close all database connections
for conn in connections.all():
if conn.connection is not None:
logger.debug(f"Closing connection for database: {conn.alias}")
conn.close()
# Clear connection pool cache
connections._databases = {}
except Exception as e:
logger.warning(f"Error closing database connections: {e}")
return response
def process_exception(self, request, exception):
"""Ensure connections are closed even if an exception occurs."""
try:
for conn in connections.all():
if conn.connection is not None:
logger.debug(f"Closing connection after exception for database: {conn.alias}")
conn.close()
except Exception as e:
logger.error(f"Error closing database connections after exception: {e}")
return None
MIDDLEWARE = [
# ...
'pr.middleware.DatabaseConnectionMiddleware', # Force connection cleanup
]