going crazy with varnish and symfony
TRANSCRIPT
Going crazy with Symfony and Varnish
David de Boer15 May 2015
Varnish
Public content
Most requested
Most processing
Static assets
Authenticated content?
$ varnishtop -i BeReqUrl
Leiden
Driebit, Amsterdam
FOSHttpCache FOSHttpCacheBundle
Break up
only one TTL
don’t cache
15 mins
1 hour
24 hours
Enable ESI sub vcl_recv { set req.http.Surrogate-Capability = "abc=ESI/1.0";}
sub vcl_backend_response { if (beresp.http.Surrogate-Control ~ "ESI/1.0") { unset beresp.http.Surrogate-Control; set beresp.do_esi = true; }}
Plain PHP<?phpheader('Surrogate-Control: ESI/1.0');?><html><body> <esi:include src="/blue.php" /> Your main content. <esi:include src="/red.php" /></body></html>
<?php// blue.phpheader('Cache-Control: no-cache');?>Blue is not cached.
<?php// red.phpheader('Cache-Control: s-maxage=3600');?>Red is cached for an hour.
ESI in Symfony# app/config/config.yml
framework: esi: enabled: true fragments: { path: /_fragment }
{# template.twig #}
{{ render_esi(controller('AppBundle:Red:list', {'max': 100})) }}
ESI performance<?php// blue.php
sleep(2);header('Cache-Control: no-cache');?>
Blue is kind of sleepy.
<?php// red.php
sleep(5);header('Cache-Control: no-cache');?>
And red even more so.
7 seconds
Client-side
Rich JavaScript apps
JSON data through Ajax
API design
ESI in JSONvarnishd -p esi_syntax=0x00000003
{ "id": 1, "name": "Martian Ambassador", "_embedded": { "category": <esi:include src="/categories/1"/> }}
Invalidation<?php
use FOS\HttpCache\ProxyClient\Varnish;use FOS\HttpCache\CacheInvalidator;
$varnishClient = new Varnish('127.0.0.1', 'http://mars-attacks.dev');$cacheInvalidator = new CacheInvalidator($varnishClient);
// If some data changes in your app:$cacheInvalidator->invalidatePath('/red.php')->flush();
Invalidation# app/config/config.ymlfos_http_cache: proxy_client: varnish: servers: 127.0.0.1 base_url: mars-attacks.dev
<?php
use FOS\HttpCacheBundle\Configuration\InvalidateRoute;
/** * @InvalidateRoute("red")*/public function editAction($id){ // This is where data changes}
Individual caching?// builtin.vcl
sub vcl_recv { // ... if (req.http.Authorization || req.http.Cookie) { /* Not cacheable by default */ return (pass); } // ...}
Individual caching?sub vcl_recv { // ... if (req.http.Authorization || req.http.Cookie) { /* Cache all the personal things! */ return (hash); } // ...}
Individual caching?sub vcl_recv { // ... if (req.http.Authorization || req.http.Cookie) { /* Cache all the personal things! */ return (hash); } // ...}
sub vcl_deliver { // ... set resp.http.Vary = "Cookie,Authorization"; }
Group together
User context
Varnish
hash lookup
vary on credentials#
content request
vary on hash header
#
App ⟳
Varnish
User contexthash lookup
vary on credentials#
content request
vary on hash header
#
⟳
Varnish
User context
##
#
##
#
# ##
##
#
#
##
#
#
#
#
#
#
##
#
#
##
# # #
Configure user contextsub vcl_recv { if (req.restarts == 0 && (req.http.cookie || req.http.authorization) && (req.method == "GET" || req.method == "HEAD") ) { if (req.http.accept) { set req.http.X-Fos-Original-Accept = req.http.accept; } set req.http.accept = "application/vnd.fos.user-context-hash"; set req.http.X-Fos-Original-Url = req.url; set req.url = "/_fos_user_context_hash";
return (hash); }
if (req.restarts > 0 && req.http.accept == "application/vnd.fos.user-context-hash" ) { set req.url = req.http.X-Fos-Original-Url; unset req.http.X-Fos-Original-Url; if (req.http.X-Fos-Original-Accept) { set req.http.accept = req.http.X-Fos-Original-Accept; unset req.http.X-Fos-Original-Accept; } else { unset req.http.accept; }
return (hash); }}
Configure user contextsub vcl_backend_response { if (bereq.http.accept ~ "application/vnd.fos.user-context-hash" && beresp.status >= 500 ) { return (abandon); }}
sub vcl_deliver { if (req.restarts == 0 && resp.http.content-type ~ "application/vnd.fos.user-context-hash" ) { set req.http.X-User-Context-Hash = resp.http.X-User-Context-Hash;
return (restart); }
set resp.http.Vary = regsub(resp.http.Vary, "(?i),? *X-User-Context-Hash *", ""); set resp.http.Vary = regsub(resp.http.Vary, "^, *", ""); if (resp.http.Vary == "") { unset resp.http.Vary; }
unset resp.http.X-User-Context-Hash;}
Cleaning cookies$ varnishlog -c -i ReqHeader -I CookieCookie: __uvt=;PHPSESSID=ht8rl2tnbikpeoau7q5g677125;_ga=GA1.2.1332615739.14⏎
31368832;uvts=320PnWhzN5bW7V2D
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, "^[; ]+|[; ]+$", "");}
Cookie: PHPSESSID=ht8rl2tnbikpeoau7q5g677125
FOSHttpCacheBundle# app/config/config.yml fos_http_cache: user_context: hash_cache_ttl: 3600 hash_header: "Custom-User-Hash-Header" role_provider: true
Custom provider<?php
use FOS\HttpCache\UserContext\ContextProviderInterface;use FOS\HttpCache\UserContext\UserContext;
class IsAwesomeProvider implements ContextProviderInterface{ private $userService;
public function __construct(YourUserService $userService) { $this->userService = $userService; }
public function updateUserContext(UserContext $userContext) { $userContext->addParameter('awesome', $this->userService->isAwesome()); }}
// <service id="app_bundle.provider" class="AppBundle\Provider\IsAwesomeProvider"> <tag name="fos_http_cache.user_context_provider" /></service>