Jump to content

Unbelievable results with Varnish


Recommended Posts

Posted

The results I got using Varnish have been nothing short of stellar.. while I'm sure some drawbacks will exist overall I'm extraordinarily pleased with the results. Hopefully a few more people can try this technique and publish their own results to help improve this solution.

First of all, mileage may vary on this technique and it can certainly be a part of your overall acceleration strategy. For me, I have a lot of IP.Content stuff as well as a huge amount of bounce traffic from search engines (ie. view one page then leave). For these viewers logging on isn't going to happen..

So to be clear, Varnish is a caching software that I downloaded from http://www.varnish-cache.org/ . It's been used with great success by some extremely large sites on the net and you should be using it too. What it will do in this case is CACHE entire rendered pages in the form that would be viewed by a NON-LOGGED IN user. Once a user chooses to log in, Varnish will pass all requests directly to the web server and all pages will be dynamic.

Here's my server specs - Note that they are pretty dismal because I know I can scale hardware vertically pretty easily, they are also very unclear because the server is a VPS server

Hardware:

  • 1 CPU (1 physical core),
  • 1.7 GB RAM,
  • Linux - Ubuntu Server 9


I'm using PHP 5.2 with eAccelerator, mod_deflate for page compression (gzip turned off in ACP)
The images directory is served through images.mydomain.com and MaxCDN is configured with an origin pull to grab images from my site and cache them. Public is served through public.mydomain.com and is configured the same. (Oh, and MaxCDN is running a $49.95 for 1TB bandwidth special, the the 1TB is not monthly.. it lasts for a year.. so go make your site faster with it) See this article I wrote: http://community.invisionpower.com/resources/articles.html/_/server-resource-management/using-maxcdn-as-a-cdn-for-more-performance-with-minimal-effort-r510

Notes: MySQL is running on the same server, but because this is primarily to test the varnish caching capabilities it will not be a major factor in this test.

To establish a baseline I ran a stress test software that simulated user traffic on a subset of pages on the site WITHOUT VARNISH INVOLVED (forums and a few pages that contained roughly 8 IP.Content blocks with caching turned on). The test was a ramp-up test.. at 10 simulated users the site was pretty snappy, at 20 simulated users there was a little bit of slow-down. The "top" command revealed Apache CPU usage was pretty high though and the whole site slowed to a crawl at 30 simulated users with 12 second response times.

At 40 simulated users the site crapped out.

It was disappointing as typical page response times on a test that simulated requests from Stockholm to my Chicago server measured around 4-5 seconds each.. but the server isn't high end and there is plenty of room to scale out by just getting a faster server.

AFTER VARNISH, a load test of 250 (!) simultaneous users resulted in an average page request delay of only 2.98 seconds. My server load average was 0.04 in top. Pages were being downloaded at a throughput of 150 Mbps. Unfortunately the stress test software has a limitation of 250 simulated users at the level I'm paying for so I don't know what the upper limit is.
  • Replies 202
  • Created
  • Last Reply
Posted

How to Set Up Varnish

I'm not going to get into every little detail but for Ubuntu I just did this from the command line:


sudo curl http://repo.varnish-cache.org/debian/GPG-key.txt | apt-key add -

sudo echo "deb http://repo.varnish-cache.org/debian/ $(lsb_release -s -c) varnish-2.1" >> /etc/apt/sources.list

sudo apt-get update

sudo apt-get install varnish

apt-get install libapache2-mod-rpaf   (because varnish is a proxy, this module will allow you to see the actual client IP in IPS rather than 127.0.0.1)

Then I edited /etc/default/varnish and found the uncommented line that started with "DAEMON_OPTS" and changed *:6081 to *:80 so Varnish would listen on the default HTTP port Then I modified my /etc/apache2/apache2.conf file so that all virtual hosts would run on port 8080 instead of port 80. e.g. Find /etc/apache2/ports.conf and change


NameVirtualHost *:80

Listen 80


to


NameVirtualHost *:8080

Listen 8080


You may need to modify your individual domain configurations to change the port number to 8080 as well. Restart apache (/etc/init.d/apache restart) Then edit the /etc/varnish/default.vcl file and paste in the following:



