Secs sell! How I cache my entire pages (server-side)

10 May 2012   1 comment   Python, Django

Powered by Fusion×

I've blogged before about how this site can easily push out over 2,000 requests/second using only 6 WSGI workers excluding latency. The reason that's possible is because the whole page(s) can be cached server-side. What actually happens is that the whole rendered HTML blob is stored in the cache server (Redis in my case) so that no database queries are needed at all.

I wanted my site to still "feel" dynamic in the sense that once you post a comment (and it's published), the page automatically invalidates the cache and thus, the user doesn't have to refresh his browser when he knows it should have changed. To accomplish this I used a hacked cache_page decorator that makes the cache key depend on the content it depends on. Here's the code I actually use today for the home page:

def _home_key_prefixer(request):
    if request.method != 'GET':
        return None
    prefix = urllib.urlencode(request.GET)
    cache_key = 'latest_comment_add_date'
    latest_date = cache.get(cache_key)
    if latest_date is None:
        # when a blog comment is posted, the blog modify_date is incremented
        latest, = (BlogItem.objects
        latest_date = latest['modify_date'].strftime('%f')
        cache.set(cache_key, latest_date, 60 * 60)
    prefix += str(latest_date)

        redis_increment('homepage:hits', request)
    except Exception:
        logging.error('Unable to redis.zincrby', exc_info=True)

    return prefix

@cache_page_with_prefix(60 * 60, _home_key_prefixer)
def home(request, oc=None):
        redis_increment('homepage:misses', request)
    except Exception:
        logging.error('Unable to redis.zincrby', exc_info=True)

And in the models I then have this:

@receiver(post_save, sender=BlogComment)
@receiver(post_save, sender=BlogItem)
def invalidate_latest_comment_add_dates(sender, instance, **kwargs):
    cache_key = 'latest_comment_add_date'

So this means:

So, the next question is, if posting a comment means that the cache is invalidated and needs to be populated, what's the ratio of hits versus hits where the cache is cleared? Glad you asked. That's why I made this page:

It allows me to monitor how often a new blog comment or general time-out means poor django needs to re-create the HTML using SQL.

At the time of writing, one in every 25 hits to the homepage requires the server to re-generate the page. And still the content is always fresh and relevant.

The next level of optimization would be to figure out whether a particular page update (e.g. a blog comment posting on a page that isn't featured on the home page) should or should not invalidate the home page. esp


Diederik van der Boor
I also found at that with fully cached pages, make sure the following is set as well:


Otherwise, Django accesses request.session and the user table, resulting in a DB query for every request. With this setting to False, Django can run purely from cache,

Your email will never ever be published

Related posts

On the command line no one can hear you screen. Or can they? 03 May 2012
Postgres collation errors on CITEXT fields when upgrading to 9.1 21 May 2012
Related by keywords:
Newfound love of @staticmethod in Python 02 July 2012
'Cache-Control' or Expires 16 September 2005
A great thing about Squid: Calamaris 04 July 2008
Welcome to the world django-fancy-cache! 01 March 2013
Custom CacheMiddleware that tells Javascript a page is cached in Django 24 August 2009
Spellcorrector 0.2 24 September 2007
ESI and Squid and Zope 17 December 2003
Squid now working in front of my Zope 30 July 2003
This site 7 months ago 19 February 2004