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.