Django не переподключается, когда PostgreSQL умирает, нужен специальный бэкэнд?

Я провел некоторое тестирование и смог подтвердить, что с помощью Django с PostgreSQL а также PGBouncer что он автоматически не восстанавливает соединение при потере соединения. Честно говоря, я не уверен, является ли это ошибкой или это сделано специально. Если это ошибка, я с радостью сообщу об этом, если нет, мне хотелось бы получить объяснение, почему и как обойти это, кроме другого пользовательского бэкенда.

Я сделал эти тесты довольно легко, выполнив следующее на Django 1.8.4 и на Django 1.8.6:

>>>Model.objects.all().count()
24
# Restart postgres with `sudo service postgres restart`
>>>Model.objects.all().count()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 318, in count
    return self.query.get_count(using=self.db)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 466, in get_count
    number = obj.get_aggregation(using, ['__count'])['__count']
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 447, in get_aggregation
    result = compiler.execute_sql(SINGLE)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 840, in execute_sql
    cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 98, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
OperationalError: server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.

Неважно, как долго я жду после перезагрузки PostgreSQL чтобы выполнить запрос, я все равно получаю те же результаты. Я вижу это в Django есть код для запуска Select 1 проверить соединение, но это не похоже на работу. Вернуться в Django 1.5.x мы написали пользовательский сервер PostgreSQL, чтобы сделать то же самое, но удалили его, так как казалось, Django это было встроено, когда мы обновили. Это ошибка?

Редактировать 06.11.2015: В Django серверная часть PostgreSQL реализует is_usable функция, которая делает SELECT 1 в базе данных. Тем не менее, я не могу найти какие-либо использования is_usable кроме как в close_if_unusable_or_obsolete но я нигде не мог найти применения этого. Я думаю, что он будет использоваться в какой-либо оболочке базы данных, которая перехватывает исключения и повторяет / реконнектится на основе is_usable,

Код, упомянутый выше, находится по адресу django/db/backends/postgresql_psychopg2/base.py в Джанго 1.8.

Edit 2 06.11.2015: Хорошо, я написал свою собственную оболочку для базы данных и просто переопределил ensure_connection метод, чтобы попытаться повторно подключиться к базе данных, когда соединение потеряно. Тем не менее, при первой попытке попасть в базу данных для сеанса я получаю этот другой грубый след. Но если я немедленно запросить снова, это работает. Если я заверну то, что я делаю в try/except: pass Блокировать все это, кажется, работает нормально, но не могу сказать, если это вызовет проблемы в дальнейшем. Вот трассировка и код.

>>> c = Model.objects.all().count()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 318, in count
    return self.query.get_count(using=self.db)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 466, in get_count
    number = obj.get_aggregation(using, ['__count'])['__count']
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 447, in get_aggregation
    result = compiler.execute_sql(SINGLE)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 838, in execute_sql
    cursor = self.connection.cursor()
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 164, in cursor
    cursor = self.make_cursor(self._cursor())
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 135, in _cursor
    self.ensure_connection()
  File "/usr/lib/python2.7/dist-packages/custom/db/backends/postgresql_psycopg2/base.py", line 73, in ensure_connection
    self._reconnect()
  File "/usr/lib/python2.7/dist-packages/custom/db/backends/postgresql_psycopg2/base.py", line 63, in _reconnect
    self.connect()
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 120, in connect
    self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 295, in set_autocommit
    self._set_autocommit(autocommit)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/postgresql_psycopg2/base.py", line 218, in _set_autocommit
    self.connection.autocommit = autocommit
  File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 97, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/postgresql_psycopg2/base.py", line 218, in _set_autocommit
    self.connection.autocommit = autocommit
ProgrammingError: autocommit cannot be used inside a transaction

Теперь для кода:

from django.db.backends.postgresql_psycopg2.base import DatabaseError, \
    IntegrityError, DatabaseWrapper as PostgresWrapper

class DatabaseWrapper(PostgresWrapper):
    def _reconnect(self):
        try:
            self.connect()
        except (DatabaseError, OperationalError):
            pass

    def ensure_connection(self):
        """
        Guarantees that a connection to the database is established.
        """
        if self.connection is None:
            with self.wrap_database_errors:
                self._reconnect()
        else:
            try:
                self.connection.cursor().execute('SELECT 1')
            except (DatabaseError, OperationalError):
                self._reconnect()

1 ответ

Решение

Я думаю, я понял это... наконец-то. Не совсем уверен, что не так с моим первым подходом, но этот, кажется, работает намного лучше.

class DatabaseWrapper(PostgresWrapper):
    def _cursor(self):
        if self.connection is not None:
            if not self.is_usable():
                self.connection.close()
                self.connection = None
        return super(DatabaseWrapper, self)._cursor()

Редактировать: закончилась открытым источником этого. Я не уверен, что он необходим на 100%, но он работает правильно после перезапуска службы Postgres на сервере. Вы можете найти его на Pypi как django-postgreconnectи на GitHub: https://github.com/mackeyja92/django-postgreconnect.

Другие вопросы по тегам