mirror of
https://github.com/pretix/pretix.git
synced 2025-12-05 21:32:28 +00:00
MySQL Galera workaround (#416)
This commit is contained in:
@@ -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
|
||||
----
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user