Cache Busting with Django FileStorages

Introduction

Why Caching

In production environments, caching the static contents is a well known practice to improve the user experiences and minimise resource utilisation. In most cases caching for static contents implemented in multiple levels from application servers to CDNs to browser.

A good caching policy always helps to deliver up to date contents with great user experience. However the caching the static contents also introduces some limitations like reflecting the recent modifications done to the static contents. So, any good cache policy cannot be the best cache policy at all.

Downsides of Caching

In a situation where there are new contents to be served but previous caches still serving the older contents, and you cannot wait for your caching policies to invalidate your old contents timely, forced cache invalidation is necessary. Therefore, we can either go to each point where the older contents have been cached and invalidate them explicitly. But, it could be time-consuming or impossible task for most cases.

Cache Busting

Instead, we can take another approach where we invalidate all the caches by modifying the cache key causing cache miss at every point. This forces the end consumer of the content to retrieve from the original source which is up to date in real time. This approach is commonly know as cache busting.

Cache busting is the known solution when there is a need for explicitly reflect the changes done in static contents in real time. For that, there are few methods.

  1. File Name Versioning
  2. File Name Hashing
  3. File Path Versioning
  4. Query Parameters

Managing Static Contents in Django

Django framework shipped with a decent mechanism to manage and serve the static files defined in each of the apps inside a Django project.

When the project is ready to be served in production mode collectstatic command collects all the static files defined in each configured apps into a folder path defined with STATIC_ROOT setting. Then the static contents will be served from the defined STATIC_ROOT path.

As we understood in the introduction, these static files might get cached at certain levels based on the caching policies defined in application servers, proxy servers, CDNs, and browsers.

The default configuration on Django does not provide any options to bust caching when there are new changes in static files that are collected into the STATIC_ROOT. Thus, a production deployment of a Django project with default static content configurations may still serve the outdated content to the end consumers.

Django ManifestStaticFilesStorage

As we know the default configuration of static content management does not support options to bust caches if there is an updated version of static content available. Because the default implementation of static file collection is handled by StaticFilesStorage

However, ManifestStaticFilesStorage a subclass of StaticFilesStorage adds a post-processing functionality that appends an MD5 hash to each of the collected file names which is a cache-busting technique discussed in the previous section.

To enable ManifestStaticFilesStorage as the active static file storage handler for a Django project, we must explicitly set the STATICFILES_STORAGE setting as follows.

STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

When debug mode enabled with DEBUG = True setting, this configuration will not post process the static files with hashed on collectstatic command.

Also these hashed files are not served through the development server provided by Django framework.

Common Caveat

When ManifestStaticFilesStorage is configured as the Static Files Storage, post-processing recursively identifies any referring paths in processing file content (such as CSS file imports, and image path references in a CSS file). Then the post-processing task attempt to rename the identified files by appending the MD5 hash of the respective file.

If there is a path reference that doesn't exist, the static file collection will stop processing while raising an error. To resolve this issue file path references should be corrected, and the files should be available and accessible to post-process by the task.

Conclusion

Although ManifestStaticFilesStorage can be used as a standard way to implement a cache-busting mechanism, it is supported for other use cases as well. For example, it is useful when you want to serve multiple versions of static content (eg: CSS files) to different clients.

References

ManifestStaticFilesStorage