[GeneralDiscussion] server outage, caching changes

[email protected] (Niall Douglas) Wed, 13 Jan 2010 08:02:07 -0800
Newsgroups gmane.comp.web.zope.zwiki
Message-ID <4B4DEE77.13979.5A70BC9__30702.1394292407$1263398566$gmane$org@s_sourceforge.nedprod.com>
On 13 Jan 2010 at 7:36, Simon Michael wrote:

> Niall, thanks a lot, I had forgotten these pages. This really deserves  
> to be prominent in the zope & plone docs. I'll reread and explore  
> those suggestions more. What makes you prefer nginx + varnish so much  
> to apache (+ varnish, perhaps) ? Note my zope with just two threads  
> and 5000 target cache size likes to get up around 700M. I have a $20/ 
> mo 1G vps from prgmr.com to try, which seems a pretty good deal..

My reasons were purely from benchmarking - nginx had a narrower 
result variance even though it was slightly slower on average than 
its next competitor which in my mind meant it was more predictable 
and therefore more desirable. BTW nginx is many times faster than 
Apache when acting as a simple frontend, so much so that I simply 
wouldn't advise using Apache at all. In the end though, my nginx does 
very little except handling a few static pages, some gzip compression 
of merged CSS and Javascript which Zope cannot currently gzip (this 
makes a BIG difference to bandwidth usage when using Plone) and of 
course HTTPS. I don't think I have the magic nginx config for this 
public, so here it is:

	# Plone's ResourceRegistries can't gzip their compressed output
	# so we need to patch it here. Makes a huge difference to first
	# load times so it's worth the extra server load
	gzip             on;
	gzip_http_version 1.0;
	gzip_buffers     16 8k;
	gzip_min_length  1000;
	gzip_proxied     any;
	gzip_types       application/x-javascript text/css;
	gzip_disable     "MSIE [1-6]\.";
	gzip_vary        on;

