How we setup Amazon Cloudfront to play nice with Rails 3.0.x on Heroku
As was mentioned in our previous post, we recently started using Amazon Cloudfront for our CDN needs. We wanted the performance benefits that we would gain from the move, we certainly didn’t want it to come at the cost of speed of deployment.
While the CloudFront custom origin server solution takes care of deployment, there is still the problem of expiring stale assets (especially Javascript and CSS).
As they say, the two toughest problems in Computer Science are caching, naming things and off-by-one errors.
Asset Fingerprinting
The problem of expiring stale content for CDN’s (and browsers) is solved by asset fingerprinting as part of Rails 3.1’s Asset Pipelining feature.
Every file in the app/assets directory in Rails 3.1 is “compiled” as part of the rake assets:precompile Rake task and the asset fingerprint is appended to the each file’s path and copied over to public/. The asset fingerprint is an MD5 digest of the contents of each file - so if a file is changed (for e.g. and image is changed or you change something in your CSS), the MD5 of the asset will change, and hence that particular asset will be ‘expired’.
The blog post that I referenced from the Open Government, uses the SHA of the most recent Git commit as the asset fingerprint. While that is a simple solution, it is quite inefficient. Think about it - any time you make a change to your site - (including model code or a change in a Rake task), your assets will be expired, even though technically the assets haven’t changed.
We don’t change our CSS/JS/images that often, but we do make changes to our business logic quite often (and deploy multiple times a day), so this solution won’t fly and defeats the purpose of the CDN, since content will always be expired.
Asset Fingerprinting without Sprockets
What we came up with is a better solution, which takes a digest of the entire public/ directory under Rails.root. It’s still less efficient than Sprockets’ asset fingerprinting, since in our solution the fingerprint is a hash of the entire directory and not each file. So even if you make a change to a single file, an unrelated file under the public/ will also be expired even if it hasn’t changed.
The hash of the public/ directory is stored in a file called assets.fingerprint under Rails.root, and is generated like this.
This is a function of a deploy Rake task which performs pre-flight checks such as these before pushing to Heroku.
We define a constant called REVISION which is defined in config/initializers/asset_fingerprint.rb thusly:
REVISION = File.read(Rails.root.join(‘assets.fingerprint’)).strip
…and in application.rb, the assets path is prepended with the hash as part of the path.
We also make sure that we use Rack::Rewrite to rewrite paths to the assets to ignore the hash and that the assets host points to the Amazon CDN.
Our entire application.rb is here for your reference: https://gist.github.com/1554090.
blog comments powered by Disqus