The nicest thing about Varnish is VCL. You can define your own business logic in Varnish and have it run at more or less the speed of light. If you compare the performance of an interpreted language you’ll find that VCL will execute significantly faster as there is no interpreter that will dynamically glue various bits of binary code together.
There is little you explicitly cannot do, especially not if you are willing to write a VMOD or two.
However, it doesn’t always make sense. You might have a Java API that already has the logic that you want to embed into Varnish. Instead of trying to force a Java runtime into Varnish the easy way is to do some sort of remote procedure call (RPC) to your application from Varnish and have Varnish understand the result.
There is of course a performance penalty to pay. For each callout a millisecond or more of latency will be added to the processing time. However, in the grand scheme of things this might not be a big problem. When the delay is less than the average jitter on your network connection the gain of optimizing it further is not all that significant.
Let’s say that we want to do AB testing. We have a pretty sophisticated algorithm for dividing users into A and B. So, instead of trying to express this algorithm in VCL we’re going to have Varnish ask another daemon for advice. A third alternative would be to express this algorithm in C and import it into Varnish as a VMOD, but we’re not going to do that either.
The first task is to find a suitable way of connecting Varnish to this other daemon. There are a couple of options. The simplest one is using the curl VMOD. This is a very well understood vmod and it works really well. Setting up a little HTTP server in any language is also trivial. This is what we’ll do.
The only downside is its performance. Even on localhost doing a TCP connection takes 120 microseconds or so. We could replace TCP with Unix sockets and someday we might do just that - in another blogpost.
I wrote a little Perl daemon to help discriminate between categories A and B. The actual algorithm for the discrimination is outside the scope of the post. In this example I just throw the URL into a MD5 algorithm and convert it into an integer. Then I check if it is modulo zero in order to pick "A" or "B". Perl was chosen since there are far too few Perl examples in blog posts these days. :-)
Now we need to integrate this with Varnish. I’m using Varnish Plus from our own repositories and there the curl VMOD is packaged in RPM package varnish-plus-vmods-extra. The cookie and std VMODs come with the main Varnish package. If you are using the community-supported version of Varnish you’ll need to compile the curl and cookie VMODs yourself.
The interesting bits of the VCL are the following:
# assign the user to A or B using external service
curl.get("http://localhost:8080/uid=" + req.http.uid);
set req.http.abgroup = curl.header("X-decision");
std.log("Callout decided user " +req.http.uid +" is in category " + req.http.abgroup);
So as you can see we fetch the data using curl.get and then store the result in a header. We call curl.free to make the curl VMOD free up any resources it has allocated. Then we log the result. Note that there is no error checking here. Some sort of fallback would probably be a good thing if you should decide to put something like this into production.
In the complete VCL we cache the decision we take in a cookie so the callout will only be done on users that are not assigned to a group yet. That reduces the performance penalty we have to pay asking the external process for advice.
The complete VCL used looks like this: