2

I am using(trying) redis as a cache to my django app. This is how I am trying to do it.

def postview(request):
    post_list = []
    if cache.get("posts") == None:
           post_list = Post.objects.all()
           cache.set("posts", post_list, timeout=None)
    else : 
           post_list = cache.get("posts")
    context = {"post_list" : post_list}
    return render(request, 'post_list.html', context)

@cache_page(60*15, key_prefix="test_cache")
def new(request):
    print("testing")
    return HttpResponse("hello, I am mohammed")

This is the output in redis-cli

luvpreet@DHARI-Inspiron-3542:~/test_venv_wrapper/test_redis$ redis-cli
127.0.0.1:6379> select 2 # I have redis db 2 as backend in django settings
OK
127.0.0.1:6379[2]> keys *
1) ":1:views.decorators.cache.cache_page.cache_test.GET.26488770af116d67b33750e5f304aa3e.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"
2) ":1:views.decorators.cache.cache_header..d314df08d6409ed165873dfa23271c50.en-us.UTC"
3) ":1:posts"
4) ":1:views.decorators.cache.cache_page..GET.d314df08d6409ed165873dfa23271c50.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"
5) ":1:views.decorators.cache.cache_header..26488770af116d67b33750e5f304aa3e.en-us.UTC"
6) ":1:views.decorators.cache.cache_page..GET.26488770af116d67b33750e5f304aa3e.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC"
7) ":1:views.decorators.cache.cache_header.cache_test.26488770af116d67b33750e5f304aa3e.en-us.UTC"

This is the value under one of the keys,

127.0.0.1> get :1:views.decorators.cache.cache_page.cache_test.GET.26488770af116d67b33750e5f304aa3e.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC
"\x80\x02cdjango.http.response\nHttpResponse\nq\x01)\x81q\x02}q\x03(U\x0e_handler_classq\x04NU\b_headersq\x05}q\x06(U\rlast-modifiedU\rLast-ModifiedU\x1dWed, 05 Apr 2017 10:56:58 GMT\x86U\aexpiresU\aExpiresU\x1dWed, 05 Apr 2017 15:06:58 GMT\x86U\x0ccontent-typeU\x0cContent-TypeU\x18text/html; charset=utf-8\x86U\rcache-controlU\rCache-ControlU\rmax-age=15000\x86uU\b_charsetq\aNU\x11_closable_objectsq\b]U\acookiesq\tcdjango.http.cookie\nSimpleCookie\nq\n)\x81q\x0b}q\x0cbU\x06closedq\r\x89U\n_containerq\x0e]q\x0fU\x14Hello, I am Mohammedq\x10aU\x0e_reason_phraseq\x11Nub."

This is some serialized value.

The queryset Post.objects.all() is cached and I have no problem in getting this from cache. But I am failing to understand this @cache_page() decorator.

Why is it making so many keys in the redis database ? Please explain the keys made in the redis database. How can I get to know this is working or not ?

Luv33preet
  • 1,686
  • 7
  • 33
  • 66
  • You could figure out the cache key by looking at the Django source code, but that's an implementation detail. The idea of using the @cache_page decorator is that Django takes care of setting and fetching from the cache. When you use the `cache_page` decorator, you are caching the result of the view (e.g. the rendered template), not individual parts of the view like querysets. How do you know that the `cache_page` decorator is not working? – Alasdair Apr 03 '17 at 12:15
  • What is the point of caching the template view if it is to be changed for every request as the post_list will be changed ?? – Luv33preet Apr 03 '17 at 12:24
  • 1
    I'm not sure I understand the question. The reason to cache `postview` is to improve performance. However if the queryset changes, then your users may see out of date pages from the cache until it expires. Unless you really need the performance improvement, it would be easier not to cache at all. – Alasdair Apr 03 '17 at 12:28
  • Like I have done in the view function in the first example, to cache the queryset. It will be difficult to manually cache multiple querysets, in this way. Is there a better way to cache the querysets ? – Luv33preet Apr 03 '17 at 12:48
  • If you need to cache multiple querysets in a single view, then you could create a function `get_queryset_from_cache` to prevent duplication. However, I'm not convinced it will noticeably improve performance for you. – Alasdair Apr 03 '17 at 13:15
  • Note that you are currently fetching the result from the cache with `cache.get("posts")` *twice*. It would be better to do `post_list = cache.get("posts")` and check `if post_list is None`. That way you can avoid the second fetch. – Alasdair Apr 03 '17 at 13:16

1 Answers1

2

cache_page decorator is a django decorator, not a django-redis decorator. So, if you were using a default cache like memcached in django, the cache_page decorator would have been making the same keys in memcached. Here is the decorator base code along the doc string :

https://github.com/django/django/blob/711123e1cdaf3b08c876c045d8d38decdc7a63d3/django/views/decorators/cache.py#L8

""" Decorator for views that tries getting the page from the cache and populates the cache if the page isn't in the cache yet. The cache is keyed by the URL and some data from the headers. Additionally there is the key prefix that is used to distinguish different cache areas in a multi-site setup. You could use the get_current_site().domain, for example, as that is unique across a Django project. Additionally, all headers from the response's Vary header will be taken into account on caching -- just like the middleware does. """

So, inherently it is creating multiple keys, one for headers and other for the HTTPResponse content. It creates the keys based on header and content, so that any change in header invalidates the cache ( for example in case of vary headers ), i.e. even with same parameters in the url, but different content in request-headers you will have seperate caches. Examples of different request-headers can be sending login info about same page for different logged in users, or serving different content for same url based on mobile/desktop user agent information present in headers. Here is the cache key code in django :

def _generate_cache_key(request, method, headerlist, key_prefix):
    """Return a cache key from the headers given in the header list."""
    ctx = hashlib.md5()
    for header in headerlist:
        value = request.META.get(header)
        if value is not None:
            ctx.update(force_bytes(value))
    url = hashlib.md5(force_bytes(iri_to_uri(request.build_absolute_uri())))
    cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
        key_prefix, method, url.hexdigest(), ctx.hexdigest())
    return _i18n_cache_key_suffix(request, cache_key)


def _generate_cache_header_key(key_prefix, request):
    """Return a cache key for the header cache."""
    url = hashlib.md5(force_bytes(iri_to_uri(request.build_absolute_uri())))
    cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
        key_prefix, url.hexdigest())
    return _i18n_cache_key_suffix(request, cache_key)


def get_cache_key(request, key_prefix=None, method='GET', cache=None):
    """
    Return a cache key based on the request URL and query. It can be used
    in the request phase because it pulls the list of headers to take into
    account from the global URL registry and uses those to build a cache key
    to check against.
    If there isn't a headerlist stored, return None, indicating that the page
    needs to be rebuilt.
    """
    if key_prefix is None:
        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
    cache_key = _generate_cache_header_key(key_prefix, request)
    if cache is None:
        cache = caches[settings.CACHE_MIDDLEWARE_ALIAS]
    headerlist = cache.get(cache_key)
    if headerlist is not None:
        return _generate_cache_key(request, method, headerlist, key_prefix)
    else:
        return None
DhruvPathak
  • 42,059
  • 16
  • 116
  • 175
  • indeed, I put the question in hurry. But I read the docs later and got what I wanted. Thanks for the answer, you get the bounty. One question is that does it cache the 'DELETE', 'PATCH' requests? – Luv33preet Apr 09 '17 at 19:27
  • https://stackoverflow.com/questions/56038304/how-to-implement-redis-cache-with-django-rest-framework – Diego Peñalver Montero May 17 '20 at 10:25