MySQL Galera workaround (#416)

This commit is contained in:
Raphael Michel
2017-02-22 16:59:23 +01:00
committed by GitHub
parent 09020143e7
commit 08e7a29623
4 changed files with 74 additions and 27 deletions

View File

@@ -102,6 +102,10 @@ Example::
``user``, ``password``, ``host``, ``port``
Connection details for the database connection. Empty by default.
``galera``
Indicates if the database backend is a MySQL/MariaDB Galera cluster and
turns on some optimizations/special case handlers. Default: ``False``
URLs
----

View File

@@ -9,6 +9,7 @@ from django.shortcuts import redirect, render
from django.utils.translation import ugettext as _
from pretix.celery_app import app
from pretix.helpers.database import casual_reads
logger = logging.getLogger('pretix.base.async')
@@ -31,10 +32,11 @@ class AsyncAction:
return JsonResponse(data)
else:
if res.ready():
if res.successful() and not isinstance(res.info, Exception):
return self.success(res.info)
else:
return self.error(res.info)
with casual_reads():
if res.successful() and not isinstance(res.info, Exception):
return self.success(res.info)
else:
return self.error(res.info)
return redirect(self.get_check_url(res.id, False))
def get_success_url(self, value):
@@ -64,24 +66,25 @@ class AsyncAction:
'ready': ready
}
if ready:
if res.successful() and not isinstance(res.info, Exception):
smes = self.get_success_message(res.info)
if smes:
messages.success(self.request, smes)
# TODO: Do not store message if the ajax client states that it will not redirect
# but handle the mssage itself
data.update({
'redirect': self.get_success_url(res.info),
'message': str(self.get_success_message(res.info))
})
else:
messages.error(self.request, self.get_error_message(res.info))
# TODO: Do not store message if the ajax client states that it will not redirect
# but handle the mssage itself
data.update({
'redirect': self.get_error_url(),
'message': str(self.get_error_message(res.info))
})
with casual_reads():
if res.successful() and not isinstance(res.info, Exception):
smes = self.get_success_message(res.info)
if smes:
messages.success(self.request, smes)
# TODO: Do not store message if the ajax client states that it will not redirect
# but handle the mssage itself
data.update({
'redirect': self.get_success_url(res.info),
'message': str(self.get_success_message(res.info))
})
else:
messages.error(self.request, self.get_error_message(res.info))
# TODO: Do not store message if the ajax client states that it will not redirect
# but handle the mssage itself
data.update({
'redirect': self.get_error_url(),
'message': str(self.get_error_message(res.info))
})
return data
def get_result(self, request):
@@ -90,10 +93,11 @@ class AsyncAction:
return JsonResponse(self._return_ajax_result(res, timeout=0.25))
else:
if res.ready():
if res.successful() and not isinstance(res.info, Exception):
return self.success(res.info)
else:
return self.error(res.info)
with casual_reads():
if res.successful() and not isinstance(res.info, Exception):
return self.success(res.info)
else:
return self.error(res.info)
return render(request, 'pretixpresale/waiting.html')
def success(self, value):

View File

@@ -1,6 +1,7 @@
import contextlib
from django.db import transaction
from django.conf import settings
from django.db import connection, transaction
class DummyRollbackException(Exception):
@@ -26,3 +27,40 @@ def rolledback_transaction():
pass
else:
raise Exception('Invalid state, should have rolled back.')
if 'mysql' in settings.DATABASES['default']['ENGINE'] and settings.DATABASE_IS_GALERA:
@contextlib.contextmanager
def casual_reads():
"""
When pretix runs with a MySQL galera cluster as a database backend, we can run into the
following problem:
* A celery thread starts a transaction, creates an object and commits the transaction.
It then returns the object ID into celery's result store (e.g. redis)
* A web thread pulls the object ID from the result store, but cannot access the object
yet as the transaction is not yet committed everywhere.
This sets the wsrep_sync_wait variable to deal with this problem.
See also:
* https://mariadb.com/kb/en/mariadb/galera-cluster-system-variables/#wsrep_sync_wait
* https://www.percona.com/doc/percona-xtradb-cluster/5.6/wsrep-system-index.html#wsrep_sync_wait
"""
with connection.cursor() as cursor:
cursor.execute("SET @wsrep_sync_wait_orig = @@wsrep_sync_wait;")
cursor.execute("SET SESSION wsrep_sync_wait = GREATEST(@wsrep_sync_wait_orig, 1);")
try:
yield
finally:
cursor.execute("SET SESSION wsrep_sync_wait = @wsrep_sync_wait_orig;")
else:
@contextlib.contextmanager
def casual_reads():
yield

View File

@@ -58,6 +58,7 @@ DATABASES = {
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120
}
}
DATABASE_IS_GALERA = config.getboolean('database', 'galera', fallback=False)
STATIC_URL = config.get('urls', 'static', fallback='/static/')