blog.eliotsykes.com

Icon

Why Rails asset caching is broken in your app and how you can fix it

Rails Asset Fingerprint Plugin

Ruby on Rails’ helper methods (image_tag etc.) that generate URLs to assets (assets are static resources like stylesheet, image, and javascript files) use the broken caching strategy of timestamped query strings, which fails to provide desirable caching behaviour. The new Asset Fingerprint plugin fixes this problem by making it easy to use a file name fingerprint solution (recommended by Google) to improve the cacheability of your app.

Why using the query string is a bad idea

From Google’s advice on leveraging proxy caching:

Most proxies, most notably Squid up through version 3.0, do not cache resources with a “?” in their URL even if a Cache-control: public header is present in the response. To enable proxy caching for these resources, remove query strings from references to static resources, and instead encode the parameters into the file names themselves.

Google uses the term “fingerprinting” to describe putting parameters in a file name to identify a unique version of that file.

Why using file timestamps is a bad idea

File timestamps are not recommended as resource fingerprints as these are often inconsistent between deployments when a file has not changed. This means your app may not take full advantage of asset caching. Reasons for timestamp inconsistency include:

  • multiple app servers checking out files from code repo at different times
  • git does not preserve file timestamps (it really doesn’t)
  • subversion not configured to preserve file timestamps
  • generated asset files (e.g. compressed css/js bundles)

A hash function checksum (e.g. MD5 checksum) of an asset file’s contents can be used to give a more reliable file fingerprint.

What the Asset Fingerprint plugin does out-of-the-box

Easiest to illustrate what the plugin does by example: say there’s an image in your app located at public/images/logo.png, the Asset Fingerprint plugin will modify Rails’ helper methods to generate asset file paths like ‘/images/logo-fp-1749689fc4ba35c5bb1a4d1a8ef903e3.png’.

The ‘-fp-’ part of the path is a fixed string used to help identify fingerprinted paths and the ’1749689fc4ba35c5bb1a4d1a8ef903e3′ part is the fingerprint of the logo.png file, which is the MD5 checksum of the logo.png file. The fingerprints are calculated once and cached for performance (like Rails does already with the file timestamps).

By default, the plugin will create symlinks on-the-fly to ensure the fingerprinted asset paths are valid and serve the expected resource. You can configure this behaviour to have symlinks generated by a rake task instead (normally run during deployment) or you can use server rewrite rules if you prefer – the plugin’s README explains this and provides examples.

What else the Asset Fingerprint plugin can do

You can configure the plugin in a single line to use timestamps as fingerprints if you prefer, or you could write a SHA-2 FingerPrinter if you have a problem with MD5.

If you have an urge to put the fingerprint in the query string (as Rails does by default) that is a single line of configuration.

Who uses the plugin?

At time of writing Asset Fingerprint is used in production for MissedConnections.com.

How to get the plugin?

You can find source and in-depth details of the Asset Fingerprint plugin, including installation and customization instructions on Github.

TLTR summary Rails’ timestamped query string asset caching is broken. Install the Asset Fingerprint plugin in your app to fix it and improve the cacheability of your app.

Photo source

New Rails Page Cache plugin: for holeless/seamless page caching

For Missed Connections the sitemap and home page are cached on the filesystem and only refreshed periodically, after an event is fired in the app to say that missed connection ads have been created or deleted.

To make users lives a little easier, I never wanted them to make the request that generated one of these cached pages.  This isn’t quite how Rails page caching works out of the box.  Instead I wanted the page cache to be primed during deployment and periodically updated by a rake-task-calling cron job.

In Rails, out of the box, if a cached page is expired, it is deleted from the filesystem and is only generated when a new request comes in for that page.  This means the first person (could be more than one person if you get concurrent requests) to request the expired page will have to wait for it to be generated.

Instead, to save users’ time, I wanted the cache to be “holeless” (inspired by the Pivotal Labs post Rails, Slashdotted: no problem). To do this there is a cron job that generates new cached pages, and then replaces the old cached pages with these new cached pages.

In other words, the expired cached pages are only deleted after a refreshed version of the same page is available on the filesystem.

The first version of the plugin and README details are available at Github: Page Cache Rails Plugin

Who’s Blog?



Hello, I’m Eliot Sykes and this is my blog. Thank you for visiting. I'm the Founder of MissedConnections.com


Read about my current projects, contact me or find me on github