July 15, 2015
3 min read time

Proper sticky session load balancing in Varnish

The documented answer - use the client director

If you casually read the Varnish Cache documentation you’ll quickly see that you should use the client director. The client director is named what it is because it relies on data from the client to distribute incoming connections to the backends in the director. So, what most people do is to reuse the session cookie and feed its contents to the client director. This works fairly well except for one issue. The session is created by the backend server. This will happen after the client has been assigned to a backend the first time. Since no session exists the first time the client is assigned to a backend this will result in all initial clients being assigned to the same server.
Another problem is that the clients will get a different server on their second request. Since they now have a session cookie the algorithm will naturally yield a different backend.


Generating a session ID in Varnish Cache

The solution is to generate the random string before the client is assigned to the backend. We set client.identity with this string.Then we need to persist the string through a cookie and make sure to re-read it from returning users. Sounds simple, right? Here is the complete VCL to make it happen.

vcl 4.0;
import std;
import directors;
import cookie;
import header;
backend s1 {
.host = "127.0.0.1";
.port = "8080";
}
backend s2 {
.host = "127.0.0.1";
.port = "9090";
}
sub vcl_init {
new cdir = directors.hash();
cdir.add_backend(s1,1);
cdir.add_backend(s2,1);
}
sub vcl_recv {
  cookie.parse(req.http.cookie);
    if (cookie.get("sticky")) {
      set req.http.sticky = cookie.get("sticky");
    } else {
    # The cookies will have floats in them.
    # Whatever, ehh, floats your boat can be used.
    set req.http.sticky = std.random(1, 100);
  }
  # use to be client.identity in V3
  set req.backend_hint = cdir.backend(req.http.sticky);
}
sub vcl_deliver {
  # persist the cookie
  # we need to use the header vmod as there might be a set-cookie
  # header on the object already and
  # we don't want to mess with it
  if (req.http.sticky) {
     header.append(resp.http.Set-Cookie,"sticky=bar" +
            req.http.sticky + ";   Expires=" + cookie.format_rfc1123(now, 60m));
   }
}


Verifying it
After deploying the VCL I inspect varnishlog to see if it works. With only one client I see all the requests are going to the same director.

$ varnishlog -i Backend

*<< BeReq >> 3
- Backend 19 s1 s1(127.0.0.1,,8080)
* << Request >> 2
* << BeReq >> 5
- Backend 19 s1 s1(127.0.0.1,,8080)
* <<; Request >> 4
* << BeReq >> 32771
- Backend 19 s1 s1(127.0.0.1,,8080)


Picture is (c) 2008 Nick Perla and used under a CC licence.