Skip to content

Commit

Permalink
Use django function caching.
Browse files Browse the repository at this point in the history
  • Loading branch information
EralpB committed May 25, 2020
1 parent cd23613 commit 832647d
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 42 deletions.
47 changes: 5 additions & 42 deletions bookshop/models.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,19 @@
from django.db import models
from django.core.cache import cache
import time
import redis
import json

r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
from functioncaching import cached_function


class Author(models.Model):
name = models.CharField(max_length=255)

# _ indicates this function is for internal use only
def _get_top_books(self):
@cached_function(timeout=24*60*60, freshness_timeout=60*60)
def _get_top_book_ids(self):
time.sleep(5) # make the calculation slower for the sake of argument
return list(Book.objects.order_by('-purchase_count')[:10])

return list(Book.objects.filter(author=self).order_by('-purchase_count').values_list('id', flat=True)[:10])

def get_top_books(self):
CACHE_TIMEOUT = 24 * 60 * 60 # 1 day
CACHE_FRESHNESS = 60 * 60 # 1 hour
REMAINING_CUTOFF = CACHE_TIMEOUT - CACHE_FRESHNESS # If TTL less than this value, needs recalculate

cache_key = 'Author:get_top_books:{}'.format(self.id)
lock_key = 'Lock:{}'.format(cache_key)
cache_value = r.get(cache_key)
if cache_value is not None:
cache_value = json.loads(cache_value)
remaining_ttl = r.ttl(cache_key)

should_recalculate = remaining_ttl < REMAINING_CUTOFF

if not should_recalculate and cache_value is not None:
# if key is fresh enough, and value exists, just return!
return list(Book.objects.filter(id__in=cache_value))

# try to acquire lock to recalculate..
try:
with r.lock(lock_key, timeout=60, blocking_timeout=0):
books = self._get_top_books()
except redis.exceptions.LockError:
# somebody else is calculating, if cache value exists, no problem, no error
if cache_value is not None:
return list(Book.objects.filter(id__in=cache_value))
else:
# we don't even have a stale cache, and some worker is calculating, no choice but to error out
raise Exception('ColdCacheException')

# only update the cache if you freshly calculated!
# In other cases the execution doesnt reach here, it either gives exceptions or returns early.
cache_representation = json.dumps([book.id for book in books])
r.set(cache_key, cache_representation, CACHE_TIMEOUT) # cache for 1 day
return books
return list(Book.objects.filter(id__in=self._get_top_book_ids()))

def __str__(self):
return self.name
Expand Down
11 changes: 11 additions & 0 deletions cachingshowcase/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,14 @@
# https://docs.djangoproject.com/en/2.1/howto/static-files/

STATIC_URL = '/static/'


CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
22 changes: 22 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
asgiref==3.2.7
bleach==3.1.5
certifi==2020.4.5.1
chardet==3.0.4
Django==2.2
django-function-caching==0.2
django-redis==4.11.0
docutils==0.16
idna==2.9
importlib-metadata==1.6.0
keyring==21.2.1
packaging==20.4
pkginfo==1.5.0.1
Pygments==2.6.1
pyparsing==2.4.7
pytz==2020.1
readme-renderer==26.0
redis==3.5.2
requests==2.23.0
requests-toolbelt==0.9.1
six==1.15.0
sqlparse==0.3.1
tqdm==4.46.0
twine==3.1.1
urllib3==1.25.9
webencodings==0.5.1
zipp==3.1.0

0 comments on commit 832647d

Please sign in to comment.