Вопрос по database, python, django, mod-wsgi, persistent – Постоянное соединение с базой данных Django

53

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

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

Заранее спасибо.

Ваш Ответ

6   ответов
0

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

DATABASE_ENGINE для имя_проекта.postgresql_psycopg2_persistent

NOTE!!! the code is not threadsafe - you can't use it with python threads because of unexpectable results, in case of mod_wsgi please use prefork daemon mode with threads=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
Пожалуйста, не делайте этого. Это совершенно небезопасно. Используйте правильный пул соединений как pgpool.
Pgpool не поможет, потому что django все равно нужно каждый раз переподключаться. Я знаю, что это не потокобезопасный код (и у меня есть потокобезопасная версия, которая использует модуль psycopg2.pool, только еще не опубликованный), но я использую python с mod_wsgi и в режиме Daemon без потоков, но с чистым prefork, так что это здесь безопасно Я добавлю примечание - спасибо. HardQuestions
используйте pgbouncer локально, тогда всякий раз, когда django подключается, pgbouncer будет использовать существующее соединение из пула. так как у вас будет локальное соединение с pgbouncer, не будет большого штрафа за соединение
22

ПытатьсяPgBouncer - облегченный пул соединений для PostgreSQL. Особенности:

Several levels of brutality when rotating connections: Session pooling Transaction pooling Statement pooling Low memory requirements (2k per connection by default).
+1. Это то, что они используют в Disqus (на сегодняшний день проект Django с самой большой базой пользователей). Я считаю это испытанием на огнестойкость.
@Vasil Спасибо, я попробую. HardQuestions
То же, что и в pgpool, он не устраняет огромные издержки при каждом соединении запроса. Код открытия соединения является реальным узким местом. HardQuestions
@Mike TK Во-первых, это не то же самое, что pgpool. Pgbouncer работает с libevent и асинхронно управляет соединениями, а не pgpool, разветвляя процесс для каждого соединения, как это делает сам Postgres (единственное отличие состоит в том, что pgpool поддерживает процессы). Из моего опыта использование pgbouncer (по сравнению с тем, чтобы вообще не использовать какой-либо метод) дает заметное ускорение.
3

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

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

This post accomplishes this by patching Django (one of the comments points out that it is better to implement a custom back end outside of the core django code) This post is an implementation of a custom db back end

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

Edit:

The Django Book mentions Postgresql connection pooling, using pgpool (tutorial). Someone posted a patch for the psycopg2 backend that implements connection pooling. I suggest creating a copy of the existing back end in your own project and patching that one.
Я пробовал pgpool, но ситуация не сильно улучшилась (мне все равно нужно каждый раз переподключаться). PgPool предназначен для немного других целей (аварийное переключение, репликация и т. Д.). HardQuestions
15

Джанго патч это реализует пул соединений MySQL и PostgreSQL через пул sqlalchemy.

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

Патч был написан после того, как немного погуглил тему.

+1 Огромное спасибо Игорю, я использовал ваш патч и он работал без нареканий. Я нашел еще одну ссылку на тот же подход вmenendez.com/blog/… но твое было намного проще заставить работать.
20

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

signals.request_finished.connect(close_connection)

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

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

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

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

Увидетьgroups.google.com/group/django-users/browse_thread/thread/… django-postgres-persistent-db-connection.diff для более общей реализации этого, но он реализован только для Postgresql. (Не то, чтобы OP даже слушал, но для любого другого, кто находит свой путь здесь ...)
Одной из причин, по которой люди предлагают новые бэкэнды, является переносимость кода; каждое обновление означает, что вам придется снова редактировать код или синхронизировать ветку, если вы начинаете комментировать базу кода django.
Я проверил исходный код в django 2.0 и обнаружил, что django закрывает только те подключения, которые истекли. Возможно, все по-другому. Ваш пример кода больше не существует, а новый код - "signal.request_finished.connect (close_old_connections) & quot;"
28

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

Persistent connections avoid the overhead of re-establishing a connection to the database in each request. They’re controlled by the CONN_MAX_AGE parameter which defines the maximum lifetime of a connection. It can be set independently for each database.

Вот (первый) коммит для этого в ветке 1.6:github.com/django/django/commit/…

Похожие вопросы