Speed is key. The snappier the site, the more visitors like you. Speed is so important that Google uses it in site rankings and as a major component of its PageSpeed Tools.
Rack::Deflater middleware compresses responses at runtime using deflate or trusty ol’ gzip. Inserted correctly into your Rack app, it can drastically reduce the size of your HTML / JSON controller responses (numbers below!). On a Heroku Rails 3 deployment, it can be configured to compress assets delivered via your dynos.
There are other (possibly better) places to handle content compression, for example:
- A frontend proxy / load balancer,
- Your CDN,
- By pre-compressing content and serving that from your web server.
We’re going to talk about the simplest thing that’ll work for most Heroku hosted Rails apps.
Rails 3 and 4
Add it to config/application.rb
thusly:
module YourApp
class Application < Rails::Application
config.middleware.use Rack::Deflater
end
end
And your HTML, JSON and other Rails-generated responses will be compressed.
Rails 3 and Runtime Asset Compression
If you’re running Rails 3 (which will serve static assets for you), you can use Rack::Deflater
for runtime asset compression as well. Configure it thusly:
module YourApp
class Application < Rails::Application
config.middleware.insert_before ActionDispatch::Static, Rack::Deflater
end
end
Inserting Rack::Deflater
before ActionDispatch::Static
means you’ll get runtime compression of assets served from Heroku in addition to the HTML, JSON, XML and other content your app returns.
Rails 4 assumes you’re serving assets from a CDN or via your webserver (and not your Rails processes) so ActionDispatch::Static
middleware isn’t enabled by default. If you try to insert Rack::Deflater
before it, you’ll get errors.
spec'ing
Controller specs skip Rack middleware. You need to assert that content is compressed / not compressed in a feature spec as they exercise the full Rails stack. We’re using Capybara’s RSpec awesomeness for our integration specs. Example:
# spec/integration/compression_spec.rb
require 'spec_helper'
feature 'Compression' do
scenario "a visitor has a browser that supports compression" do
['deflate','gzip', 'deflate,gzip','gzip,deflate'].each do|compression_method|
get root_path, {}, {'HTTP_ACCEPT_ENCODING' => compression_method }
response.headers['Content-Encoding'].should be
end
end
scenario "a visitor's browser does not support compression" do
get root_path
response.headers['Content-Encoding'].should_not be
end
end
Compression Overhead - Dynamic Content
There is overhead to compressing content - let’s see how significant it is. These tests were run against a fairly typical Rails 3.2 app.
We’ll run Siege against a dynamic content page running under Thin for 30 seconds, simulating 10 concurrent users. I’m picking typical results: I ran Siege numerous times for each scenario.
siege -t30s -c 10 'http://127.0.0.1:3000/contact'
| |Before Rack::Deflater|After Rack::Deflater| |Transactions|271 hits|265 hits| |Data transferred|1.24 MB|0.45 MB| |Transaction rate|9.19 trans/sec|8.93 trans/sec|
So we were a little slower, but the content is around 1/3rd the size.
Compression Overhead - Static Content
Let’s try against static asset content.
siege -t10s -c 10 'http://localhost:3000/assets/application.css'
| |Before Rack::Deflater|After Rack::Deflater| |Transactions|22601 hits|4052 hits| |Data transferred|658.26 MB|21.44 MB| |Transaction rate|2374.05 trans/sec|443.33 trans/sec|
So when we benchmark the raw speed of compressing static assets, it’s around 5 times slower. The benefit, though, is that application.css is around 6 times smaller - 6.2KB instead of 30.1KB.
Plus we’re still serving 443 requests per second. That is far beyond the demands that’d be put on any one dyno, and at traffic levels like this you’re probably already using a CDN.
Real world impact
Once you’ve enabled Rack::Deflater
middleware, you should see compression statistics in the Chrome web inspector for (at least) your Rails-generated content. For example:
Assets are also being compressed in this Rails 3.2 app via Rack::Deflater
, hence the compression ratios for application.css
and application.js
.
| |Before Rack::Deflater|After Rack::Deflater|Compression Rate| |Google PageSpeed analysis|79 of 100|93 of 100|N/A| |application.css|30.1KB|6.2KB|79%| |application.js|117.0KB|40.8KB|65%| |page html|4.3KB|2.6KB|40%| |Total size|151.4KB|49.6KB|67%|
So with minimal effort we were able to decrease our page sizes significantly (1/3rd of their original size!) and bump up our PageSpeed analysis 14 points.