ESI - Full page caching with Symfony2
Launched about a month ago, techup.ch runs on the Symfony2 PHP framework, which is still undergoing heavy development but is already a great framework.
Full page caching basics
Don't get me wrong, the framework is fast, pages are rendered by our fairly modest server in 40-50ms on average, so it hardly needs optimization. However I still wanted to try and squeeze more speed out of it, and also get a chance to play with cool stuff, so I decided to implement full page caching with ESI into the application.
The way this works is that you typically install some reverse proxy like Varnish, which will sit between the web and your http server. More complex setups might include another http server in front of varnish to gzip output but I won't go into details on that in this post. The purpose of the reverse proxy is that it will cache the output of your application, for as long as you specify in your Cache-Control header. Once a page is cached, it will just return the output to the clients straight, without ever hitting your http server, php, or your application. Needless to say this is ideal for performance. Symfony2 is a great match for this type of cache because it's supported natively as I'll show, and it also implements a reverse proxy layer in php, that can be used for development or on hostings where you can't have access to Varnish. It acts just the same and is automatically turned off if an ESI-capable proxy is added in front of php.
ESI awesomeness
Of course the issue with caching the entire output is that most sites have areas with dynamic content, especially when users are logged in. This is where ESI comes into play. ESI stands for Edge Side Includes, and is a standard that defines a way to tell reverse proxies how to assemble pages out of smaller bits, that can be cached for various amounts of time, or fully dynamic.
So if you take for example an event page on techup, you have two user-dependent areas, the "login with twitter" button, which turns into "@username" once you're logged in, and the "attend" button is also showing attend or unattend depending on the user viewing the page. Those two areas are ESI includes. What this means for the reverse proxy is that it will first try and fetch the main page content out of its cache, and if found, it will then process the <esi:include src="http://..." /> tags that it finds. Those tags contain the url to a sub-component of the page. So one url will point to an action in one of my controllers that only outputs an attend button, green or red depending on the user viewing it. The rest of the page is still taken out of the cache.
Each of those sub-components have their own Cache-Control header, which means that you can composite a page with various components that expire after various durations.
The way this is done in Symfony2 is pretty straightforward. Your controller actions must always return a response object, and all you need for the reverse proxy cache length is to set the shared max age of the response - beware, max age will apply to the entire page, so you really want to use the shared variant. It's as simple as calling $response->setSharedMaxAge(3600);, 3600 being the length in seconds.
In your templates, if you use Twig, and you really should with Symfony2, it is also quite easy to define an <esi:include /> tag. You call out the controller/action that you want to execute, give it some parameters, and specify it as being standalone which means it's an ESI include, for example {% render 'FooBundle:Default:attendButton' with ['event_id': event.id], ['standalone': true] %}. For more info on how to set that up feel free to go read the Symfony2 docs on the topic.
Invalidation woes
The tricky part, which is also a slightly controversial topic, is invalidation. In theory if you say that a page or sub-component is cache-able for X seconds, you should just live with it and let it be cached, even if the data changed. Now this is an acceptable downside on really high traffic sites, or in cases where only admins publish content and it doesn't really matter if it takes a few seconds/minutes to appear to the end users. But I like to give our users feedback when they add or change data, and I think they should see it straight away, so I decided to invalidate the cached pages in the proxy whenever the data is modified.
I will refer you to the docs as to how to actually setup support for purging (invalidating) caches in your proxy of choice, no point in repeating it all here, but what I want to share is the approach I took on actually managing invalidation. As you may know, invalidation can quickly get very tricky to handle. So what I did is just built centralized methods that contain all the invalidation logic for one domain model. When that model changes, it's passed to the matching method and all the urls that will render it are purged. This at least allows you to keep a good overview of the pages that are affected, and gives you a single point of entry to make adjustments to those invalidation rules.
// src/Application/FooBundle/Controller/FooController.php
protected function invalidateEvent($event)
{
$args = array('event' => $event->getId(), 'title' => $event->getSlug());
$this->invalidate('viewEvent', $args);
$this->invalidate('home');
}
protected function invalidate($route, $parameters = array())
{
$url = $this->router->generate($route, $parameters, true);
$context = stream_context_create(array('http'=>array('method'=>'PURGE')));
$stream = fopen($url, 'r', false, $context);
fclose($stream);
}
This example implementation will do a PURGE request to the site URL. This only scales if you have one single Varnish instance though. I assume you must do a PURGE request on each if you have a redundant setup, but in this case it might become cleaner to use an external job queue like Gearman to execute those outside of php.
There are a few gotchas you should consider, especially if you use the Symfony2 reverse proxy and not Varnish. First of all one thing that is fairly obvious is that you must prevent anyone from purging stuff, otherwise attackers could DDoS you with PURGE requests and make your load skyrocket. The second issue is that if you return a 404 code for "Not purged" a.k.a the page wasn't cached, fopen() will throw a php warning, which is really not that nice. For this reason, and since I don't want to care whether the purge happened or not for now, I chose to just respond always with a 200. It could be handled nicer with curl though, if you really need to have a proper response code to your PURGE requests.
// app/AppCache.php
protected function invalidate(Request $request)
{
if ($_SERVER['SERVER_ADDR'] !== $request->getClientIp() || 'PURGE' !== $request->getMethod()) {
return parent::invalidate($request);
}
$response = new Response();
if (!$this->store->purge($request->getUri())) {
$response->setStatusCode(200, 'Not purged');
} else {
$response->setStatusCode(200, 'Purged');
}
return $response;
}
The results
It sounds nice and all, but is it actually working?
I used JMeter to benchmark the site with and without reverse proxy. Note that I used the integrated Symfony cache layer and not Varnish, so the results would be even better with Varnish since it's written in C and doesn't have to to hit apache and php on every request.
Before: / => 63req/sec /86/rails-hock => 100req/sec /api/events/upcoming.json => 70req/sec /api/event/10.json => 120req/sec After: / => 200req/sec * /86/rails-hock => 230req/sec /api/events/upcoming.json => 100req/sec * /api/event/10.json => 800req/sec * my 20mbps internet line was the bottleneck for those because they have too large response bodies
In short: Holy crap. Now for the two first pages tested, the improvement is "modest" because they include sub-components which are not cacheable, so they always require some full framework cycles. But the last one which is from the API is just amazing, with 8 times more requests processed per second.
All I can say to conclude is that this is worth playing with, and that Symfony2 really doesn't disappoint with regard to speed. If you have any experience with that kind of setup and want to add anything feel free to do so in the comments, questions are also welcome.
December 09, 2010 // PHP
Post a comment:
Formatting: you may use [code php] [/code] (or other languages) for code blocks, links are automatically linked. <strong>, <em> and <blockquote> html tags are allowed without nesting, the rest will be escaped.


