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
]
Вернуться на верх