#

#Default backend definition.  Set this to point to your content

#server.

#

backend default {

.host = "127.0.0.1";

.port = "8080";

}


sub vcl_fetch {

                ## Remove the X-Forwarded-For header if it exists.

        remove req.http.X-Forwarded-For;


        ## insert the client IP address as X-Forwarded-For. This is the normal IP address of the user.

        set    req.http.X-Forwarded-For = req.http.rlnclientipaddr;

        set obj.ttl = 300s;

        set obj.grace = 30s;

                ## Deliver the content

        return(deliver);

}


## Deliver

sub vcl_deliver {

                ## We'll be hiding some headers added by Varnish. We want to make sure people are not seeing we're using Varnish.

              ## Since we're not caching (yet), why bother telling people we use it?

        remove resp.http.X-Varnish;

        remove resp.http.Via;

        remove resp.http.Age;

        remove resp.http.X-Powered-By;

}


sub vcl_recv {

# If they are or were logged in, let them pass through to the web server

if (req.http.cookie && req.http.cookie ~ "member_id") {

    pass;

   }

else {

# Clear out the cookies as if we were an unauthenticated user and hit the cache

  unset req.http.cookie;

  set req.grace = 15s;

}

}


sub vcl_hash {

if (req.http.cookie && req.http.cookie ~ "member_id") { 

set req.hash += "auth"; 

}

    set req.hash += req.url;


 hash;

}


Posted

I wrote Matt with the hope that he will change one of the core source files..


Here is a change to that will enable page-level varnish cache control:

Modify \admin\sources\classes\output\formats\html\htmlOutput.php:


			if ($this->settings['aux_headers']) {


				$auxhdr =  $this->settings['aux_headers'];


				if (is_array($auxhdr)) {

					foreach ($auxhdr as $header => $value) {

						header ($header . ":" . $value);

					}

				}

			}


Full Method:


	/**

	 * Prints any header information for this output module

	 *

	 * @access	public

	 * @return	void		Prints header() information

	 */

	public function printHeader()

	{

		//-----------------------------------------

		// Start GZIP compression

        //-----------------------------------------


		if ( $this->settings['disable_gzip'] != 1 )

		{

		    $buffer = "";


		    if ( count( ob_list_handlers() ) )

		    {

				$buffer = ob_get_contents();

				ob_end_clean();

			}


			if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) AND strstr( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') )

			{

				@ob_start('ob_gzhandler');

			}

			else

			{

				@ob_start();

			}


			print $buffer;

		}


		if ( $this->settings['print_headers'] )

    	{

			if ( isset( $_SERVER['SERVER_PROTOCOL'] ) AND strstr( $_SERVER['SERVER_PROTOCOL'], '/1.0' ) )

			{

				header("HTTP/1.0 " . $this->_headerCode . ' ' . $this->_headerStatus );

			}

			else

			{

				header("HTTP/1.1 " . $this->_headerCode . ' ' . $this->_headerStatus );

			}


			/* Forcing a download? */

			if ( $this->_forceDownload )

			{

				header( "Content-type: unknown/unknown" );

				header( "Content-Disposition: attachment; filename=\"" . IPSText::alphanumericalClean( $this->registry->output->getTitle() )  . ".html\"" );

			}

			else

			{

				header( "Content-type: text/html;charset=" . IPS_DOC_CHAR_SET );

			}


			if ( $this->settings['nocache'] )

			{

				$expires	= ( $this->_headerExpire ) ? gmdate( "D, d M Y H:i:s", time() + $this->_headerExpire ) . " GMT" : gmdate( "D, d M Y H:i:s", time() - 86400 ) . " GMT";

				$maxAge		= $this->_headerExpire;

				$nocache	= ( ! $this->_headerExpire ) ? 'no-cache,' : '';


				header( "Cache-Control:  ". $nocache . "must-revalidate, max-age=" . $maxAge );

				header( "Expires: " . $expires );


				if ( ! $this->_headerExpire )

				{

					header( "Pragma: no-cache" );

				}

			}


// ============================== NEW CODE ======================================

			if ($this->settings['aux_headers']) {


				$auxhdr =  $this->settings['aux_headers'];


				if (is_array($auxhdr)) {

					foreach ($auxhdr as $header => $value) {

						header ($header . ":" . $value);

					}

				}

			}

// ============================== END NEW CODE ======================================


        }

	}


