Helping Speed Up Websites With django-cachebuster
I released a cache-busting application for Django in March called django-cachebuster. It hasn't seen the uptake I would have expected, given the static/media changes introduced with the release of Django 1.3. After all, existing solutions seemed to be simple snippets supporting only Django 1.2 and lower. I'm writing this article to provide some exposure to django-cachebuster, plain and simple. If you try it out, please provide feedback, enter issues and/or submit pull requests to improve the app.
Seriously, what's this for?
Why would you want django-cachebuster added to your Django project? Let's start by understanding how web browsers ask servers for files:
- A user tells their web browser to go to www.example.com
- The browser gets the page's HTML markup from the server and parses it to discover additional elements required for the page to look and function correctly. Mostly, these are images, style sheets and javascript files.
- For each additional element, the browser checks its local file cache to see if it already has a file of that name
- If it doesn't exist locally, it gets the file from the server
- If it exists locally and there is no expiration date on the file, the web browser will request the file from the server (this is the default behaviour for most web servers):
- the server may respond with a status code (304) indicating that there have been no modifications since its last retrieval, or
- it will send back the file.
- If it exists locally and there is an expiration date on the file that has expired, it will fetch the file from the server again
- If it exists locally and there is an expiration date on the file that has not expired, it will do nothing. (this is the best scenario for high capacity server configurations)
Point #7 simply indicates that you want to cut out any unnecessary traffic between the multitude of users viewing your website content. The technology to enable this functionality is available to server admins regardless of the web server they use - if it isn't, you should switch! It is enabled by configuring the server to allow what I'll call 'asset far-future expiration'. Once enabled, the web servers simply tell the browser to not bother coming to get affected files again until some ridiculously distant date comes to pass such as the year 2050. Translated, it instructs the web browser software to keep the file in its cache until the expiration date. This makes your users' web browsing experience much faster and responsive in addition to reducing the number of incoming web browser requests to your servers - enabling higher capacity within existing infrastructure and reducing costs...
... at a cost.
What's the catch?
You've decided to change the color of your website logo, update Javascript/CSS files or add new functionality. Without far-future expiration, you would just roll out all of the new files and web browsers would find them immediately. With far-future expiration enabled, the web browser will not bother to check its local copy of a file against the server - it will never get the new content unless the user manually refreshes the page.
What's the fix?
django-cachebuster, or something similar. Web browser file cache-busting is typically done by appending a unique string as a query parameter at the end of the asset's URL. (ie. instead of www.example.com/styles.css, we would have www.example.com/styles.css?3141548702). Typically, the unique value used is simply the file's timestamp. This ensures that the server uses the same unique value for each file request until the file is updated, which causes its timestamp to be changed. django-cachebuster has some efficiency improvements to allow for reducing file I/O (timestamp reads) by using the current git commit; this also helps to ensure cross-system compatible unique ids. Outside contributors have also submitted compatibility changes for cloud-based (such as Amazon's CloudFront) asset serving.
Find out how to install and configure django-cachebuster on GitHub here: https://github.com/jaddison/django-cachebuster.