Ok, in the end I managed to solve this using the following vcl file. Note that I added a couple of extra bits to allow cache expiration grace when the backend has died.
It seems my main failure was using unset req.http.Set-Cookie;
when I should have been using unset obj.http.Set-Cookie;
in the vcl_fetch
section. (obj
in vcl_fetch and req
in vcl_recv section).
director rails_director round-robin {
{
.backend = {
.host = "xxx.xxx.xxx.xxx";
.port = "http";
.probe = {
.url = "/lbcheck/lbuptest";
.timeout = 0.3 s;
.window = 8;
.threshold = 3;
}
}
}
}
sub vcl_recv {
if (req.backend.healthy) {
set req.grace = 30s;
} else {
set req.grace = 1h;
}
if (req.url ~ "^/login") {
pipe;
}
if (req.url ~ "^/sessions") {
pipe;
}
if (req.url ~ "\.(css|js|jpg|jpeg|gif|ico|png)\??\d*$") {
unset req.http.cookie;
lookup;
} else {
if (req.http.cookie ~ "user_credentials") {
pipe;
} else {
unset req.http.cookie;
}
}
# Only cache GET and HEAD requests
if (req.request != "GET" && req.request != "HEAD") {
pipe;
}
}
sub vcl_fetch {
set obj.grace = 1h;
if (req.url ~ "^/login") {
pass;
}
if (req.url ~ "^/sessions") {
pass;
}
if (req.http.cookie ~ "user_credentials") {
pass;
} else {
unset obj.http.Set-Cookie;
}
# cache CSS and JS files
if (req.url ~ "\.(css|js|jpg|jpeg|gif|ico|png)\??\d*$") {
unset obj.http.Set-Cookie;
}
if (obj.status >=400 && obj.status <500) {
error 404 "File not found";
}
if (obj.status >=500 && obj.status <600) {
error 503 "File is Temporarily Unavailable";
}
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
Do you anticipate using Edge Side Includes (ESI)? If so, the Nginx ESI module is broken and has some open bugs. If you use Varnish, output isn't compressed, so you're somewhat stuck using Nginx to do compression of ESI enabled pages. While I work with Python frameworks, Rails is similar.
With your current setup, you could do something like:
Nginx -> Apache -> Passenger -> Rails
Varnish -> Apache -> Passenger -> Rails
Both would drop in front of your existing system. With Nginx, you could also give it direct access to the static files and allow it to serve those without having to proxy through Apache. Using the Location directive, you can slice off portions of your webspace and prevent that from having to go through the proxy.
However, if you wanted to move completely to Nginx, your infrastructure becomes:
nginx -> passenger -> rails (nginx -> uwsgi -> python)
If you add Varnish, you end up with:
varnish -> nginx -> passenger -> rails
unless you use ESI, in which case you end up with:
nginx -> varnish -> nginx -> passenger -> rails
At some point, removing Varnish from the mix becomes quite intriguing. However, recent Varnish releases are still faster than Nginx's caching and you have a lot of control over how you can cache. While both Nginx and Varnish give you quite a bit of control, Varnish's VCL allows you to write C code to do things that neither does out of the box, without touching the daemon's source code. Whether that is useful to you is up to you.
Since you are using Apache currently, I would be more inclined to put Varnish in front unless you are going to migrate to Nginx and remove Apache completely. Varnish in your case is more of a drop-in solution. If you decide that you're going to use ESI in the future, you would need to run both.
Best Answer
You could get the best of both worlds if you arrange things like so:
User -> nginx -> Varnish -> Rails
Turn gzip compression on from nginx to user. That's the slowest segment and also the most costly. I am assuming that your nginx, Varnish and Rails instances are local to each other. Your local bandwidth should be more than sufficient. Besides it does not make too much sense to gzip only to decompress to assemble the ESI.