Posted

How the above code might be used. Add this to any template, database display template, etc.

// Instruct Varnish to cache this page-level content for 600 seconds for non-logged in users


<php>$this->settings['aux_headers'] = Array("Varnish-Control" => "600");</php>

Then save this as your default.vcl varnish configuration and restart varnish:


#Default backend definition.  Set this to point to your content

#server.

#

backend default {

.host = "127.0.0.1";

.port = "8080";

}


sub vcl_fetch {

                ## Remove the X-Forwarded-For header if it exists.

        remove req.http.X-Forwarded-For;

        unset obj.http.Server;

        set obj.http.Server = "IPS";


                ## insert the client IP address as X-Forwarded-For. This is the normal IP address of the user.

        set    req.http.X-Forwarded-For = req.http.rlnclientipaddr;


        if (obj.http.Varnish-Control) {

                C{

                char *ttl;

                ttl = VRT_GetHdr(sp, HDR_OBJ, "\020Varnish-Control:");

                VRT_l_obj_ttl(sp, atoi(ttl));

                }C

                remove obj.http.Varnish-Control;

        }


        set obj.ttl = 300s;

        set obj.grace = 30s;

                ## Deliver the content

        return(deliver);

}


## Deliver

sub vcl_deliver {

                ## We'll be hiding some headers added by Varnish. We want to make sure people are not seeing we're using Varnish.

              ## Since we're not caching (yet), why bother telling people we use it?

        remove resp.http.X-Varnish;

        remove resp.http.Via;

        remove resp.http.Age;


                ## We'd like to hide the X-Powered-By headers. Nobody has to know we can run PHP and have version xyz of it.

        remove resp.http.X-Powered-By;

}


sub vcl_recv {

if (req.http.cookie && req.http.cookie ~ "member_id") {

    pass;

   }

else {

  unset req.http.cookie;

  set req.grace = 15s;

}

}


sub vcl_hash {

if (req.http.cookie && req.http.cookie ~ "member_id") { set req.hash +=

"auth"; }

    set req.hash += req.url;


 hash;

}




Posted

Am I looking at proprietary source code posted in a public forum?




This forum is only available to those logged in with a customer account and at least it was a public method (small joke). But yeah, you can't read that post without already having access to the source code. PM me or hit the moderator report button if you have issues in the future as I don't want to go off topic on this thread.
Posted

Here is an updated default.vcl, I noticed a problem with the IPS software setting a cookie to member_id=0 for guests automatically.. so checking just for member_id wasn't enough of an indicator that the person was logged on. In this case we want to treat guests as not special and they would view content as if they weren't logged in.



backend default {

.host = "127.0.0.1";

.port = "8080";

}


sub vcl_fetch {

		## Remove the X-Forwarded-For header if it exists.

        remove req.http.X-Forwarded-For;

	unset obj.http.Server;

	set obj.http.Server = "IPS";


		## insert the client IP address as X-Forwarded-For. This is the normal IP address of the user.

        set    req.http.X-Forwarded-For = req.http.rlnclientipaddr;


	if (obj.http.Varnish-Control) {

		C{

		char *ttl;

		ttl = VRT_GetHdr(sp, HDR_OBJ, "\020Varnish-Control:");

		VRT_l_obj_ttl(sp, atoi(ttl));

		}C

		remove obj.http.Varnish-Control;

	}

        else {

		set obj.ttl = 300s;

	}


	set obj.grace = 30s;

		## Deliver the content

        return(deliver);

}


## Deliver

