Going crazy with caching
Caching pages of logged in users
PHP Benelux, January 28th, 2017
© David Buchmann
HTTP Caching
- Why re-render content that does not change?
- Scaling and response time
- Less servers, more users
What is a reverse proxy again?
What about the real live?
Strategies when working with sessions
- Avoid Sessions, remove when no longer needed
- Delegate to the frontend: "Cheating" with Javascript
- Cache lookup despite cookies
- User Context: Cache by group
Avoid Sessions
Avoid Session
- Delete cookie as soon as no longer needed
- Watch out for autostarted PHP sessions
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time()-3600);
}
Cleanup Cookies: Remove all but session cookie
sub vcl_recv {
set req.http.cookie = ";" + req.http.cookie;
set req.http.cookie = regsuball(req.http.cookie, "; +", ";");
set req.http.cookie = regsuball(req.http.cookie, ";(PHPSESSID)=", "; \1=");
set req.http.cookie = regsuball(req.http.cookie, ";[^ ][^;]*", "");
set req.http.cookie = regsuball(req.http.cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
remove req.http.Cookie;
}
}
varnish-cache.org/trac/wiki/VCLExampleRemovingSomeCookies #RemovingallBUTsomecookies
Logic in the Frontend
Render User Specific Parts in Javascript
- User state is communicated to Javascript in cookie
- Alter page when loaded, e.g. with jquery:
$(document).ready(function () {
$("#labels-edit").show();
$("#milestone-edit").show();
$("#assignee-edit").show();
});
Logic in the Frontend
- Pro
- Fine grained control
- No network overhead, easy on backend
- Can handle complex user interfaces
- Con
- Templating in frontend and backend?
- Development effort and hard to maintain
- Search engines don't need to see those parts anyways
Ajax for User Specific Parts
Ajax
- Separate endpoint in application for each fragment
- Do request when page loaded, e.g. with jquery:
$(document).ready(function () {
$.ajax({
url: "/sidebar.html"
}).done(function( html ) {
$( "#sidebar" ).append( html );
});
});
Ajax
- Pro
- Can be executed after page already displayed
- Good for slow, non-critical information
- Con
- Network overhead (mobile...)
- Additional logic in the backend
- Complicated to maintain
Cache lookup despite cookies
Cache lookup despite cookies
- Very easy to shoot yourself in the foot!
- The backend must send correct cache headers
builtin.vcl
// builtin.vcl
sub vcl_recv {
// ...
if (req.method != "GET" && req.method != "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 (hash);
}
Cache lookup despite cookies
// default.vcl
sub vcl_recv {
// ...
if (req.method != "GET" && req.method != "HEAD") {
/* We only deal with GET and HEAD by default */
return (pass);
}
return (hash);
}
Edge Side Includes
Use Edge Side Includes
Like server side include, but on Varnish:
- Content embeds URLs to sub-parts of the content
- Varnish fetches and caches elements separatly
- Individual caching rules per fragment.
E.g. some elements vary on cookie, different TTL, ...
- Symfony has built-in support for ESI
ESI HTML
<html>
<body>
Main body.
<esi:include src="fragment.php" />
</body>
</html>
sub vcl_recv {
// Add a Surrogate-Capability header
// to announce ESI support.
set req.http.Surrogate-Capability = "abc=ESI/1.0";
}
sub vcl_backend_response {
// Check for ESI acknowledgement
// and remove Surrogate-Control header.
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
}
ESI
- Pro
- Separate caching for fragments
- Server side, makes search engines happy
- Simple to use in the application
- Con
- Each include must be resolved before response can be sent
Symfony has built-in ESI support
# app/config/config.yml
framework:
esi: { enabled: true }
fragments: { path: /_fragment }
Make sure either your webserver is only reachable from the Varnish server or add access restrictions for /_fragment
{# index.html.twig #}
{% render_esi(controller( 'AppBundle:Comments:comments', {'param': 42 })) %}
User Context
Introducing the User Context Hash
- Group based caching
- Transparent (reverse proxy does most of the job)
- Computed for every user in application
- Hash generation can be customized
- Cached by session ID, using HTTP cache of course!
User Context
Mix solutions
- Edge side includes
- Ajax
- Move logic to frontend
- User context
Questions / Input / Feedback ?
Twitter: @dbu
Tools
FOSHttpCache
- Cache tagging and invalidation
- User context
FOSHttpCacheBundle
- Request matcher for caching headers
- Annotations for caching headers and invalidation
- Invalidation service for active invalidation
- Listener for user context