wiki:VCLExampleCachingLoggedInUsers

Caching logged in users

One of the hardest things to get right when caching, is to be able to cache content for logged in or cookied users.

The below snippets of code show you how to do this with varnish, using vcl_hash and esi.

Here is what you need in your setup to make this work:

  • A value in your cookie that unique defines your user.
    • This can be a session key if you let your cache expire
    • This should be a UUID if you do not let your cache expire, but purge manually instead.
  • A predictable identifier in your esi calls to indicate what content is state specific, and which is not
    • This let's varnish do the right thing, without needing to know anything about your application
    • Since you control the ESI urls, and the user never sees them, any markers are safe

IMPORTANT

As caching per-user content creates an extra cache entry for every user, it's important to cache as little as possible. Some guidelines:

  • Keep your per-user ESI as small as possible: Do not include things in your ESI that are not UUID specific.
  • Be precise: Do not make an ESI UUID specific if it is only LOGIN specific.
  • Limit the scope: When caching differently based on cookie values, consider limiting the URLs this applies to.

VCL

Here is the required VCL to make this work:

### vcl_hash creates the key for varnish under which the object is stored. It is
### possible to store the same url under 2 different keys, by making vcl_hash
### create a different hash.
sub vcl_hash {

    ### these 2 entries are the default ones used for vcl. Below we add our own.
    set req.hash += req.url;
    set req.hash += req.http.host;
    
    ### Hash differently based on a cookie value. For example, some users may 
    ### want "not safe for work" content as part of their search results, and
    ### some users may not.
    if( req.url ~ '^/search' && req.http.Cookie ~ "show_nsfw=1" ) {
        ### add this fact to the hash
        set req.hash += "show nsfw";
        
        ### set a header so it can be logged in the backend/varnishlog
        set req.http.X-Show-NSFW = "1";
    }   
    
    ### regsub replaces only the bit in your match criteria with whatever you
    ### ask it too. In this case, we need to remove *EVERYTHING* else from the
    ### cookie, except the UUID, hence the full string match    
    ### This is using the UUID, which will always stay the same. If you wish
    ### to cache based on the session, you could use "myapp_session" instead.
    ### The regex is PCRE, so it works only in trunk. If you wish to use it
    ### with Varnish 2.0, remove the '?' non-greedy operator and be aware it
    ### changes the behavior of the regex.
    if( req.http.Cookie ~ "myapp_unique_user_id" ) {
        set req.http.X-Varnish-Hashed-On = 
            regsub( req.http.Cookie, "^.*?myapp_unique_user_id=([^;]*);*.*$", "\1" );
    }
    
    ### if the esi request is UUID specific, add the UUID to the hashing
    ### The only requirement on the application is now that UUID specific 
    ### content mentions the string UUID in it.    
    if( req.url ~ "/esi/.*UUID" && req.http.X-Varnish-Hashed-On ) { 
        set req.hash += req.http.X-Varnish-Hashed-On;
    }

    ### if the esi request is LOGIN specific, add a string to the hashing
    ### The only requirement on the application is now that LOGIN specific 
    ### content mentions the string LOGIN in it.    
    if( req.url ~ "/esi/.*LOGIN" && req.http.X-Varnish-Hashed-On ) {
        set req.hash += "logged in";
    }  

    hash;
}

HTML Example

Below is a sample webpage that would take advantage of the per user caching.

<h1>MyApp Page</h1>

<div id="header">
    <!-- use an ESI to either show 'logged in as bob' or a sign up link -->
    <!-- esi:include src="/esi/user_header?UUID" -->
</div>

<div id="menu">
    <ul>
        <li> ...
        <li> ...
        <!-- use an ESI to show links in the menu to 'my profile' and 'settings' if the user is logged in -->
        <!-- esi:include src="/esi/user_menu?LOGIN" -->
    <ul>
</div>

<div id="content">
    ...
</div>