sub vcl_deliver {



		## We'll be hiding some headers added by Varnish. We want to make sure people are not seeing we're using Varnish.

              ## Since we're not caching (yet), why bother telling people we use it?

        remove resp.http.X-Varnish;

        remove resp.http.Via;

        remove resp.http.Age;


		## We'd like to hide the X-Powered-By headers. Nobody has to know we can run PHP and have version xyz of it.

        remove resp.http.X-Powered-By;


        if (obj.hits > 0) {

                set resp.http.X-Cache = "HIT";

        } else {

                set resp.http.X-Cache = "MISS";

        }


}


sub vcl_recv {

if (req.http.cookie && (req.http.cookie ~ "member_id" && !(req.http.cookie ~ "member_id=0"))) {

   	 pass;

   }

else {

  unset req.http.cookie;

  set req.grace = 15s;

}

}


sub vcl_hash {

if (req.http.cookie && req.http.cookie ~ "member_id") { set req.hash +=

"auth"; }

    set req.hash += req.url;


 hash;

}

Posted

Agreed.. If I ever go anything bigger than a vps I'll be sure to check out Varnish. Right now all I have are two VPS's and several shared but the VPS's are so unreliable due to a horrible host that I don't have anything important on them so I'm not in a huge need for Varnish.. but with the goals I have in mind for my site I"m thinking Varnish could be the way to go. Thanks Mat.

Posted

I know this is getting a little crazy here, but I'm in the process of trying a few things. One thing I want to attempt is to work out the ability to do Varnish caching using ESI for IP.Content pages where you use parse database tags. Otherwise, I'm trying to figure out a good way to do subpage caching using varnish. This may be possible.. and if so it may greatly accelerate the site.

I feel like in order to truly scale this software more, a second MySQL driver with read/write splitting should be created, that full page caching should be written into IP.Content for database parsed pages.. there is a lot with this software that is in no way customized to the user, but it's impossible for IPS to know what parts of the site you are going to customize to the end user.

To that end it would be pretty awesome do be able to see things like <esi:include src="/forums/index"/> embedded in the page and have varnish maintain a cache of the forum index even for logged in users. Although I don't know how truly practical this is. However, this would allow you to accelerate this software to potentially accommodate a thousand simultaneous users without breaking a sweat (at least that would be nice). Losing some functionality in exchange for performance is always a tradeoff when scaling.

THIS NEW VCL configuration will cache static content regardless of whether the user is logged in (js, css, jpg, gif, png files)



#Default backend definition.  Set this to point to your content

#server.

#

backend default {

.host = "127.0.0.1";

.port = "8080";

}


sub vcl_fetch {

		## Remove the X-Forwarded-For header if it exists.

        remove req.http.X-Forwarded-For;

	unset obj.http.Server;

	set obj.http.Server = "IPS";


		## insert the client IP address as X-Forwarded-For. This is the normal IP address of the user.

        set    req.http.X-Forwarded-For = req.http.rlnclientipaddr;


	if (obj.http.Varnish-Control) {

		C{

		char *ttl;

		ttl = VRT_GetHdr(sp, HDR_OBJ, "\020Varnish-Control:");

		VRT_l_obj_ttl(sp, atoi(ttl));

		}C

		remove obj.http.Varnish-Control;

	}

        else {

		set obj.ttl = 300s;

	}


	set obj.grace = 30s;


	if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") {

   		unset obj.http.set-cookie;

	}


		## Deliver the content

        return(deliver);

}


## Deliver

sub vcl_deliver {



		## We'll be hiding some headers added by Varnish. We want to make sure people are not seeing we're using Varnish.

              ## Since we're not caching (yet), why bother telling people we use it?

        remove resp.http.X-Varnish;

        remove resp.http.Via;

        remove resp.http.Age;


		## We'd like to hide the X-Powered-By headers. Nobody has to know we can run PHP and have version xyz of it.

        remove resp.http.X-Powered-By;


        if (obj.hits > 0) {

                set resp.http.X-Cache = "HIT";

        } else {

                set resp.http.X-Cache = "MISS";

        }


	set resp.http.X-Rick-Would-Never = "Let you down";  #  You can probably remove this ;)

}


sub vcl_recv {


if (req.http.cookie && (req.http.cookie ~ "member_id" && !(req.http.cookie ~ "member_id=0"))) {


	if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") {

    		lookup;

 	}

	else 

	{

   	 	pass;

	}


   }

