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

D6bc02afa30223e3acd9fdf512d82e2c?s=128

Thomas Schedler

October 26, 2018
Tweet

More Decks by Thomas Schedler

Other Decks in Technology

Transcript

  1. Caching the uncachable Or how to deliver visitor specific content

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

    and technical consultant. Young father trying to master Heston Blumenthal recipes.
  3. Why?

  4. None
  5. Who knows about HTTP Caching?

  6. HTTP Caching Basics A short recap.

  7. Browser Reverse Proxy Symfony Application GET / GET / Browser

    200 OK 200 OK GET / 200 OK
  8. HTTP/1.1 200 OK Cache-Control: public, max-age=3600, s-maxage=86400 <!DOCTYPE html> <html>

    <body> Interesting content </body> </html> GET /
  9. HTTP/1.1 200 OK Expires: Sat, 27 Oct 2015 16:00:00 GMT

    <!DOCTYPE html> <html> <body> More Content </body> </html> GET /
  10. 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
  11. 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
  12. Custom HTTP Header Pass additional information with the request or

    the response.
  13. 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 /
  14. 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
  15. “ Caching entire responses isn't always possible for highly dynamic

    sites, or is it?
  16. Partly caching with ESI HTTP Standards FTW!

  17. 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
  18. // 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 })) }}
  19. https://www.kuechengoetter.de

  20. Caching Implementations Symfony HttpCache or Varnish?

  21. Symfony HttpCache – Easy to setup – Implemented in PHP

    – Not feature complete
  22. // 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); // ...
  23. // 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; } }
  24. Varnish – Super performant – Feature complete – Configured using

    VCL – Implemented in C – Harder to setup
  25. 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
  26. // public/index.php <?php // ... Request::setTrustedProxies( ['127.0.0.1'], Request::HEADER_X_FORWARDED_ALL ); //

    ...
  27. // 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; } }
  28. # 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"; }
  29. # 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. }
  30. None
  31. FOSHttpCacheBundle Simplifies cache handling and invalidation in Symfony applications.

  32. FOSHttpCacheBundle Features – Use rules to set cache headers –

    CacheManager / Annotations for comfortable Invalidation – Tagged Cache Invalidation – User Context based caching using session
  33. Audience Targeting Show and cache different content for different target

    groups on the same URL.
  34. 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
  35. None
  36. None
  37. None
  38. Who knows the Vary header?

  39. 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
  40. 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
  41. 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
  42. Symfony HttpCache Use extended version of the Symfony HttpCache from

    Sulu to support Audience Targeting.
  43. <?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; } // ...
  44. // app/WebsiteCache.php <?php use Sulu\Component\HttpCache\HttpCache; class WebsiteCache extends HttpCache {

    }
  45. // web/website.php <?php // ... $kernel = new WebsiteKernel(SYMFONY_ENV, SYMFONY_DEBUG);

    if (SYMFONY_ENV !== 'dev') { $kernel = new WebsiteCache($kernel, true); Request::enableHttpMethodParameterOverride(); } // ...
  46. Varnish VCL to the rescue!

  47. “ I really love the Varnish Configuration Language! Said no

    one, ever.
  48. None
  49. # default.vcl vcl 4.0; import header; backend default { .host

    = ”127.0.0.1”; .port = "8000"; }
  50. 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; }
  51. 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; }
  52. 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; }
  53. 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=/;"); } }
  54. 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; }
  55. 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=/;"); } }
  56. 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=/;"); } }
  57. Sulu Docs

  58. How to reevaluate target groups on cache hit? Good old

    Javascript.
  59. Thanks for watching! sulu.io