iPerSec
internet Performance & Security

varnish : simple and fast http acceleration

novembre 15th, 2007 by jfbus

I’ve been playing with varnish for a few days now. Until now, I have been using lighttpd as an HTTP accelerator, in front of my apache servers.

I love lighttpd a lot : it is simple, very efficient, and quite customizable through lua. But it is not a great HTTP accelerator :

  • it only uses non-persistent HTTP connections to the back-end,
  • it does not cache (a simple caching reverse-proxy can be written is lua using mod_magnet, but this solution is not satisfactory).

I had to find a better alternative…

I could have used squid, like many, but I dislike squid. Why ?

  • it is bloated (for my use) : as a general purpose proxy, I only need 10% of what squid provides,
  • it is complex to setup (because of the many options it has),
  • squid 2.6 only supports persistent back-end connections through a patch, and squid 3.0 is still a RC.

Here comes varnish… varnish is simple, clean and very flexible : it only works as a reverse proxy providing HTTP acceleration features, and it nearly works out of the box (with a some strange caveats). Nearly every action that varnish takes can be replaced/extended with some code, using a simple programming langage : vcl. This allows for a lot of flexibility and, being compiled in C code, has nearly no overhead.

Some would say that varnish has been less tested on the field than squid, and that squid 3.0 might be as stable as varnish, but I still dislike the bloat.

caching content with varnish

When setting up varnish using the default configuration in front of your application (in my case : a PHP app), you’ll probably notice that varnish caches nothing.

This is probably because varnish does not cache if it sees a cookie in the incoming request. No problem, this can be solved with some vcl code. You just have to have to take the default vcl code and remove the req.http.Cookie condition in vcl_recv (Don’t use the default vcl code included in man vcl : it is wrong). Here is the modified vcl code :

backend default {
set backend.host = "127.0.0.1"; // use your own backend ip address
set backend.port = “8080″; // use your own backend port
}
sub vcl_recv {
if (req.request != “GET” && req.request != “HEAD”) {
pipe;
}
if (req.http.Expect) {
pipe;
}
if (req.http.Authenticate) { // was (req.http.Authenticate || req.http.Cookie)
pass;
}
lookup;
}
sub vcl_pipe {
pipe;
}
sub vcl_pass {
pass;
}
sub vcl_hash {
set req.hash += req.url;
set req.hash += req.http.host;
hash;
}
sub vcl_hit {
if (!obj.cacheable) {
pass;
}
deliver;
}
sub vcl_miss {
fetch;
}
sub vcl_fetch {
if (!obj.valid) {
error;
}
if (!obj.cacheable) {
pass;
}
if (obj.http.Set-Cookie) {
pass;
}
insert;
}
sub vcl_deliver {
deliver;
}
sub vcl_timeout {
discard;
}
sub vcl_discard {
discard;
}

Next, you might notice that varnish caches a little bit too much : personalized pages are cached and served to other people. This is because varnish (by default) only partially supports HTTP/1.1 caching.

HTTP/1.1 is defined by RFC2616, and defines the following headers :
Cache-Control: [...]
Pragma: [...]

Varnish supports Cache-Control: max-age=ddd (max-age=0 to disable caching, max-age=120 to cache for 2 minutes), but does not support the following :

  • Cache-control: no-cache and Pragma: no-cache : this should disable all caching,
  • Cache-control: private : this should disable proxy caching, but not browser caching.

Cache-control can also get other values, which are also generally not supported by varnish.

This is a major problem with PHP, because PHP only uses no-cache and not max-age, and could have been a show-stopper. Thankfully, it can be easily corrected with a few lines of vcl code. Just replace vcl_recv and vcl_fetch :

sub vcl_recv {
if (req.request != "GET" && req.request != "HEAD") {
pipe;
}
if (req.http.Expect) {
pipe;
}
if (req.http.Authenticate) {
pass;
}
if (req.http.Cache-Control ~ "no-cache") {
pass;
}
lookup;
}
sub vcl_fetch {
if (!obj.valid) {
error;
}
if (!obj.cacheable) {
pass;
}
if (obj.http.Set-Cookie) {
pass;
}
if (obj.http.Pragma ~ "no-cache" || obj.http.Cache-Control ~ "no-cache" || obj.http.Cache-Control ~ "private") {
pass;
}
insert;
}

We’ll talk about performance later…

Posted in HTTP

2 Responses

  1. Antonio

    Very interesting article. You just earned a new fellow reader :)

    I like a lot this article and others I just read on this blog.

    Mind me a question, have you ever considered using the standard edition of LiteSpeed instead of lighttpd?

    LiteSpeed has some good features like compatibility with .htaccess files (with caching) and mod_rewrite.

    Best regards,

    Antonio.

  2. shahidpazeez

    Hai,

    I configured varnish and varnish linten on port 6081. I am using haproxy for load balancing and haprroxy listen on port 80. But when varnish checks for backend it checks on port 6081 insted of 80. In varnish log it is like this
    13 TxHeader b Host: example.com:6081

    There is no problem when varnish log is 80. then the varnish log looks like this.
    13 TxHeader b Host: example.com

    If we specify port 80 on the url the it shows the problem.
    13 TxHeader b Host: example.com:80

    And page didn’t load

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.