else {

  unset req.http.cookie;

  set req.grace = 15s;

}


}


sub vcl_hash {

if (req.http.cookie && req.http.cookie ~ "member_id") { set req.hash +=

"auth"; }

    set req.hash += req.url;


 hash;

}



  • 2 weeks later...
Posted

If you want to avoid modifying the IPS code you can control how long Varnish caches within the VCL as well.

Here are tests you can use in the VCL to check for a pass condition.

req.url ~ "(section=markasread|section=login|register)" You should also include in here any iframe/ESI scripts used for managing Varnish caching.
req.http.Cookie ~ "member_id=" && req.http.Cookie !~ "member_id=(0|-1)"

We have ran up to 8,000+ concurrent users at one time with Varnish in play on a quad core 2.2ghz machine. We also retain full functionality for logged in and out users by way of a custom script included either in an iframe or ESI, which also handles guest count and topic view counts.

Posted

We also retain full functionality for logged in and out users by way of a custom script included either in an iframe or ESI, which also handles guest count and topic view counts.





Hello,

That's great to ear,
Would you be kind to share your vcl?

Thanks

Posted

Hello,



That's great to ear,


Would you be kind to share your vcl?



Thanks




I am not allowed to share scripts developed on company time, sorry!

I can say that the example script above is very similar at the base level and would work much better with the checks I provided above.
  • 2 weeks later...
Posted

Hello,

Just want to share my custom setup to handle topics and forums for now.
This setup uses ESI to update topics views of cached content.
I don't have a big board and this is only for playing around, but with this setup I can serve topics in 1/2 time a was serving before, with an hit rate of 50%.

A short notice ... my current setup is: nginx -> varnish -> apache2+mod_php

backend default {

  .host = "127.0.0.1";

  .port = "8080";

}


sub vcl_recv {

  if ((req.http.Cookie ~ "member_id=" && req.http.Cookie !~ "member_id=(0|-1)") || req.http.Cookie ~ "guestSkinChoice=")  {

    return(pass);

  } else {

    if (req.url ~ "^/forum" || req.url ~ "^/topic" || req.url ~ "^/public") {

      unset req.http.cookie;

      set req.grace = 15s;

    }

  }

}


sub vcl_fetch {

  if ((req.http.Cookie ~ "member_id=" && req.http.Cookie !~ "member_id=(0|-1)") || req.http.Cookie ~ "guestSkinChoice=") {

    return(pass);

  } else {

    if (req.url ~ "^/public") {

      unset beresp.http.set-cookie;

      set beresp.ttl = 3600s;

      set beresp.grace = 30s;

    }

    if (req.url ~ "^/forum") {

      unset beresp.http.set-cookie;

      set beresp.ttl = 300s;

      set beresp.grace = 30s;

    }

    if (req.url ~ "^/topic") {

      esi;

      unset beresp.http.set-cookie;

      set beresp.ttl = 600s;

      set beresp.grace = 30s;

    }

  }

}


sub vcl_deliver {

  if (obj.hits > 0) {

    set resp.http.X-Cache = "HIT";

  } else {

    set resp.http.X-Cache = "MISS";

  }

}

php file to update topic views (update_topicviews.php):

<?

require_once( './conf_global.php' );


$link = mysql_connect($INFO['sql_host'], $INFO['sql_user'], $INFO['sql_pass']);


@mysql_select_db($INFO['sql_database'], $link);

@mysql_query("INSERT INTO topic_views (views_tid) VALUES ('" . $_GET['tid'] . "')");


mysql_close($link);

?>

add this to the beginning of topicViewTemplate:

<if test="!$this->memberData['member_id']">

  <esi:include src="/update_topicviews.php?tid={$topic['tid']}" />

</if>




I would like to have some comments from you.

Posted

Here is my most up-to-date config.. some things to watch out for are if guests can use either a default skin or a mobile skin. For the previous config, this is addressed by just not caching pages if they choose an alternate skin. But I'm not 100% that the skin cookie will be set for mobile browsers by default. We didn't figure this out until stumbling upon a cached mobile version of our home page showing up in our pc browser. We also added custom error pages to handle some errors if we decided to take down apache in /var/www/errors/###.html (where ### is an error code, 503).

