Slide 1

Slide 1 text

Caching the uncachable Or how to deliver visitor specific content super fast.

Slide 2

Slide 2 text

I'm Thomas Schedler @chirimoya | https://github.com/chirimoya ... head of development and technical consultant. Young father trying to master Heston Blumenthal recipes.

Slide 3

Slide 3 text

Why?

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Slide 6

Slide 6 text

Who knows about HTTP Caching?

Slide 7

Slide 7 text

HTTP Caching Basics A short recap.

Slide 8

Slide 8 text

Browser Reverse Proxy Symfony Application GET / GET / Browser 200 OK 200 OK GET / 200 OK

Slide 9

Slide 9 text

HTTP/1.1 200 OK Cache-Control: public, max-age=3600, s-maxage=86400 Interesting content GET /

Slide 10

Slide 10 text

HTTP/1.1 200 OK Expires: Sat, 27 Oct 2015 16:00:00 GMT More Content GET /

Slide 11

Slide 11 text

HTTP/1.1 200 OK Last-Modified: Wed, 24 Oct 2018 07:28:00 GMT Quite recent Content GET / GET / If-Modified-Since: Wed, 24 Oct 2018 07:28:00 GMT HTTP/1.1 304 Not Modified

Slide 12

Slide 12 text

HTTP/1.1 200 OK ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" Still more Content GET / GET / If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" HTTP/1.1 304 Not Modified

Slide 13

Slide 13 text

Custom HTTP Header Pass additional information with the request or the response.

Slide 14

Slide 14 text

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 /

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

“ Caching entire responses isn't always possible for highly dynamic sites, or is it?

Slide 17

Slide 17 text

Partly caching with ESI HTTP Standards FTW!

Slide 18

Slide 18 text

ESI - Edge Side Includes – The ESI specification describes tags to communicate with the gateway cache – In Symfony the is implemented – If the response contains ESI tags, the cache either requests the page fragment from the backend or embeds the fresh cache entry

Slide 19

Slide 19 text

// 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 })) }}

Slide 20

Slide 20 text

https://www.kuechengoetter.de

Slide 21

Slide 21 text

Caching Implementations Symfony HttpCache or Varnish?

Slide 22

Slide 22 text

Symfony HttpCache – Easy to setup – Implemented in PHP – Not feature complete

Slide 23

Slide 23 text

// src/CacheKernel.php

Slide 24

Slide 24 text

// src/Controller/NameController.php setSharedMaxAge(3600); return $response; } }

Slide 25

Slide 25 text

Varnish – Super performant – Feature complete – Configured using VCL – Implemented in C – Harder to setup

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

// public/index.php

Slide 28

Slide 28 text

// src/Controller/NameController.php setSharedMaxAge(3600); return $response; } }

Slide 29

Slide 29 text

# 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"; }

Slide 30

Slide 30 text

# 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. }

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

FOSHttpCacheBundle Simplifies cache handling and invalidation in Symfony applications.

Slide 33

Slide 33 text

FOSHttpCacheBundle Features – Use rules to set cache headers – CacheManager / Annotations for comfortable Invalidation – Tagged Cache Invalidation – User Context based caching using session

Slide 34

Slide 34 text

Audience Targeting Show and cache different content for different target groups on the same URL.

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

Who knows the Vary header?

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Symfony HttpCache Use extended version of the Symfony HttpCache from Sulu to support Audience Targeting.

Slide 44

Slide 44 text

hasAudienceTargeting = $hasAudienceTargeting; } // ...

Slide 45

Slide 45 text

// app/WebsiteCache.php

Slide 46

Slide 46 text

// web/website.php

Slide 47

Slide 47 text

Varnish VCL to the rescue!

Slide 48

Slide 48 text

“ I really love the Varnish Configuration Language! Said no one, ever.

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

# default.vcl vcl 4.0; import header; backend default { .host = ”127.0.0.1”; .port = "8000"; }

Slide 51

Slide 51 text

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; }

Slide 52

Slide 52 text

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; }

Slide 53

Slide 53 text

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; }

Slide 54

Slide 54 text

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=/;"); } }

Slide 55

Slide 55 text

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; }

Slide 56

Slide 56 text

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=/;"); } }

Slide 57

Slide 57 text

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=/;"); } }

Slide 58

Slide 58 text

Sulu Docs

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Thanks for watching! sulu.io