Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way of setting up varnish for caching django sites

I have just set up a server with just varnish installed in front of my backend server, where I have two different django sites, being served through nginx+gunicorn

It seem to work, but I get Header Age = 0, and looking at the documentation, that's not very good.

I want to cache pages for anonymous users, but not for authenticated users or if a user have a cookie called "AUTHENTICATION"

Here is my default.vcl

backend django {
    .host = "backend1";
    .port = "8080";
}


sub vcl_recv {  

  # unless sessionid/csrftoken is in the request, don't pass ANY cookies (referral_source, utm, etc)  
  if (req.request == "GET" && (req.url ~ "^/static" || (req.http.cookie !~ "sessionid" && req.http.cookie !~ "csrftoken" && req.http.cookie !~ "AUTHENTICATION"))) {  
    remove req.http.Cookie;  
  }  


    #normalize accept-encoding to account for different browsers  
    if (req.http.Accept-Encoding) {
        if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
            # No point in compressing these
            remove req.http.Accept-Encoding;
        } elsif (req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
        } elsif (req.http.Accept-Encoding ~ "deflate") {
            set req.http.Accept-Encoding = "deflate";
        } else {
            # unknown algorithm
            remove req.http.Accept-Encoding;
        }
    }  



}  

sub vcl_fetch {  

  # /static and /media files always cached  
  if (req.url ~ "^/static" || req.url ~ "^/media") {  
       unset beresp.http.set-cookie;  
       return (deliver);  
  }  

  # pass through for anything with a session/csrftoken set  
  if (beresp.http.set-cookie ~ "sessionid" || beresp.http.set-cookie ~ "csrftoken" || beresp.http.set-cookie ~ "AUTHENTICATION") {  
    return (hit_for_pass);  
  } else {  
    return (deliver);  
  }  

} 

Could it be that sessionid is set for every user, even if they are not logged in, and that is preventing Varnish effectively cache pages for anon users?

Edit:

Using isvarnishworking.com this is the output:

HTTP/1.1 200 OK
Server: cloudflare-nginx
Date:   Fri, 15 Nov 2013 09:30:20 GMT
Content-Type:   text/html; charset=utf-8
Connection: keep-alive
Set-Cookie: __cfduid=d281023a84b2e5351d109c1848eeca1601384507820317; expires=Mon, 23-Dec-2019 23:50:00 GMT; path=/; domain=.mydomain.com; HttpOnly
Vary:   Cookie
X-Frame-Options:    SAMEORIGIN
X-Varnish:  1602772074
Age:    0
Via:    1.1 varnish
CF-RAY: cdaec14fab00412
Content-Encoding:   gzip

Edit 2:

My new default.vcl:

backend django {
    .host = "backend1";
    .port = "8080";
}

sub vcl_recv {  

    #normalize accept-encoding to account for different browsers  
    if (req.http.Accept-Encoding) {
        if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
            # No point in compressing these
            remove req.http.Accept-Encoding;
        } elsif (req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
        } elsif (req.http.Accept-Encoding ~ "deflate") {
            set req.http.Accept-Encoding = "deflate";
        } else {
            # unknown algorithm
            remove req.http.Accept-Encoding;
        }
    }  
}



sub vcl_fetch {  
  if (req.url ~ "^/static" || req.url ~ "^/media") {  
    unset beresp.http.set-cookie;  
  }  

  if (beresp.http.set-cookie !~ "sessionid" && beresp.http.set-cookie !~ "csrftoken" && beresp.http.set-cookie !~ "AUTHENTICATION") {  
    unset beresp.http.set-cookie; 
  }
} 

Result from isvarnishworking.com

HTTP/1.1 200 OK
Server: cloudflare-nginx
Date:   Fri, 15 Nov 2013 12:08:42 GMT
Content-Type:   text/html; charset=utf-8
Connection: keep-alive
Set-Cookie: __cfduid=d55ea1b56e978cbbf3384d0fa2f21571e1384517322491; expires=Mon, 23-Dec-2019 23:50:00 GMT; path=/; domain=.mydomain.com; HttpOnly
Vary:   Cookie
X-Frame-Options:    SAMEORIGIN
X-Varnish:  1240916568
Age:    0
Via:    1.1 varnish
CF-RAY: cdbd4119f3b0412
Content-Encoding:   gzip

Edit 3:

backend default {
    .host = "backend1";
    .port = "8080";
}

