per_request_cache

This commit is contained in:
Laura Klünder 2024-12-27 18:50:36 +01:00
parent 06135cedfd
commit 87b7f00740
6 changed files with 56 additions and 21 deletions

View file

@ -1,6 +1,7 @@
import re
from functools import wraps
from c3nav.mapdata.utils.cache.local import per_request_cache
from c3nav.mapdata.utils.user import get_user_data_lazy
@ -55,3 +56,15 @@ class UserDataMiddleware:
def __call__(self, request):
request.user_data = get_user_data_lazy(request)
return self.get_response(request)
class RequestCacheMiddleware:
"""
Resets the request_cache at the start of every request.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
per_request_cache.clear()
return self.get_response(request)

View file

@ -14,6 +14,7 @@ from django.utils.translation import ngettext_lazy
from c3nav.mapdata.models import MapUpdate
from c3nav.mapdata.models.base import SerializableMixin, TitledMixin
from c3nav.mapdata.utils.cache.local import per_request_cache
class AccessRestriction(TitledMixin, models.Model):
@ -38,20 +39,20 @@ class AccessRestriction(TitledMixin, models.Model):
@staticmethod
def get_all() -> set[int]:
cache_key = 'all_access_restrictions:%s' % MapUpdate.current_cache_key()
access_restriction_ids = cache.get(cache_key, None)
access_restriction_ids = per_request_cache.get(cache_key, None)
if access_restriction_ids is None:
access_restriction_ids = set(AccessRestriction.objects.values_list('pk', flat=True))
cache.set(cache_key, access_restriction_ids, 300)
per_request_cache.set(cache_key, access_restriction_ids, 300)
return access_restriction_ids
@staticmethod
def get_all_public() -> set[int]:
cache_key = 'public_access_restrictions:%s' % MapUpdate.current_cache_key()
access_restriction_ids = cache.get(cache_key, None)
access_restriction_ids = per_request_cache.get(cache_key, None)
if access_restriction_ids is None:
access_restriction_ids = set(AccessRestriction.objects.filter(public=True)
.values_list('pk', flat=True))
cache.set(cache_key, access_restriction_ids, 300)
per_request_cache.set(cache_key, access_restriction_ids, 300)
return access_restriction_ids
@ -321,14 +322,14 @@ class AccessPermission(models.Model):
return AccessRestriction.get_all()
cache_key = cls.request_access_permission_key(request)+f':{can_grant}'
access_restriction_ids = cache.get(cache_key, None)
access_restriction_ids = per_request_cache.get(cache_key, None)
if access_restriction_ids is None:
permissions = cls.get_for_request_with_expire_date(request, can_grant=can_grant)
access_restriction_ids = set(permissions.keys())
expire_date = min((e for e in permissions.values() if e), default=timezone.now() + timedelta(seconds=120))
cache.set(cache_key, access_restriction_ids, min(300, (expire_date - timezone.now()).total_seconds()))
per_request_cache.set(cache_key, access_restriction_ids, min(300, (expire_date - timezone.now()).total_seconds()))
return set(access_restriction_ids) | (set() if can_grant else AccessRestriction.get_all_public())
@classmethod

View file

@ -23,6 +23,7 @@ from c3nav.mapdata.fields import I18nField
from c3nav.mapdata.grid import grid
from c3nav.mapdata.models.access import AccessRestrictionMixin
from c3nav.mapdata.models.base import SerializableMixin, TitledMixin
from c3nav.mapdata.utils.cache.local import per_request_cache
from c3nav.mapdata.utils.fields import LocationById
from c3nav.mapdata.utils.models import get_submodels
@ -620,10 +621,10 @@ class Position(CustomLocationProxyMixin, models.Model):
if not user.is_authenticated:
return False
cache_key = 'user_has_positions:%d' % user.pk
result = cache.get(cache_key, None)
result = per_request_cache.get(cache_key, None)
if result is None:
result = cls.objects.filter(owner=user).exists()
cache.set(cache_key, result, 600)
per_request_cache.set(cache_key, result, 600)
return result
def serialize_position(self, request=None):

View file

@ -16,6 +16,7 @@ from shapely.ops import unary_union
from c3nav.mapdata.tasks import process_map_updates
from c3nav.mapdata.utils.cache.changes import GeometryChangeTracker
from c3nav.mapdata.utils.cache.local import per_request_cache
class MapUpdate(models.Model):
@ -48,47 +49,47 @@ class MapUpdate(models.Model):
@classmethod
def last_update(cls, force=False):
if not force:
last_update = cache.get('mapdata:last_update', None)
last_update = per_request_cache.get('mapdata:last_update', None)
if last_update is not None:
return last_update
try:
with cls.lock():
last_update = cls.objects.latest().to_tuple
cache.set('mapdata:last_update', last_update, None)
per_request_cache.set('mapdata:last_update', last_update, None)
except cls.DoesNotExist:
last_update = (0, 0)
cache.set('mapdata:last_update', last_update, None)
per_request_cache.set('mapdata:last_update', last_update, None)
return last_update
@classmethod
def last_processed_update(cls, force=False, lock=True):
if not force:
last_processed_update = cache.get('mapdata:last_processed_update', None)
last_processed_update = per_request_cache.get('mapdata:last_processed_update', None)
if last_processed_update is not None:
return last_processed_update
try:
with (cls.lock() if lock else nullcontext()):
last_processed_update = cls.objects.filter(processed=True).latest().to_tuple
cache.set('mapdata:last_processed_update', last_processed_update, None)
per_request_cache.set('mapdata:last_processed_update', last_processed_update, None)
except cls.DoesNotExist:
last_processed_update = (0, 0)
cache.set('mapdata:last_processed_update', last_processed_update, None)
per_request_cache.set('mapdata:last_processed_update', last_processed_update, None)
return last_processed_update
@classmethod
def last_processed_geometry_update(cls, force=False):
if not force:
last_processed_geometry_update = cache.get('mapdata:last_processed_geometry_update', None)
last_processed_geometry_update = per_request_cache.get('mapdata:last_processed_geometry_update', None)
if last_processed_geometry_update is not None:
return last_processed_geometry_update
try:
with cls.lock():
last_processed_geometry_update = cls.objects.filter(processed=True,
geometries_changed=True).latest().to_tuple
cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None)
per_request_cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None)
except cls.DoesNotExist:
last_processed_geometry_update = (0, 0)
cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None)
per_request_cache.set('mapdata:last_processed_geometry_update', last_processed_geometry_update, None)
return last_processed_geometry_update
@property
@ -239,7 +240,8 @@ class MapUpdate(models.Model):
LevelRenderData.rebuild(geometry_update_cache_key)
transaction.on_commit(
lambda: cache.set('mapdata:last_processed_geometry_update', last_geometry_update.to_tuple, None)
lambda: per_request_cache.set('mapdata:last_processed_geometry_update',
last_geometry_update.to_tuple, None)
)
else:
logger.info('No geometries affected.')
@ -282,7 +284,7 @@ class MapUpdate(models.Model):
if new:
transaction.on_commit(
lambda: cache.set('mapdata:last_update', self.to_tuple, None)
lambda: per_request_cache.set('mapdata:last_update', self.to_tuple, None)
)
if settings.HAS_CELERY and settings.AUTO_PROCESS_UPDATES:
transaction.on_commit(

View file

@ -1,8 +1,7 @@
from collections import OrderedDict
from django.core.cache import cache
from c3nav.mapdata.models import MapUpdate
from django.conf import settings
class NoneFromCache:
@ -42,6 +41,9 @@ class LocalCacheProxy:
self._items.pop(next(iter(self._items.keys())))
def _check_mapupdate(self):
# todo: would be nice to not need this… why do we need this?
from c3nav.mapdata.models import MapUpdate
mapupdate = MapUpdate.current_cache_key()
if self._mapupdate != mapupdate:
self._items = OrderedDict()
@ -52,3 +54,18 @@ class LocalCacheProxy:
cache.set(key, value, expire)
self._items[key] = value
self._prune()
def clear(self):
self._items.clear()
class RequestLocalCacheProxy(LocalCacheProxy):
""" this is a subclass without prune, to be cleared after every request """
def _prune(self):
pass
def _check_mapupdate(self):
pass
per_request_cache = RequestLocalCacheProxy(maxsize=settings.CACHE_SIZE_LOCATIONS)

View file

@ -390,6 +390,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'c3nav.mapdata.middleware.RequestCacheMiddleware',
'c3nav.mapdata.middleware.UserDataMiddleware',
'c3nav.site.middleware.MobileclientMiddleware',
'c3nav.control.middleware.UserPermissionsMiddleware',