On a new project with lot of traffic, we are thinking on how to structure our Symfony2 app to take advantage of caches, and be ready to be more aggressive in the future. I'd love to know your opinion.
Let's say a user requests a page a list of places. This page has:
- list
- common data (title, author, description)
- user data (the user likes the list + other data)
- first 20 places
- common data (title, photo of each place)
- user data (the rates of the user for those places)
The HTML could be like:
<html>...
<body>
<header>
...
<!-- Embed the top user menu -->
<esi:include src="http://example.com/profile/menu" />
...
</header>
<content>
...
common data of the list
...
<!-- Embed the common data of the first 20 places, the same for everyone -->
<esi:include src="http://example.com/lists/17/places" />
...
<!-- Embed the user data of the list (used in JS) -->
<esi:include src="http://example.com/lists/17/user" />
...
<!-- Embed the user data of the list of places (used in JS) -->
<esi:include src="http://example.com/lists/17/places/user" />
...
</content>
</body>
</html>
The HTML will be cached on the gateway (Symfony or Varnish). The list of places will be cached most of the time on the gateway too. The user data requests will be the ones which are called and not be cached (not initially at least).
Questions:
thanks a lot!
We have used Varnish on one site for whole-page caching and I've been using Symfony2 for few years, but keep in mind that I haven't used Varnish + Symfony2 + ESI on any production environment.
I think the basic idea is OK. If menu is the same in many pages and list of places also the same on many pages, you get common content cached by Varnish or Symfony reverse cache. As Varnish usually holds cache in memory, you get your content faster and don't have to call rendering and DB querying code at each request.
The hard part is making those ESI requests cached if the user is logged in. As I know, in default Varnish configuration, requests with Cookie in them are never cached. If you tend to pass cookies to ESI requests, those ESI responses will not be shared between users.
You can try making some rules from URL, but if you use default Symfony twig helpers, generated URLs are /_internal/..., so it might be hard to differ public and private ones.
Also you can configure to always ignore any cookies if Cache-Control: public
is passed. This is done by default in Symfony:
if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) {
$response->setPrivate(true);
}
As you see from the code, if you have public
directive, response will never be private.
I haven't found how Varnish processes this directive - as I understand, it does not cache any requests that have cookie by default. So I think you have to tweak configuration to accomplish this.
If the main page is also to be cached, I don't see how you could skip the includes.
I assume JS is required for your registered users (not search bots), so I would suggest to use Javascript to differ the loading of user data.
Javascript code can look if the user has cookie session-id
etc. and make request to get the data only in this case. It might also be a good idea to set some other cookie, like _loggedin
to avoid Javascript code from getting the session id.
Not logged in users can also have some data in the cookies, like _likedPost:1,2,132
. Javascript can get this cookie and make some HTML corrections without even making the additional request.
As we did with these cookies: we separated JS-only cookies from application cookies. We did this by some pattern, like _\w
for JS cookies. Then we tweaked Varnish configuration to split Cookie header and remove these JS-only cookies. Then, if there is no other cookie left, the response is shared with everyone. Application (Symfony) does not get those cookies, as they are stripped.
I think it does if it is the same in every page.
I think ESI is good as Varnish can hold cache in memory. So it might be that it would not even make any queries to your hard disk for the content. As your controller cache might be also in-memory, I think Varnish would look for the cache more quicker than Symfony framework with all the routing, PHP code, services initialization etc.
It depends, but I think that it could be better approach. Keep in mind, that caches live different lives. For example, if your places list is cached for 2 hours, at the end of this time places can have changed - some new items are new on the list and some of them are missing. Your list to the user is still the old one (cached), but you provide user's data about the new list - some of the data is not needed, some of it is missing.
It might be better approach to get loaded places by javascript, for example searching for some HTML attribute like data-list-item-id
and then make ajax request querying data about these items. In this case your user data will be synchronized with current cached list and you can make 1 ajax request for both lists instead of 2.
If cache invalidation (PURGE requests) are not used, all HTTP cache sheme is indeed good to scale. You can scale the application to several servers and configure Varnish to call them randomly, by some rule or just to use one of them as a failsafe. If the bandwidth is still too big, you can always modify the cache timeouts and other configuration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With