2012-01-20 11:01:26
prescription version of cipralex d
Home remedy for dog constipation. dilantin and leg pain, http://dynamic-boutique-events.com/dynamic/showthread.php?tid=97728 kenalog no rx required What are the side effects of estradiol in dogs too much synthroid http://awillage.com/forum/showthread.php?tid=9387 cash price for tricor fungal infections natural remedies, generic norvasc available http://smkn27jakarta.com/forum/showthread.php?tid=120947 metrogel no prescription required eye allergies & eye patches Shellfish allergy itchy white pimples. http://www.kkspionier.pl/mybb/thread-2923.html purchase nonprescription generic accupril Cilostazol 115 mg canine allergies food2012-01-20 11:03:52
price for generic ovral
Best site, http://fileserver.hostoi.com/Forum/showthread.php?tid=18014 buy lumigan american pharmacy, 17912,2012-01-20 11:31:45
how much ramipril online
caverta in uk. wellbutrin zoloft treatment night sweats, http://www.hi5sms.in/wp-content/plugins/zingiri-forum/mybb/showthread.php?tid=126448 key buy fougera cheap effectiveness of oxcarbazepine Nizoral for yeast inections. http://kristin-brent.com ajanta pharma indocin sales buspar has helped me treatment for face herpes lip herpes2012-01-20 12:01:25
cheap floxstat soma
Cool site, http://www.pmz.tv/wp-content/plugins/zingiri-forum/mybb/showthread.php?tid=451277 cheapest place to buy co-trimoxazole 100, qpafj,2012-01-20 12:04:03
canadian fluvoxamine free shipping
Cool site, http://www.pmz.tv/wp-content/plugins/zingiri-forum/mybb/showthread.php?tid=479099 single dose pramipexole online, 86776,2012-01-20 12:06:39
tramadol kenalog saturday delivery
sun allergy rash Can you take naproxen with vicodon http://ccgi.surveysoft.plus.com/PTKDC/forum/showthread.php?tid=2653 adalat help to purchase medicine how to get addicted to motrin Home giardia test2012-01-20 12:34:25
lipitor 40 mg cost
Cool site, http://sokalarab.com/market/thread-18714.html overnight imipramine delivery all states, 875302,2012-01-20 12:39:27
sildenafil d purchase
3, http://dynamic-boutique-events.com/dynamic/showthread.php?tid=103559 cheapest azelastine fedex, tusjv,2012-01-20 13:09:46
black-cialis discount presciptions
3, http://www.rosteer.com/entry.php?429-Buy-Lidocaine-Sale.-Pain-Analgesic-Buy-Lidocaine-Paste ordering lidocaine collect on delivery, >:)),2012-01-20 13:12:01
buy orapred from mexico online
1, http://www.forseniorsonly.org/forums/showthread.php?tid=379864 savella xp cheap, hqdoz,2012-01-20 13:14:34
is hydrochlorothiazide an otc drug
1, http://level1radio.com/mybb/showthread.php?tid=30505 curam china buy, 37327,2012-01-20 13:31:36
cheap panadol world cruise
Cool site, http://www.livecashonline.com/forum/showthread.php?tid=34731 buy tofranil low cost, mgmvd,2012-01-20 13:36:23
price for 200 mg modafinil
4, http://batainwatain.net/entry.php?907-Co-trimoxazole-European-Pharmacy.-Travelers-Diarrhea-Bacterial-Infections-Cystiti buy co-trimoxazole compound, 547,2012-01-20 13:41:31
discount code for benicar america
Soy oil allergy Pictures of women with genital herpes http://knowyourpet.co.uk/entry.php?480-Cod-Benicar-Cheap-Mastercard-High-Blood-Pressure-Hypertension-Shop-Benicar-Qocli buy benicar september compare price metformin, Ibuprofen in your liver forever2012-01-20 14:06:21
azithromycin otc directions
Cool site, http://tv8888.com/text/burden-of-depression-in-the-workplace-by-co-morbid-anxiety-and-fe4.html rimonabant otc weight gain, :-PPP,2012-01-20 15:06:26
free samples of hydrochlorothiazide otc or omeprezole
Nice comment, http://knowyourpet.co.uk/entry.php?572-Sca-Avodart-Cheap-Enlarged-Prostate-BPH-Benign-Prostatic-Hyperplasia-Baldness avodart price canadian pharmacy, qdhro,2012-01-20 15:38:41
how to order differin on line
Thanks for comments, http://7graphicsets.com/demo/1/forum/showthread.php?tid=25860 sumatriptan and coding for sale pharmicy, 551393,2012-01-20 15:44:06
price for colchicine oral
3, http://blantyre.jasonlafferty.com/wp-content/plugins/zingiri-forum/mybb/showthread.php?tid=10369 veterinary lentolith online, >:[,2012-01-20 16:01:24
cheap generic india indocin
5, http://blantyre.jasonlafferty.com/wp-content/plugins/zingiri-forum/mybb/showthread.php?tid=8824 buy estrogen topical gel .75, =-],2012-01-20 16:12:07
35 for sale pulmicort 51
5, http://ccgi.surveysoft.plus.com/PTKDC/forum/showthread.php?tid=1862 cheap cod postinor, 78135,2012-01-20 16:31:25
50 buy penis-growth-pills mg
2, http://www.hi5sms.in/wp-content/plugins/zingiri-forum/mybb/showthread.php?tid=132871 azelaic acid in over the counter items, >:-],2012-01-20 17:11:18
can you buy viagra-super-active in the united states
Cool site, http://armyjumpmaster.com/forum/showthread.php?tid=200637 discount order buy modalert online, twl,2012-01-20 17:36:31
viagra ivermectin cheap
exelon in eddystone pa. clonidine pain pump. http://prothom-alojobs.com/forum/thread-41006.html pet tramal no prescription bladder infections in the elderly. scat vomit mpeg.2012-01-20 17:41:33
online metronidazole oxalate
thrush fungal infection, sinus infection fungal http://jazcid.com/text/bma-welcomes-the-mrsa-target69.html order armour express Clinical trial atorvastatin zetia low cholesterol diet http://www.commentadded.com/showthread.php?tid=18236 where buy postinor canada Oregon zyprexa lawyer. free vomit movies,2012-01-20 18:13:46
colcrys prescriptions in el paso
Loratadine prescription strength, Herpes holistic cure http://normahhashim.com/text/generic-cost-luvox-discount-prices-cheapest-brandcf.html free shipping luvox cheap can thyroid medication cause lupus Interactions zantac chromium,2012-01-20 18:39:22
7 buy vistaril and proscar
Over the counter migraine medicine, glucophage and univasc http://bowibowibowi.web.id/entry.php?526-Acomplia-Otc-Equivalent.-Weight-Management-Obesity-Weight-Loss-Acomplia-Otc-Prod buy acomplia 100 mg prescription postpartum depression treatment with tetrathiomolybdate Coumadin adn cabbage. http://norwegiangamingzone.com/forum/showthread.php?tid=43472 reminyl sales from greece causes of involuntary muscle spasms. Xanax vs zoloft. http://suweesh.com/showthread.php?tid=60413 oxitard uk cheap purchase buy Tetracycline eye ointment pros and cons prozac, http://www.pmz.tv/wp-content/plugins/zingiri-forum/mybb/showthread.php?tid=454561 fda approved glumetza otc baby skin infection, Taper off zoloft to cymbalta2012-01-20 19:08:47
offshore pharmacies cyclobenzaprine
Nice comment, http://www.forum.newsradio24.com/showthread.php?tid=11015 cheap prescription estradiol without, eoegv,2012-01-20 19:11:06
cheap milnacipran 30mg
1, http://cheapestslimex.com/text/how-much-cost-meridia-price-shops-which-deliver-worldwide69.html buy meridia online inu, >:P,2012-01-20 19:34:05
penis-growth-oil sales dubai
ranitidine and urinary track infections hama antibodies interfere with thyroid labs http://mylittleandy.com/text/find-elimite-from-canadian-pharmacies-and-no-prescription0a.html elimite 200mg buy on line Zoloft generic information what is the purpose of prednisone http://www.billalbrecht.com/entry.php?1236-Cheap-1-Dicyclomine-20mg-Muscle-Spasms-Anticholinergic-Irritable-Bowel-Syndrome dicyclomine best price alopecia areata methotrexate asthma attack symptoms http://www.eksibis.com/topic-Depo-Sibutramine-15mg-Buy-Weight-Management-Obesity-Weight-Loss-Sibutramine-Disc price of sibutramine medications how are lysozymes and penicillin similar cystic acne the system,2012-01-20 19:36:20
seretide and other otc medicines
4, http://neomania.eu/foorum/showthread.php?tid=10097 order gliclazide sibutramine, rjewi,First pagePrevious 5 - 6 - [7] - 8 - 9 - 10 - 11 - 12 - 13 Next Last page