If you do follow the nginx route, let me know if you have trouble 
figuring out how to correctly handle 503 errors. It took me weeks of 
experimentation :(

BTW I've attached my varnish default.vcl below. You'll note that it 
manages to "mount" cached copies of Google Finance and the RePeC 
bibliographic database as subdirectories of my websites - this allows 
one to work around cross-domain scripting in AJAX. Such is the power 
of varnish!

I have a low end 1.2Ghz Celeron D dedicated server, yet a nginx + 
varnish config will easily max out my 100Mbit network port before 
maxing out my CPU. I'd doubt though that my CPU would handle a 1Gbit 
max out though.

HTH,
Niall

# This VCL config file is adapted from template.vcl in 
http://pypi.python.org/pypi/plone.recipe.varnish
backend default {
	.host = "localhost";
	.port = "6100";
	.first_byte_timeout = 300s; /* varnish v2.0.3 or later only */
}

backend repec {
        .host = "ideas.repec.org";
        .port = "80";
}

backend google {
	.host = "209.85.229.106"; /*www.google.com";*/
	.port = "80";
}

/* Only permit cluster to purge files from cache */
acl purge {
	"dedi1.nedprod.com";
	"us1.nedproductions.biz";
	"localhost";
}

sub vcl_recv {
	set req.grace = 20s; /* Only enable if you don't mind slightly stale 
content */

	/* Before anything else we need to fix gzip compression */
	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;
		} else if (req.http.Accept-Encoding ~ "gzip") {
			set req.http.Accept-Encoding = "gzip";
		} else if (req.http.Accept-Encoding ~ "deflate") {
			set req.http.Accept-Encoding = "deflate";
		} else {
			# unknown algorithm
			remove req.http.Accept-Encoding;
		}
	}

	if (req.request == "PURGE") {
		if (!client.ip ~ purge) {
			error 405 "Not allowed.";
		}
		/* Always purge by URL rather than going via vcl_hash
		   as it hashes other factors which break purging */
		purge_url(req.url);
		error 200 "Purged";
	}

	/* Rewrite all requests to /repec/cgi-bin/authorref.cgi to 
http://ideas.repec.org/cgi-bin/authorref.cgi */
	if (req.url ~ "^/repec/cgi-bin/authorref.cgi\?handle=" || req.url ~ 
"^/repec/cgi-bin/ref.cgi\?handle=") {
		set req.http.host = "ideas.repec.org";
		set req.url = regsub(req.url, "^/repec", "");
		set req.backend = repec;
		remove req.http.Cookie;
		lookup;
	} else if(req.url ~ "^/googlefinance/finance/converter\?") {
		set req.http.host = "www.google.com";
		set req.url = regsub(req.url, "^/googlefinance", "");
		set req.backend = google;
		remove req.http.Cookie;
		lookup;
	} else {
		set req.backend = default;
		if (req.http.X-Forwarded-Proto == "https" ) {
			set req.http.X-Forwarded-Port = "443";
		} else {
			set req.http.X-Forwarded-Port = "80";
		}
		if (req.http.host ~ "^(www\.|ipv6\.)?([-0-9a-zA-Z]+)\.([a-zA-
Z]+)$") {
			set req.http.host = regsub(req.http.host, "^(www\.|ipv6\.)?([-0-9a-
zA-Z]+)\.([a-zA-Z]+)$", "\1\2.\3");
			set req.url = "/VirtualHostBase/" req.http.X-Forwarded-Proto
				regsub(req.http.host, "^(www\.|ipv6\.)?([-0-9a-zA-Z]+)\.([a-zA-
Z]+)$", "/\1\2.\3:")
				req.http.X-Forwarded-Port
				regsub(req.http.host, "^(www\.|ipv6\.)?([-0-9a-zA-Z]+)\.([a-zA-
Z]+)$", "/\2.\3/\2.\3/VirtualHostRoot")
				req.url;
		}
	}

	if (req.request != "GET" &&
		req.request != "HEAD" &&
		req.request != "PUT" &&
		req.request != "POST" &&
		req.request != "TRACE" &&
		req.request != "OPTIONS" &&
		req.request != "DELETE") {
		/* Non-RFC2616 or CONNECT which is weird. */
		pipe;
	}

	if (req.request != "GET" && req.request != "HEAD") {
		/* We only deal with GET and HEAD by default */
		pass;
	}

	if (req.http.Cookie) {
	        # We only care about the "__ac.*" cookies, used for 
authentication and special persistent p_* cookies.
	        if (req.http.Cookie ~ "__ac.*" ) {
	                pass;
		}
		# Else strip all cookies
		remove req.http.Cookie;
        }

	if (req.http.If-None-Match) {
		pass;
	}

	if (req.url ~ "createObject") {
		pass;
	}

	lookup;
}

sub vcl_pipe {
	# This is not necessary if you do not do any request rewriting.
	set req.http.connection = "close";
}

sub vcl_hash {
	# Normally it hashes on URL and Host but we rewrite the host
	# into a VirtualHostBase URL. Therefore we can hash on URL alone.
	set req.hash += req.url;

	# One needs to include compression state normalised above
	if (req.http.Accept-Encoding) {
		set req.hash += req.http.Accept-Encoding;
	}

	# Differentiate based on login cookie too
	#set req.hash += req.http.cookie;

	return (hash);
}

sub vcl_hit {
	if (req.request == "PURGE") {
		purge_url(req.url);
		error 200 "Purged";
	}
}

sub vcl_miss {
	if (req.request == "PURGE") {
		error 404 "Not in cache";
	}
}

sub vcl_fetch {
	set req.grace = 20s; /* Only enable if you don't mind slightly stale 
content */
	if (req.url ~ "\.svgz$") {
		# Add a Content-Encoding to match compressed SVG
		set obj.http.Content-Type = "image/svg+xml";
		set obj.http.Content-Encoding = "gzip";
		remove obj.http.Content-Length;
		set obj.ttl = 86400s;
		set obj.http.Cache-Control = "max-age=3600";
		deliver;
	}
	if (req.http.host == "ideas.repec.org" || req.http.host == 
"www.google.com") {
		set obj.http.Content-Type = "text/html; charset=utf-8"; /* Correct 
the wrong response */
		set obj.ttl = 86400s;
		set obj.http.Cache-Control = "max-age=3600";
		deliver;
	}
	if (obj.http.Set-Cookie) {
		pass;
	}
	if (req.http.Authorization && !obj.http.Cache-Control ~ "public") {
		pass;
	}
	/* Only use this if you wish to override Plone's CacheFu */
	if (obj.ttl < 3600s) {
		if (obj.http.Cache-Control ~ "(private|no-cache|no-store)") {
			set obj.ttl = 60s; /* Caching everything anonymous for 60s is 
handy for being slashdotted :) */
		} else {
			set obj.ttl = 3600s;
		}
	}
}