We also had a problem with varnish actually caching redirects.. so some people who were not logged in would get caught in an infinite redirect for a particular URI because Varnish cached the redirect.



backend default { 

.host = "127.0.0.1"; 

.port = "8081"; 

} 


sub vcl_fetch { 


	// Dont cache 302 redirects and anything else other than what should be cached

	if (obj.status != 200 && obj.status != 404) {

		set obj.ttl = 0s;

		return (deliver);

	}


        ## Remove the X-Forwarded-For header if it exists. 

        remove req.http.X-Forwarded-For; 

        unset obj.http.Server; 

        set obj.http.Server = "IPS"; 


        ## insert the client IP address as X-Forwarded-For. This is the normal IP address of the user. 

        set    req.http.X-Forwarded-For = req.http.rlnclientipaddr; 


        if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") { 

                unset obj.http.set-cookie; 

        } 


        ## Deliver the content 

        return(deliver); 

} 


## Deliver 

sub vcl_deliver { 



        ## We'll be hiding some headers added by Varnish. We want to make sure people are not seeing we're using Varnish. 

        ## Since we're not caching (yet), why bother telling people we use it? 

        remove resp.http.X-Varnish; 

        remove resp.http.Via; 

        remove resp.http.Age; 


        ## We'd like to hide the X-Powered-By headers. Nobody has to know we can run PHP and have version xyz of it. 

        remove resp.http.X-Powered-By; 


        if (obj.hits > 0) { 

                set resp.http.X-Cache = "HIT"; 

        } else { 

                set resp.http.X-Cache = "MISS"; 

        } 


} 


sub vcl_recv { 


set req.http.X-Device = "pc";

if (req.http.User-Agent ~ "iPad" || req.http.User-Agent ~ "iP(hone|od)" || req.http.User-Agent ~ "Android" || req.http.User-Agent ~ "SymbianOS" || req.http.User-Agent ~ "^BlackBerry" || req.http.User-Agent ~ "^SonyEricsson" || req.http.User-Agent ~ "^Nokia" || req.http.User-Agent ~ "^SAMSUNG" || req.http.User-Agent ~ "^LG")

{

	set req.http.X-Device = "mobile";

}


if (req.request == "POST") {

	pass;

}


if (req.url ~ "(section=markasread|section=login|register)") {

	pass;

}

else {


if (req.http.cookie && (req.http.cookie ~ "member_id" && req.http.cookie !~ "member_id=(0|-1)")) { 


        if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") { 

                lookup; 

        } 

        else  

        { 

                pass; 

        } 


   } 

else { 

  unset req.http.cookie; 

  set req.grace = 15s; 

} 


}


} 


sub vcl_hash { 

	if (req.http.cookie && req.http.cookie ~ "member_id") 

	{ 

		set req.hash += "auth"; 

	}


	set req.hash += req.url; 

	set req.hash += req.http.X-Device;


 hash; 

}



sub vcl_error {

set obj.http.Content-Type = "text/html; charset=utf-8";


if ( obj.status >= 500 && obj.status <= 505) {

C{

#include <stdio.h>

#include <string.h>


FILE * pFile;

char content [100];

char page [10240];

char fname [50];


page[0] = '\0';

sprintf(fname, "/var/www/errors/%d.html", VRT_r_obj_status(sp));


pFile = fopen(fname, "r");

while (fgets(content, 100, pFile)) {

strcat(page, content);

}

fclose(pFile);

VRT_synth_page(sp, 0, page, "<!-- XID: ", VRT_r_req_xid(sp), " -->", vrt_magic_string_end);

}C

} else {

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);

}



Posted

Let's hope that you will fix all problems and give us the best possible from Varnish to use it on Invision :)



Thank you




Your WHM panel based, So why do you not use the WHM plugin which is Globally rather than per account ?

http://www.unixy.net/varnish/



Only makes sense. :whistle:

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...