This last few days I spent some time adding Varnish in front of a Rails app. My previous experience with Varnish is basically the old times in Heroku, a magic non-configurable Varnish layer on top of your app. This time we're setting up our own infrastructure, including Varnish.
Some of the details about the application and its caching requirements that might affect the Varnish setup are:
X-SOMETHING: a
and another one with X-SOMETHING: b
will not have the same content, so will not share cache. (see Trying to understand the “Vary” HTTP header StackOverflow thread)The first thing to do is get Varnish, process that I will not describe here. In order to run Varnish we need to create the configuration file, see VCL documentation.
Here I paste a sample VCL file with some comments. I took most of it from other examples.
backend default {
.host = "yourapphost.com";
.port = "80";
}
sub vcl_recv {
set req.grace = 10m;
# This rule is to insert the client's ip address into the request header
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For = req.http.X-Forwarded-For +", "+ client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
# Don't cache requests other than GET or HEAD
if (req.request != "GET" && req.request != "HEAD") {
return (pass);
}
return(lookup);
}
sub vcl_pipe {
set bereq.http.connection = "close";
return(pipe);
}
sub vcl_fetch {
set beresp.grace = 10m;
if (beresp.http.Cache-Control ~ "max-age") {
unset beresp.http.Set-Cookie;
return(deliver);
}
return(hit_for_pass);
}
sub vcl_deliver {
# The below provides custom headers to indicate whether the response came from
# varnish cache or directly from the app.
if (obj.hits > 0) {
set resp.http.X-Varnish-Cache = "HIT";
} else {
set resp.http.X-Varnish-Cache = "MISS";
}
}
With that simple Varnish script all is ready to run varnish.
Run varnishd -a 0.0.0.0:8080 -s malloc,500M -F -f varnish.vcl
to run it locally for testing. This will basically run Varnish on port 8080 with 500M cache in memory using the config file varnish.vcl
. Any http request to the port 8080 will be handled by Varnish, either delegating it to the webserver or returning it from its cache. You can check the response headers to validate it is working.
At this point Varnish is already working on top of the rails server.
The Varnish configuration needs the webapp to set a max-age
in the Cache-Control
header in order to cache it in Varnish.
To do so, put the following line somewhere in the code of your action.
class PostsController < ApplicationController
def index
# Your index action code
# ...
expires_in 30.seconds, public: true
end
end
This will tell Varnish to cache the whole response for 30 seconds. You can validate sending requests through Varnish and checking the X-Varnish-Cache
header value.
This is all for this part. So far this is too basic, quite straight-forward. This is not really the most interesting part of our setup. Next parts will include the Vary header configuration and the user-specific content using ESI.