sub vcl_error {
    set obj.http.Content-Type = "text/html; charset=utf-8";
    synthetic {"
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>"} obj.status " " obj.response {"</title>
  </head>
  <body>
    <div style="background-color:yellow;">
      <h1>This website is unavailable</h1>
      <p>If you are seeing this page, either maintenance is being 
performed
      or something really bad has happened. Try returning in a few 
minutes.</p>
      <h2>Error "} obj.status " " obj.response {"</h2>
      <p>"} obj.response {"</p>
      <h3>Guru Meditation:</h3>
      <p>XID: "} req.xid {"</p>
      <address>
         <a href="http://www.nedproductions.biz/">ned Productions 
Ltd.</a>
      </address>
    </div>
    <div style="position:fixed;top:0;left:0;width:100%;height:100%;z-
index:-1;">
    <img alt="" src="/static/BBCTestCard.jpg" 
style="width:100%;height:100%" /></div>
  </body>
</html>
"};
    return (deliver);
}









#Below is a commented-out copy of the default VCL logic.  If you
#redefine any of these subroutines, the built-in logic will be
#appended to your code.
#
#sub vcl_recv {
#    if (req.request != "GET" &&
#      req.request != "HEAD" &&
#      req.request != "PUT" &&
#      req.request != "POST" &&
#      req.request != "TRACE" &&
#      req.request != "OPTIONS" &&
#      req.request != "DELETE") {
#        /* Non-RFC2616 or CONNECT which is weird. */
#        return (pipe);
#    }
#    if (req.request != "GET" && req.request != "HEAD") {
#        /* We only deal with GET and HEAD by default */
#        return (pass);
#    }
#    if (req.http.Authorization || req.http.Cookie) {
#        /* Not cacheable by default */
#        return (pass);
#    }
#    return (lookup);
#}
#
#sub vcl_pipe {
#    return (pipe);
#}
#
#sub vcl_pass {
#    return (pass);
#}
#
#sub vcl_hash {
#    set req.hash += req.url;
#    if (req.http.host) {
#        set req.hash += req.http.host;
#    } else {
#        set req.hash += server.ip;
#    }
#    return (hash);
#}
#
#sub vcl_hit {
#    if (!obj.cacheable) {
#        return (pass);
#    }
#    return (deliver);
#}
#
#sub vcl_miss {
#    return (fetch);
#}
#
#sub vcl_fetch {
#    if (!obj.cacheable) {
#        return (pass);
#    }
#    if (obj.http.Set-Cookie) {
#        return (pass);
#    }
#    set obj.prefetch =  -30s;
#    return (deliver);
#}
#
#sub vcl_deliver {
#    return (deliver);
#}
#
#sub vcl_discard {
#    /* XXX: Do not redefine vcl_discard{}, it is not yet supported 
*/
#    return (discard);
#}
#
#sub vcl_prefetch {
#    /* XXX: Do not redefine vcl_prefetch{}, it is not yet supported 
*/
#    return (fetch);
#}
#
#sub vcl_timeout {
#    /* XXX: Do not redefine vcl_timeout{}, it is not yet supported 
*/
#    return (discard);
#}
#
#sub vcl_error {
#    set obj.http.Content-Type = "text/html; charset=utf-8";
#    synthetic {"
#<?xml version="1.0" encoding="utf-8"?>
#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
# "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
#<html>
#  <head>
#    <title>"} obj.status " " obj.response {"</title>
#  </head>
#  <body>
#    <h1>Error "} obj.status " " obj.response {"</h1>
#    <p>"} obj.response {"</p>
#    <h3>Guru Meditation:</h3>
#    <p>XID: "} req.xid {"</p>
#    <address>
#       <a href="http://www.varnish-cache.org/">Varnish</a>
#    </address>
#  </body>
#</html>
#"};
#    return (deliver);
#}


--
forwarded from http://zwiki.org/GeneralDiscussion#msg4B4DEE77.13979.5A70BC9@s_sourceforge.nedprod.com