Do casual reads only on Galera

This commit is contained in:
Raphael Michel
2017-03-08 18:15:39 +01:00
parent 6bbdbddfaa
commit f9646d9325
3 changed files with 43 additions and 69 deletions

View File

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

View File

@@ -1,7 +1,6 @@
import contextlib import contextlib
from django.conf import settings from django.db import transaction
from django.db import connection, transaction
class DummyRollbackException(Exception): class DummyRollbackException(Exception):
@@ -29,38 +28,9 @@ def rolledback_transaction():
raise Exception('Invalid state, should have rolled back.') 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():
@contextlib.contextmanager """
def casual_reads(): Kept for backwards compatibility.
""" """
When pretix runs with a MySQL galera cluster as a database backend, we can run into the yield
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

@@ -50,6 +50,14 @@ debug_fallback = "runserver" in sys.argv
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback) DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
db_backend = config.get('database', 'backend', fallback='sqlite3') db_backend = config.get('database', 'backend', fallback='sqlite3')
DATABASE_IS_GALERA = config.getboolean('database', 'galera', fallback=False)
if DATABASE_IS_GALERA and 'mysql' in db_backend:
db_options = {
'init_command': 'SET SESSION wsrep_sync_wait = 1;'
}
else:
db_options = {}
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.' + db_backend, 'ENGINE': 'django.db.backends.' + db_backend,
@@ -58,10 +66,10 @@ DATABASES = {
'PASSWORD': config.get('database', 'password', fallback=''), 'PASSWORD': config.get('database', 'password', fallback=''),
'HOST': config.get('database', 'host', fallback=''), 'HOST': config.get('database', 'host', fallback=''),
'PORT': config.get('database', 'port', fallback=''), 'PORT': config.get('database', 'port', fallback=''),
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120 'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
'OPTIONS': db_options
} }
} }
DATABASE_IS_GALERA = config.getboolean('database', 'galera', fallback=False)
STATIC_URL = config.get('urls', 'static', fallback='/static/') STATIC_URL = config.get('urls', 'static', fallback='/static/')