Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Caching the uncachable

Caching the uncachable

Or how to deliver visitor specific content super fast. #symfony #sulu #cms

Thomas Schedler

October 26, 2018
Tweet

More Decks by Thomas Schedler

Other Decks in Technology

Transcript

  1. I'm Thomas Schedler @chirimoya | https://github.com/chirimoya ... head of development

    and technical consultant. Young father trying to master Heston Blumenthal recipes.
  2. HTTP/1.1 200 OK Expires: Sat, 27 Oct 2015 16:00:00 GMT

    <!DOCTYPE html> <html> <body> More Content </body> </html> GET /
  3. HTTP/1.1 200 OK Last-Modified: Wed, 24 Oct 2018 07:28:00 GMT

    <!DOCTYPE html> <html> <body> Quite recent Content </body> </html> GET / GET / If-Modified-Since: Wed, 24 Oct 2018 07:28:00 GMT HTTP/1.1 304 Not Modified
  4. HTTP/1.1 200 OK ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" <!DOCTYPE html> <html> <body> Still

    more Content </body> </html> GET / GET / If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" HTTP/1.1 304 Not Modified
  5. Browser Reverse Proxy Symfony Application GET / GET / Browser

    200 OK Cache-Control: max-age=240 200 OK X-Reverse-Proxy-TTL: 3600 GET / GET / 200 OK X-Reverse-Proxy-TTL: 3600 200 OK Cache-Control: max-age=240 PURGE /
  6. Browser Reverse Proxy Symfony Application GET / GET / Browser

    200 OK Cache-Control: max-age=240 200 OK X-Reverse-Proxy-TTL: 3600 X-Cache-Tags: p1,p2,p3 GET / GET / 200 OK X-Reverse-Proxy-TTL: 3600 X-Cache-Tags: p1,p2 200 OK Cache-Control: max-age=240 BAN X-Cache-Tags: p3
  7. ESI - Edge Side Includes – The ESI specification describes

    tags to communicate with the gateway cache – In Symfony the <esi:include/> is implemented – If the response contains ESI tags, the cache either requests the page fragment from the backend or embeds the fresh cache entry
  8. // app/config/config.yml framework:
 ... esi: { enabled: true } //

    app/Resources/views/Default/index.html.twig
 
 {# you can use a controller reference #}
 {{ render_esi(controller('AppBundle:News:latest', { 'limit': 5 })) }}
 
 {# ... or a URL #}
 {{ render_esi(url('latest_news', { 'limit': 5 })) }}
  9. // src/CacheKernel.php <?php namespace App; use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; class CacheKernel extends

    HttpCache { } // public/index.php <?php use App\Kernel; use App\CacheKernel; // ... $kernel = new Kernel($env, $debug); $kernel = new CacheKernel($kernel); // ...
  10. // src/Controller/NameController.php <?php namespace App\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class

    NameController { /** * @Route("/name/{name}") */ public function index(string $name) { $response = new Response('Your name is ' . $name); $response->setSharedMaxAge(3600); return $response; } }
  11. Varnish Installation 1. Actually install varnish 2. Start varnish on

    port 80 3. Start your application on a different port 4. Tell varnish on which port your application is running 5. Add varnish as a trusted proxy in Symfony 6. Add cache headers to your responses 7. Configure the cache with VCL in more detail
  12. // src/Controller/NameController.php <?php namespace App\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class

    NameController { /** * @Route("/name/{name}") */ public function index(string $name) { $response = new Response('Your name is ' . $name); $response->setSharedMaxAge(3600); return $response; } }
  13. # default.vcl vcl 4.0; # Default backend definition. Set this

    to point to your content server. backend default { .host = ”127.0.0.1"; .port = "8080"; }
  14. # default.vcl sub vcl_recv { # Happens before we check

    if we have this in cache already. # # Typically you clean up the request here, removing cookies you don't need, # rewriting the request, etc. } sub vcl_deliver { # Happens when we have all the pieces we need, and are about to send the # response to the client. # # You can do accounting or modifying the final object here. }
  15. FOSHttpCacheBundle Features – Use rules to set cache headers –

    CacheManager / Annotations for comfortable Invalidation – Tagged Cache Invalidation – User Context based caching using session
  16. Audience Targeting Goals – Differentiate between different visitor target groups

    – Target groups are evaluated by a ruleset – first visit – each browser session – each hit – Do not start a session on the server – Cache the response per target group
  17. Browser Reverse Proxy Symfony Application GET /en Accept-Encoding: gzip, deflate

    Browser 200 OK
 Content-Encoding: gzip 200 OK
 Vary: Accept-Encoding
 Content-Encoding: gzip GET /en Accept-Encoding: gzip, deflate GET /en Accept-Encoding: gzip 200 OK
 Content-Encoding: gzip GET /en Accept-Encoding: deflate 200 OK
 Content-Encoding: deflate 200 OK
 Vary: Accept-Encoding
 Content-Encoding: deflate GET /en Accept-Encoding: deflate Browser
  18. Reverse Proxy Symfony Application GET /en Browser 200 OK X-Sulu-Target-Group:

    1 GET /_sulu_target_group X-Sulu-Original-Url: /en 200 OK Cache-Control: max-age=0 Set-Cookie: _svtg=1; Set-Cookie: _svs=…; GET /en Cookie: _svtg=1; _svs=… 200 OK Cache-Control: max-age=0 200 OK Vary: X-Sulu-Target-Group X-Reverse-Proxy-TTL: 3600 GET /en X-Sulu-Target-Group: 1
  19. Reverse Proxy Symfony Application Browser GET /about-us X-Sulu-Target-Group: 1 200

    OK X-Reverse-Proxy-TTL: 3600 GET /about-us Cookie: _svtg=1; _svs=… Browser 200 OK Cache-Control: max-age=0 GET /about-us Cookie: _svtg=1; _svs=… 200 OK Cache-Control: max-age=0
  20. <?php namespace Sulu\Component\HttpCache; use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache as AbstractHttpCache; use Symfony\Component\HttpKernel\HttpKernelInterface; class

    HttpCache extends AbstractHttpCache { private $hasAudienceTargeting; public function __construct( HttpKernelInterface $kernel, $hasAudienceTargeting = false, $cacheDir = null ) { parent::__construct($kernel, $cacheDir); $this->hasAudienceTargeting = $hasAudienceTargeting; } // ...
  21. // web/website.php <?php // ... $kernel = new WebsiteKernel(SYMFONY_ENV, SYMFONY_DEBUG);

    if (SYMFONY_ENV !== 'dev') { $kernel = new WebsiteCache($kernel, true); Request::enableHttpMethodParameterOverride(); } // ...
  22. sub vcl_recv { if (req.http.Cookie ~ "_svtg" && req.http.Cookie ~

    "_svs") { set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); } elseif (req.restarts == 0) { set req.http.X-Sulu-Original-Url = req.url; set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); set req.url = "/_sulu_target_group"; } elseif (req.restarts > 0) { set req.url = req.http.X-Sulu-Original-Url; unset req.http.X-Sulu-Original-Url; } unset req.http.Cookie; }
  23. sub vcl_recv { if (req.http.Cookie ~ "_svtg" && req.http.Cookie ~

    "_svs") { set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); } elseif (req.restarts == 0) { set req.http.X-Sulu-Original-Url = req.url; set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); set req.url = "/_sulu_target_group"; } elseif (req.restarts > 0) { set req.url = req.http.X-Sulu-Original-Url; unset req.http.X-Sulu-Original-Url; } unset req.http.Cookie; }
  24. sub vcl_recv { if (req.http.Cookie ~ "_svtg" && req.http.Cookie ~

    "_svs") { set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); } elseif (req.restarts == 0) { set req.http.X-Sulu-Original-Url = req.url; set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); set req.url = "/_sulu_target_group"; } elseif (req.restarts > 0) { set req.url = req.http.X-Sulu-Original-Url; unset req.http.X-Sulu-Original-Url; } unset req.http.Cookie; }
  25. sub vcl_deliver { if (resp.http.X-Sulu-Target-Group) { set req.http.X-Sulu-Target-Group = resp.http.X-Sulu-Target-Group;

    set req.http.Set-Cookie = "_svtg=" + resp.http.X-Sulu-Target-Group + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/;"; return (restart); } if (resp.http.Vary ~ "X-Sulu-Target-Group") { set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "max-age=(\d+)", "max- age=0"); set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "s-maxage=(\d+)", "s- maxage=0"); } if (req.http.Set-Cookie) { set resp.http.Set-Cookie = req.http.Set-Cookie; header.append(resp.http.Set-Cookie, "_svs=" + now + "; path=/;"); } }
  26. sub vcl_recv { if (req.http.Cookie ~ "_svtg" && req.http.Cookie ~

    "_svs") { set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); } elseif (req.restarts == 0) { set req.http.X-Sulu-Original-Url = req.url; set req.http.X-Sulu-Target-Group = regsub(req.http.Cookie, ".*_svtg=([^;]+).*", "\1"); set req.url = "/_sulu_target_group"; } elseif (req.restarts > 0) { set req.url = req.http.X-Sulu-Original-Url; unset req.http.X-Sulu-Original-Url; } unset req.http.Cookie; }
  27. sub vcl_deliver { if (resp.http.X-Sulu-Target-Group) { set req.http.X-Sulu-Target-Group = resp.http.X-Sulu-Target-Group;

    set req.http.Set-Cookie = "_svtg=" + resp.http.X-Sulu-Target-Group + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/;"; return (restart); } if (resp.http.Vary ~ "X-Sulu-Target-Group") { set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "max-age=(\d+)", "max- age=0"); set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "s-maxage=(\d+)", "s- maxage=0"); } if (req.http.Set-Cookie) { set resp.http.Set-Cookie = req.http.Set-Cookie; header.append(resp.http.Set-Cookie, "_svs=" + now + "; path=/;"); } }
  28. sub vcl_deliver { if (resp.http.X-Sulu-Target-Group) { set req.http.X-Sulu-Target-Group = resp.http.X-Sulu-Target-Group;

    set req.http.Set-Cookie = "_svtg=" + resp.http.X-Sulu-Target-Group + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/;"; return (restart); } if (resp.http.Vary ~ "X-Sulu-Target-Group") { set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "max-age=(\d+)", "max- age=0"); set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "s-maxage=(\d+)", "s- maxage=0"); } if (req.http.Set-Cookie) { set resp.http.Set-Cookie = req.http.Set-Cookie; header.append(resp.http.Set-Cookie, "_svs=" + now + "; path=/;"); } }