Compare commits

...

4 Commits

Author SHA1 Message Date
Raphael Michel
cedd0c877e Reduce search to attendee names 2021-03-25 20:01:29 +01:00
Raphael Michel
c9cebd820a Safeguard against infinite quotas 2021-03-24 09:03:27 +01:00
Raphael Michel
e2a40aaa3a Do not update query cache at all 2021-03-23 20:34:48 +01:00
Raphael Michel
584ff16f58 Add a quota cache in redis 2021-03-23 19:53:32 +01:00
3 changed files with 61 additions and 31 deletions

View File

@@ -736,14 +736,7 @@ with scopes_disabled():
def search_qs(self, queryset, name, value):
return queryset.filter(
Q(secret__istartswith=value)
| Q(attendee_name_cached__icontains=value)
| Q(addon_to__attendee_name_cached__icontains=value)
| Q(attendee_email__icontains=value)
| Q(addon_to__attendee_email__icontains=value)
| Q(order__code__istartswith=value)
| Q(order__invoice_address__name_cached__icontains=value)
| Q(order__email__icontains=value)
Q(attendee_name_cached__icontains=value)
)
def has_checkin_qs(self, queryset, name, value):

View File

@@ -10,6 +10,7 @@ from django.db.models import (
)
from django.dispatch import receiver
from django.utils.timezone import now
from django_redis import get_redis_connection
from django_scopes import scopes_disabled
from pretix.base.models import (
@@ -89,7 +90,7 @@ class QuotaAvailability:
def queue(self, *quota):
self._queue += quota
def compute(self, now_dt=None):
def compute(self, now_dt=None, dbcache=True):
now_dt = now_dt or now()
quotas = list(set(self._queue))
quotas_original = list(self._queue)
@@ -106,18 +107,18 @@ class QuotaAvailability:
self._close(quotas)
try:
self._write_cache(quotas, now_dt)
self._write_cache(quotas, now_dt, dbcache)
except OperationalError as e:
# Ignore deadlocks when multiple threads try to write to the cache
if 'deadlock' not in str(e).lower():
raise e
def _write_cache(self, quotas, now_dt):
def _write_cache(self, quotas, now_dt, dbcache):
# We used to also delete item_quota_cache:* from the event cache here, but as the cache
# gets more complex, this does not seem worth it. The cache is only present for up to
# 5 seconds to prevent high peaks, and a 5-second delay in availability is usually
# tolerable
update = []
update = defaultdict(list)
for q in quotas:
rewrite_cache = self._count_waitinglist and (
not q.cache_is_hot(now_dt) or self.results[q][0] > q.cached_availability_state
@@ -129,12 +130,21 @@ class QuotaAvailability:
q.cached_availability_time = now_dt
if q in self.count_paid_orders:
q.cached_availability_paid_orders = self.count_paid_orders[q]
update.append(q)
update[q.event_id].append(q)
if update:
Quota.objects.using('default').bulk_update(update, [
'cached_availability_state', 'cached_availability_number', 'cached_availability_time',
'cached_availability_paid_orders'
], batch_size=50)
# if dbcache:
# Quota.objects.using('default').bulk_update(sum((quotas for event, quotas in update.items()), []), [
# 'cached_availability_state', 'cached_availability_number', 'cached_availability_time',
# 'cached_availability_paid_orders'
# ], batch_size=50)
if settings.HAS_REDIS:
rc = get_redis_connection("redis")
for eventid, quotas in update.items():
rc.hmset(f'quotas:{eventid}:availability', {
str(q.id): ",".join([str(i) for i in self.results[q]]) for q in quotas
})
def _close(self, quotas):
for q in quotas:

View File

@@ -14,6 +14,7 @@ from django.utils.timezone import get_current_timezone, now
from django.views import View
from django.views.decorators.cache import cache_page
from django.views.generic import ListView, TemplateView
from django_redis import get_redis_connection
from pytz import UTC
from pretix.base.i18n import language
@@ -366,35 +367,61 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_n
'date_from'
)
quotas_to_compute = []
quotas_by_event = defaultdict(list)
for se in qs:
if se.presale_is_running:
quotas_to_compute += [
q for q in se.active_quotas
if not q.cache_is_hot(now() + timedelta(seconds=5))
]
quotas_by_event[se.event_id] += [
q for q in se.active_quotas
if not q.cache_is_hot(now() + timedelta(seconds=5))
]
name = None
qcache = {}
if quotas_to_compute:
qa = QuotaAvailability()
qa.queue(*quotas_to_compute)
qa.compute()
for se in qs:
if settings.HAS_REDIS:
rc = get_redis_connection("redis")
for ev, quotas in quotas_by_event.items():
d = rc.hmget(f'quotas:{ev}:availability', [str(q.pk) for q in quotas])
for redisval, q in zip(d, quotas):
if redisval is not None and b',' in redisval:
parts = redisval.decode().strip().split(',')
if parts[0].isdigit() and parts[1] == "None":
qcache[q] = (int(parts[0]), None)
quotas_to_compute.remove(q)
else:
try:
qcache[q] = tuple(int(rv) for rv in parts)
quotas_to_compute.remove(q)
except ValueError:
pass
if quotas_to_compute:
se._quota_cache = qa.results
qa = QuotaAvailability()
qa.queue(*quotas_to_compute)
qa.compute(dbcache=False)
qcache.update(qa.results)
for se in qs:
if qcache:
se._quota_cache = qcache
kwargs = {'subevent': se.pk}
if cart_namespace:
kwargs['cart_namespace'] = cart_namespace
settings = event.settings if event else se.event.settings
timezones.add(settings.timezones)
tz = pytz.timezone(settings.timezone)
s = event.settings if event else se.event.settings
timezones.add(s.timezones)
tz = pytz.timezone(s.timezone)
datetime_from = se.date_from.astimezone(tz)
date_from = datetime_from.date()
if name is None:
name = str(se.name)
elif str(se.name) != name:
ebd['_subevents_different_names'] = True
if se.event.settings.show_date_to and se.date_to:
if s.show_date_to and se.date_to:
datetime_to = se.date_to.astimezone(tz)
date_to = se.date_to.astimezone(tz).date()
d = max(date_from, before.date())
@@ -402,13 +429,13 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_n
first = d == date_from
ebd[d].append({
'continued': not first,
'timezone': settings.timezone,
'time': datetime_from.time().replace(tzinfo=None) if first and settings.show_times else None,
'timezone': s.timezone,
'time': datetime_from.time().replace(tzinfo=None) if first and s.show_times else None,
'time_end': (
datetime_to.time().replace(tzinfo=None)
if (date_to == date_from or (
date_to == date_from + timedelta(days=1) and datetime_to.time() < datetime_from.time()
)) and settings.show_times
)) and s.show_times
else None
),
'event': se,
@@ -420,9 +447,9 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_n
ebd[date_from].append({
'event': se,
'continued': False,
'time': datetime_from.time().replace(tzinfo=None) if se.event.settings.show_times else None,
'time': datetime_from.time().replace(tzinfo=None) if s.show_times else None,
'url': eventreverse(se.event, 'presale:event.index', kwargs=kwargs),
'timezone': se.event.settings.timezone,
'timezone': s.timezone,
})