While talking about WordPress and it’s abysmal performance in high traffic situations to a client, we started looking back at Varnish and other solutions to keep their machine responsive. Since most of the caching solutions generate a page, serve it and cache it, posts and comments tend to lag behind the cache. db-cache does work around this by caching the query objects so that the pages can be generated more quickly and does expire the cache when tables are updated, but, its performance is still lacking. Using APC’s opcode cache or memcached just seemed to add complexity to the overall solution.
Sites like perezhilton.com appear to run behind multiple servers running Varnish, use wp-cache, move the images off to a CDN which results in a 3 request per second site with an 18 second pageload. Varnish’s cache always shows an age of 0 meaning Varnish is acting more as a load balancer than a front-end cache.
Caching isn’t without its downside. Your weblogs will not represent the true traffic. Since Varnish intercepts and serves requests before they get to the backend, those hits never hit the log. Forget pageview/postview stats (even with addons) because the addon won’t get loaded except during caching. Certain Widgets that rely on cookies or IP addresses will need to be modified. A workaround is to use a Text Box Widget and do an ESI include of the widget. For this client, we needed only some of the basic widgets. The hits in the apache logs will come from an IP of 127.0.0.1. Adjust your apache configuration to show the X-Forwarded-For IP address in the logs. If you truly need statistics, you’ll need to use something like Google Analytics. Put their code outside your page elements so that waiting for that javascript to load doesn’t slow down the rendering in the browser.
The test site, http://varnish.cd34.com/ is running Varnish 2.0.4, Apache2-mpm-prefork 2.2.11, Debian/Testing, WordPress 2.8.2. I’ve loaded the default .xml import for testing templates so that there were posts with varied dates and construction in the site. To replicate the client’s site, the following Widgets were added the sidebar: Search, Archives, Categories, Pages, Recent Posts, Tag Cloud, Calendar. Calendar isn’t in the existing site, but, since it is a very ‘expensive’ SQL query to run, it made for a good benchmark.
The demo site is running on:
model name : Intel(R) Celeron(R) CPU 2.40GHz
stepping : 9
cpu MHz : 2400.389
cache size : 128 KB
with a Western Digital 80gb 7200RPM IDE drive. Since all of the benchmarking was done on the same machine without any config changes taking place between tests, our benchmarks should represent as even a test base as we can expect.
Regrettably, our underpowered machine couldn’t run the benchmark with 50 concurrent tests, nor, could it run the benchmarks with the Calendar Widget enabled. In order to get apachebench to run, we had to bump the number of requests down and reduce the number of concurrent tests.
These results are from Apache without Varnish.
Server Software: Apache
Server Hostname: varnish.cd34.com
Server Port: 80
Document Path: /
Document Length: 43903 bytes
Concurrency Level: 10
Time taken for tests: 159.210 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 4408200 bytes
HTML transferred: 4390300 bytes
Requests per second: 0.63 [#/sec] (mean)
Time per request: 15921.022 [ms] (mean)
Time per request: 1592.102 [ms] (mean, across all concurrent requests)
Transfer rate: 27.04 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 2 7.0 0 25
Processing: 14785 15863 450.2 15841 17142
Waiting: 8209 8686 363.4 8517 9708
Total: 14785 15865 451.4 15841 17142
Percentage of the requests served within a certain time (ms)
50% 15841
66% 15975
75% 16109
80% 16153
90% 16628
95% 16836
98% 17001
99% 17142
100% 17142 (longest request)
Normally we would have run the Varnish enabled test without the Calendar Widget, but, I felt confident enough to run the test with the widget in the sidebar. Varnish was configured with a 12 hour cache (yes, I know, I’ll address that later) and the ESI Widget was loaded.
Server Software: Apache
Server Hostname: varnish.cd34.com
Server Port: 80
Document Path: /
Document Length: 45544 bytes
Concurrency Level: 50
Time taken for tests: 18.607 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 457980000 bytes
HTML transferred: 455440000 bytes
Requests per second: 537.44 [#/sec] (mean)
Time per request: 93.034 [ms] (mean)
Time per request: 1.861 [ms] (mean, across all concurrent requests)
Transfer rate: 24036.81 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 1.8 0 42
Processing: 1 92 46.2 105 451
Waiting: 0 91 45.8 104 228
Total: 2 93 46.0 105 451
Percentage of the requests served within a certain time (ms)
50% 105
66% 117
75% 123
80% 128
90% 142
95% 155
98% 171
99% 181
100% 451 (longest request)
As you can see, even with the aging hardware, we went from .63 requests per second to 537.44 requests per second.
But, more about that 12 hour cache. The ESI Widget uses an Edge Side Include to include the sidebar into the template. Rather than just cache the entire page, we instruct Varnish to cache the page and include the sidebar. As a result, when a person surfs the site and goes from the front page to a post page, the sidebar doesn’t need to be regenerated when they go to the 2nd page. With wp-cache, it would have regenerated the sidebar Widgets and then cached the resulting page. Obviously, that 12 hour cache is going to affect the usability of the site, so, ESI widget purges the sidebar, front page and post page any time a post is updated or deleted or commented on. Voila, even with a long cache time, we are presented with a site that is dynamic and not delayed until wp-cache’s page cache expires. As this widget is a concept, I’m sure a little intelligence can be added to prevent the excessive purging in some cases, but, it does handle things reasonably well. There are some issues not currently handled with the ESI including how to handle users that are logged for comments. With some template modifications, I think those pieces can be handled with ESI to provide a lightweight method for the authentication portion.
While I have seen other sites mention Varnish and other methods to keep your wordpress installation alive in high traffic, I believe this approach is a step in the right direction. With the ESI widget, you can focus on your site, and let the server do the hard work. This methodology is based on a CMS that I have contemplated writing for many years, though, using Varnish rather than static files.
It is a concept developed in roughly four hours including the time to write the widget and do the benchmarking. It isn’t perfect, but does address the immediate needs of the one client. I think we can consider this concept a success.
If you don’t have the ability to modify your system to run Varnish, then you would be limited to running wp-cache and db-cache. If you can connect to a memcached server, you might consider running Memcached for WordPress as it will make quite a difference as well.
This blog site, http://cd34.com/blog/ is not running behind Varnish. To see the Varnish enabled site with ESI Widget, go to http://varnish.cd34.com/
Software Mentioned:
* Varnish ESI and Purge and Varnish’s suggestions for helping WordPress
* WordPress
* wp-cache
* db-cache
Sites used for reference:
* Supercharge WordPress
* SSI, Memcached and Nginx (with mentions of a Varnish/ESI configuration)
Varnish configuration used for ESI-Widget:
backend default {
.host = "127.0.0.1";
.port = "81";
}
sub vcl_recv {
if (req.request == "PURGE") {
purge("req.url == " req.url);
}
if (req.url ~ "\.(png|gif|jpg|ico|jpeg|swf|css|js)$") {
unset req.http.cookie;
}
if (!(req.url ~ "wp-(login|admin)")) {
unset req.http.cookie;
}
}
sub vcl_fetch {
set obj.ttl = 12h;
if (req.url ~ "\.(png|gif|jpg|ico|jpeg|swf|css|js)$") {
set obj.ttl = 24 h;
} else {
esi; /* Do ESI processing */
}
}