Постоянное подключение к базе данных Django

Я использую django с apache и mod_wsgi и PostgreSQL (все на одном хосте), и мне нужно обрабатывать множество простых динамических запросов страниц (сотни в секунду). Я столкнулся с проблемой, что узким местом является то, что django не имеет постоянного соединения с базой данных и повторно подключается к каждому запросу (что занимает около 5 мс). Выполняя бенчмарк, я получил это с постоянным подключением, я могу обрабатывать около 500 р/с, пока я не получаю только 50 р/с.

У кого-нибудь есть совет? Как изменить django для использования постоянного соединения? Или ускорить подключение от python к DB

Спасибо заранее.

Ответ 1

Django 1.6 добавил поддержку постоянных подключений (ссылка на doc для django 1.9):

Постоянные соединения избегают накладных расходов при восстановлении подключение к базе данных в каждом запросе. Они контролируются CONN_MAX_AGE, который определяет максимальное время жизни подключение. Он может быть установлен независимо для каждой базы данных.

Ответ 2

Попробуйте PgBouncer - легкий пул соединений для PostgreSQL. Особенности:

  • Несколько уровней жестокости при вращении соединений:
    • Объединение сеансов
    • Объединение транзакций
    • Составление отчетов
  • Низкие требования к памяти (по умолчанию 2k на соединение).

Ответ 3

В Django trunk отредактируйте django/db/__init__.py и закомментируйте строку:

signals.request_finished.connect(close_connection)

Этот обработчик сигнала заставляет его отключиться от базы данных после каждого запроса. Я не знаю, какие побочные эффекты будут делать это, но не имеет смысла начинать новое соединение после каждого запроса; он разрушает производительность, как вы заметили.

Я использую это сейчас, но я не выполнил полный набор тестов, чтобы увидеть, что-нибудь сломается.

Я не знаю, почему все думают, что для этого нужен новый бэкэнд или специальный пул соединений или другие сложные решения. Это кажется очень простым, хотя я не сомневаюсь, что есть некоторые неясные ошибки, которые заставляют их делать это в первую очередь - с этим нужно бороться более разумно; Как вы заметили, накладные расходы 5 мс для каждого запроса довольно много для высокопроизводительного сервиса. (Мне требуется 150 мс - я пока не понял почему.)

Изменить: другое необходимое изменение - в django/middleware/transaction.py; удалите два теста transaction.is_dirty() и всегда вызывайте commit() или rollback(). В противном случае он не будет совершать транзакцию, если она только считывает из базы данных, которая будет закрывать блокировки, которые должны быть закрыты.

Ответ 4

Я создал небольшой Django patch, который реализует объединение пулов MySQL и PostgreSQL с помощью объединения sqlalchemy.

Это отлично работает при создании http://grandcapital.net/ в течение длительного периода времени.

Патч был написан после немного поглаживания темы.

Ответ 5

Отказ от ответственности: я не пробовал это.

Я считаю, что вам нужно реализовать пользовательский конец базы данных. В Интернете есть несколько примеров, показывающих, как реализовать конец базы данных с пулом соединений.

Использование пула соединений, вероятно, будет хорошим решением для вас, поскольку сетевые подключения сохраняются открытыми, когда соединения возвращаются в пул.

  • Этот пост достигает этого, исправляя Django (один из комментариев указывает, что лучше реализовать пользовательский задний конец за пределами core django code)
  • Этот пост представляет собой реализацию пользовательского db back end

Оба сообщения используют MySQL - возможно, вы можете использовать похожие методы с Postgresql.

Edit:

  • В книге Django упоминается пул соединений Postgresql, используя pgpool (tutorial).
  • Кто-то отправил патч для бэкэнд psycopg2, который реализует объединение пулов. Я предлагаю создать копию существующего конца в вашем собственном проекте и исправить это.

Ответ 6

Я создал небольшой пользовательский интерфейс psycopg2, который реализует постоянное соединение с использованием глобальной переменной. Благодаря этому я смог улучшить количество запросов в секунду с 350 до 1600 (на очень простой странице с несколькими выборами) Просто сохраните его в файле с именем base.py в любом каталоге (например, postgresql_psycopg2_persistent) и установите в настройках

DATABASE_ENGINE для projectname.postgresql_psycopg2_persistent

Внимание!!! код не является потокобезопасным - вы не можете использовать его с потоками python из-за непредсказуемых результатов, в случае mod_wsgi используйте режим демона prefork с потоками = 1


# Custom DB backend postgresql_psycopg2 based
# implements persistent database connection using global variable

from django.db.backends.postgresql_psycopg2.base import DatabaseError, DatabaseWrapper as BaseDatabaseWrapper, \
    IntegrityError
from psycopg2 import OperationalError

connection = None

class DatabaseWrapper(BaseDatabaseWrapper):
    def _cursor(self, *args, **kwargs):
        global connection
        if connection is not None and self.connection is None:
            try: # Check if connection is alive
                connection.cursor().execute('SELECT 1')
            except OperationalError: # The connection is not working, need reconnect
                connection = None
            else:
                self.connection = connection
        cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs)
        if connection is None and self.connection is not None:
            connection = self.connection
        return cursor

    def close(self):
        if self.connection is not None:
            self.connection.commit()
            self.connection = None

Или вот потокобезопасный, но потоки python не используют несколько ядер, поэтому вы не получите такого повышения производительности, как в предыдущем. Вы можете использовать этот вариант с несколькими процессами.

# Custom DB backend postgresql_psycopg2 based
# implements persistent database connection using thread local storage
from threading import local

from django.db.backends.postgresql_psycopg2.base import DatabaseError, \
    DatabaseWrapper as BaseDatabaseWrapper, IntegrityError
from psycopg2 import OperationalError

threadlocal = local()

class DatabaseWrapper(BaseDatabaseWrapper):
    def _cursor(self, *args, **kwargs):
        if hasattr(threadlocal, 'connection') and threadlocal.connection is \
            not None and self.connection is None:
            try: # Check if connection is alive
                threadlocal.connection.cursor().execute('SELECT 1')
            except OperationalError: # The connection is not working, need reconnect
                threadlocal.connection = None
            else:
                self.connection = threadlocal.connection
        cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs)
        if (not hasattr(threadlocal, 'connection') or threadlocal.connection \
             is None) and self.connection is not None:
            threadlocal.connection = self.connection
        return cursor

    def close(self):
        if self.connection is not None:
            self.connection.commit()
            self.connection = None