sub vcl_recv {  

  # unless sessionid/csrftoken is in the request, don't pass ANY cookies (referral_source, utm, etc)  
  if (req.request == "GET" && (req.url ~ "^/static" || (req.http.cookie !~ "sessionid" && req.http.cookie !~ "csrftoken" && req.http.cookie !~ "AUTHENTICATION"))) {  
    remove req.http.Cookie;  
  }  

    #normalize accept-encoding to account for different browsers  
    if (req.http.Accept-Encoding) {
        if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
            # No point in compressing these
            remove req.http.Accept-Encoding;
        } elsif (req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
        } elsif (req.http.Accept-Encoding ~ "deflate") {
            set req.http.Accept-Encoding = "deflate";
        } else {
            # unknown algorithm
            remove req.http.Accept-Encoding;
        }
    }  

}  

sub vcl_fetch {  

  # /static and /media files always cached  
  if (req.url ~ "^/static" || req.url ~ "^/media") {  
       unset beresp.http.set-cookie; 
  }

  if (beresp.http.set-cookie !~ "sessionid" && beresp.http.set-cookie !~ "csrftoken" && beresp.http.set-cookie !~ "AUTHENTICATION") {  
    unset beresp.http.set-cookie;
  }

} 

My backend response (without varnish in front) is:

GET / HTTP/1.1
Host: www.mydomain.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:25.0) Gecko/20100101 Firefox/25.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: nb-no,nb;q=0.9,no-no;q=0.8,no;q=0.6,nn-no;q=0.5,nn;q=0.4,en-us;q=0.3,en;q=0.1
Accept-Encoding: gzip, deflate
Cookie: __cfduid=d8f496aef561efd7a30c3d9f909a02cf31384507505064; sessionid=twoq45r21gn341545ohubilyp739r42ee; _ga=GA1.2.382479980.1384507508
Connection: keep-alive

HTTP/1.1 200 OK
Server: cloudflare-nginx
Date: Fri, 15 Nov 2013 14:37:53 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Language, Cookie
X-Frame-Options: SAMEORIGIN
Content-Language: nb
CF-RAY: cdcae94f68105af
Content-Encoding: gzip
like image 603
Tomas Jacobsen Avatar asked Nov 15 '13 08:11

Tomas Jacobsen


People also ask

Is Varnish Cache good?

Varnish is tremendously fast and relies on pthreads to handle a massive amount of incoming requests. The threading model and the use of memory for storage will result in a significant performance boost of your application. If configured correctly, Varnish Cache can easily make your website 1,000 times faster.

How do I know if Varnish Cache is working?

To verify that Varnish is proxying look for the existence of the X-Varnish header in the response. The Age header will be 0 on a cache miss and above zero on a hit. The first request to a page will always be a miss.


1 Answers

Could it be that sessionid is set for every user, even if they are not logged in, and that is preventing Varnish effectively cache pages for anon users?

You are correct. After logout a new session is immediately started and a new session cookie is planted on user's machine. To work around this problem I created a custom logout view that I use with sites I use with Varnish:

from django.conf import settings
from django.contrib.auth.views import logout

def logout_user(request):
    """After logging out some of the cookies should be deleted,
    allowing upstream cache to work effectively."""
    response = logout(request)
    request.session.modified = False  # forces session middleware not to set its own cookie
    response.delete_cookie(settings.CSRF_COOKIE_NAME)
    response.delete_cookie(settings.SESSION_COOKIE_NAME)
    return response

As you can see I force session middleware not to set a new cookie, and then I delete the old cookies (I also get rid of the csrf cookie).

Edit: Also, this code seems completely unnecessary, as Varnish do this automatically for any cookie being set:

  # pass through for anything with a session/csrftoken set  
  if (beresp.http.set-cookie ~ "sessionid" || beresp.http.set-cookie ~ "csrftoken" || beresp.http.set-cookie ~ "AUTHENTICATION") {  
    return (hit_for_pass);  
  } else {  
    return (deliver);  
  }  

Also note that hit_for_pass will make a particular URL not cachable for a couple of minutes (for all users!). Try those three diagnostics:

  1. Clear cookies, remove the code above, restart Varnish, check if Age is still set to 0.
  2. Check headers coming from your backend (nginx). Maybe it is setting the Age value itself, or forcing Varnish to do so using other cache control cookies?
  3. Use varlog to check if those responses are being cached.

Edit 2: Your isvarnishworking.com output shows that the servers sets a cookie called __cfduid. Every time cookie is set Varnish automatically enters hit-for-pass mode (see the code I linked to in the edit above). That is most likely the reason of the problem. I guess that was the reason for the code I deemed unnecessary. I'd try explicitly removing all unknown cookies:

sub vcl_fetch {  
  if (req.url ~ "^/static" || req.url ~ "^/media") {  
    unset beresp.http.set-cookie;  
  }  

  if (beresp.http.set-cookie !~ "sessionid" && beresp.http.set-cookie !~ "csrftoken" && beresp.http.set-cookie !~ "AUTHENTICATION") {  
    unset beresp.http.set-cookie; 
  }
} 
like image 185
Ludwik Trammer Avatar answered Sep 23 '22 14:09

Ludwik Trammer