11

I'm using Django's per-view @cache_page decorator and have set a different key_prefix for each view.

I've previously deleted the cache with:

from django.core.cache import cache
cache.clear()

But what if I just want to delete the keys containing a specific key_prefix? I can obviously do it by just connecting to the database and delete with raw sql but I wonder if it can be done with 'pure' Django?

I'm using a database cache, not a memory cache.

I'm using Django 1.11 and Python 3.6

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Wessi
  • 1,702
  • 4
  • 36
  • 69
  • 1
    Possible duplicate of [Remove all matching keys from Django cache](https://stackoverflow.com/questions/25292426/remove-all-matching-keys-from-django-cache) – lapinkoira Sep 26 '17 at 09:11
  • @lapinkoira, thanks for the link. The answer in the linked question states that it only works with Django localmemcache. I'm using Django's database cache. I've updated the answer to clarify. – Wessi Sep 26 '17 at 12:27
  • You can use this link for your problem @Wessi. https://drupal.stackexchange.com/questions/21434/how-do-i-clear-cache-using-an-sql-query – Yasemin Gurcan Sep 29 '17 at 22:13

4 Answers4

11

I achieved what I want with the code like this:

# first: pip install django-redis

from django.core.cache import cache

cache.delete_many(keys=cache.keys('*.letters.*'))

It deletes all caches which keys contain "letters".

EDIT: I use redis server. I didn't test it for other cache servers.

8

As @e4c5 mentioned cache is used for fast stuff, you should be using redis for the same. But since your question is about database I would answer the same.

There is no existing function to do this in Django. But then best part of python is you can easily monkey path to add new functionality. Below is a test request I created

def index(request):
    cache.set("name", "tarun")
    cache.set("name_1", "tarun")
    cache.set("name2", "tarun")
    cache.set("name_4", "tarun")
    cache.set("nam", "tarun")

    cache.clear(prefix="name")
    nam = cache.get("nam")
    name_4 = cache.get("name_4", default="deleted")
    return HttpResponse("Hello, world. nam={nam}, name_4={name_4}".format(nam=nam, name_4=name_4))

Patched

To get the prefix functionality you need to add below patch code in some place. I used settings.py as such

original_clear = None


def patch_clear():
    from django.db import connections, router
    from django.core.cache.backends.db import DatabaseCache

    def __clear(self, prefix=None, version=None):
        db = router.db_for_write(self.cache_model_class)
        connection = connections[db]
        table = connection.ops.quote_name(self._table)
        with connection.cursor() as cursor:
            if prefix is None:
                cursor.execute('DELETE FROM %s ' % table)
            else:
                prefix = self.make_key(prefix, version)
                cursor.execute("DELETE FROM %s where cache_key like '%s%%'" % (table, prefix))

    global original_clear
    original_clear = DatabaseCache.clear
    DatabaseCache.clear = __clear

patch_clear()
Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • It turns out Django doesn't actually prefix the cache_key. The cache_key with prefix added looks like this `:1:views.decorators.cache.cache_page.prefixname.GET.eb9a021b08e0b96a809a931c0538e9c2.d41d8cd98f00b204e9800998ecf8427e.da-dk.Europe/Copenhagen` I tried changing your code to f"DELETE FROM {table} where cache_key like '%{prefix}%'", but the executed query looks like this `b"DELETE FROM `cache_table` where cache_key like '%:1:prefixname%'"` Do you have any idea about how to get rid of the `:1:` in front of the prefix? – Wessi Oct 03 '17 at 11:10
  • 1
    Remove `prefix = self.make_key(prefix, version)` from my code that should avoid the `:1` – Tarun Lalwani Oct 03 '17 at 11:17
  • I would rather extend the DatabaseCache class and create a backend on my own with a second method clear_prefix, and use `'BACKEND': 'path.to.my.backend',` in the settings – jde-chil Sep 03 '21 at 08:34
7

TLDR; cache.delete and cache.delete_many are your available options.

Long answer. @cache_page is over rated. When you use this decorator, you often find that the cache always contains many more cache entries than you expected. You end up wanting to delete a whole bunch of cache entries. Which seems to be exactly what has happened here.

I'm using a database cache, not a memory cache.

One of the main ideas of using caching is to reduce the load on the server another is to reduce expensive calculations or db queries. But in reality a great many web pages do not have expensive calculations. Most slow queries can be optimized by carefully choosing your indexes.

If the database itself is that cache, you are not reducing the load on the database. And what if you need to display different content for different users? This get's awfully complicated.

what if I just want to delete the keys containing a specific key_prefix?

Consider using redis. This is one of the best caching backends available in django (as a third party module). Being able to delete multiple keys in a single command is one of the many useful features of redis.

e4c5
  • 52,766
  • 11
  • 101
  • 134
  • 1
    "If the database itself is that cache, you are not reducing the load on the database." I strongly disagree with this. If a complex list takes 60 seconds and 10,000 queries to generate, and you cache it in the db, it can be retrieved in 0.1 seconds with one query. DB load can be *drastically* reduced via db caching, without going to redis. That's why it exists. – shacker Dec 11 '18 at 19:31
  • @shacker if you need 10,000 queries to generate a result you have a horribly, horribly designed application that probably ought to be written from scratch. – e4c5 Dec 11 '18 at 23:46
  • 1
    OK 10,000 was an exaggeration to make a point, but I am working some VERY large and VERY difficult data and there are some necessarily unusual modeling patterns. Trust me - caching is necessary here, I don't much care whether it's cached in the db, redis or memcache - the caching benefit is enormous, and virtually the same regardless of backend. – shacker Dec 12 '18 at 23:22
  • 1
    RDMS systems already have query caching built in. There are size limits and other issues that arise but caching in the database takes away available space from that query cache and can cause smaller result sets to get pushed out reducing the overall performance of your application. – Matthew Purdon Sep 17 '19 at 16:22
  • @MatthewPurdon database caching is completley different from redis and memcache that's been discussed here. Yes there are integrations where memcache is combined with the db. Yes, there are situations where memcache + a proxy sits in front of the db. But there's no way the size of your memcache or redis db can effect the db query cache. – e4c5 Sep 18 '19 at 01:47
  • 1
    I completely agree with @shacker. While database is often the bottleneck and indeed you can achieve better results optimizing queries and so on, sometimes you need to generate reports and pull data from different sources, process complex calculations, etc. While using the database as the cache backend for high traffic web pages may not be a good idea, it is perfectly fine to use on enterprise applications for example, where you have low traffic but complex operations. The setup is simple and the benefit will pretty much be the same as using redis or memcache. It really depends on the use case. – Vitor Freitas Mar 20 '21 at 05:08
0

With LocMemCache, you can delete all the cache values whose keys contain name as shown below. *list() is needed for cache._cache.keys() otherwise you get error and specifying a version is needed for cache.delete() otherwise you cannot delete all the cache values whose keys contain name and the answer of my question explains the default version of a cache value with LocMemCache:

from django.core.cache import cache
from django.http import HttpResponse

def test(request):
    cache.set("first_name", "John")
    cache.set("last_name", "Smith", version=2)
    cache.set("age", 36, version=3)
    cache.set("gender", "Male")
    
    print(cache._cache.keys())
    # odict_keys([':1:gender', ':3:age', ':2:last_name', ':1:first_name'])

            # `list()` is needed
    for key in list(cache._cache.keys()):
        new_key = key.split(":", 2)[2]
        version = key.split(":", 2)[1]

        if "name" in new_key:
            cache.delete(new_key, version=version)

    print(cache._cache.keys())
    # odict_keys([':1:gender', ':3:age'])

    return HttpResponse("Test")
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129