Slide 1

Slide 1 text

Caching the 
 Uncacheable Or how to deliver content tailored to visitors at lightning speed.

Slide 2

Slide 2 text

Hi, I’m Thomas Schedler @chirimoya – Co-founder & CEO of Sulu GmbH – More than 20 years of experience in web technologies & development – PHP, Symfony, React, SQL, Redis, Elasticsearch, Varnish, … – Open source enthusiast – Loves cooking and mountains [email protected] https://github.com/chirimoya

Slide 3

Slide 3 text

Why bother with HTTP Caching?

Slide 4

Slide 4 text

Why HTTP Caching? – Improves web performance by reducing server load and response times – Enhances user experience by delivering content faster – Saves costs by reducing the resources needed to handle high traffic volumes

Slide 5

Slide 5 text

“ HTTP caching is a technique that allows web resources to be temporarily stored on a client's device or an intermediary server, reducing the need for requests to the original server and improving web performance. ChatGPT

Slide 6

Slide 6 text

HTTP Cache – Caches entire HTTP responses – Controlled using HTTP headers (e.g., Cache-Control, ETag, …) – Examples: browser caches, CDNs, reverse proxy caches – Best for static content or content that doesn't change frequently

Slide 7

Slide 7 text

Layers of HTTP-Caching – Browser Cache – Shared Proxies – Reverse Proxies

Slide 8

Slide 8 text

Layers of HTTP-Caching – Browser Cache – Stores web content locally on the user's device – Provides instant access to previously visited content without re-fetching from the server – Controlled by cache headers like 
 Cache-Control and Expires – Shared Proxies – Reverse Proxies

Slide 9

Slide 9 text

Layers of HTTP-Caching – Browser Cache – Shared Proxies – Distributed systems that cache content closer to the user's location – Enhance content delivery speed by serving cached content from a nearby location – Useful for serving static assets like images, scripts and stylesheets to a global audience – Reverse Proxies

Slide 10

Slide 10 text

Layers of HTTP-Caching – Browser Cache – Shared Proxies – Reverse Proxies – Sit between client and web server, caching responses from the server – Examples include Varnish, Nginx, and Symfony HTTP-Cache – Can cache dynamic content and provide load balancing – Controlled by cache headers and can be configured for specific caching rules

Slide 11

Slide 11 text

Reverse Proxies

Slide 12

Slide 12 text

Reverse Proxies

Slide 13

Slide 13 text

HTTP Header Cache-Control The Cache-Control HTTP header field holds directives that control caching in browsers and shared caches (e.g. Proxies, CDNs)

Slide 14

Slide 14 text

Cache-Control Directives – public — Indicates that the response can be stored in any cache, including shared and private caches – private — Indicates that the response can be stored only in a private cache (e.g. local caches in browsers) – no-cache — Indicates that the response can be stored in caches, but the response must be validated with the origin server before each reuse – no-store — Indicates that any caches of any kind (private or shared) should not store this response

Slide 15

Slide 15 text

Cache-Control Directives – max-age=[N] — Indicates that the response remains fresh until N seconds after the response is generated – s-maxage=[N] — Indicates how long the response is fresh for (similar to max-age) — but it is specific to shared caches, and they will ignore max-age when it is present – must-revalidate — Indicates that the response can be stored in caches and can be reused while fresh. If the response becomes stale, it must be validated with the origin server before reuse

Slide 16

Slide 16 text

Cache-Control Directives – stale-while-revalidate — Indicates that the cache could reuse a stale response while it revalidates the response in the background – stale-if-error — Indicates that the cache can reuse a stale response when an upstream server generates an error – … https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Symfony Reverse Proxy Symfony comes with a reverse proxy written in PHP. It's not a fully-featured reverse proxy cache like Varnish, but it is a great way to start.

Slide 22

Slide 22 text

// src/Controller/LuckyController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class LuckyController extends AbstractController { #[Route('/lucky/number')] public function number(): Response { $number = random_int(0, 100); return $this->render('lucky/number.html.twig', [ 'number' => $number, ]); } }

Slide 23

Slide 23 text

// src/Controller/LuckyController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\Cache; use Symfony\Component\Routing\Annotation\Route; class LuckyController extends AbstractController { #[Route('/lucky/number')] #[Cache(public: true, maxage: 3600, mustRevalidate: true)] public function number(): Response { $number = random_int(0, 100); return $this->render('lucky/number.html.twig', [ 'number' => $number, ]); } }

Slide 24

Slide 24 text

# config/packages/framework.yaml when@prod: framework: http_cache: true

Slide 25

Slide 25 text

getSession(); $number = $session->get('number', random_int(0, 100)); $session->set('number', $number); return $this->render('lucky/number.html.twig', [ 'number' => $number, ]); } } !

Slide 26

Slide 26 text

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

Slide 27

Slide 27 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 28

Slide 28 text

# config/packages/framework.yaml framework: # ... esi: true fragments: { path: /_fragment }

Slide 29

Slide 29 text

{# templates/pages/homepage.html.twig #} {# you can use a controller reference #} {{ render_esi(controller('App\\Controller\\WeatherController::forecast' })) }} {# ... or a URL #} {{ render_esi(url('weather_forecast')) }}

Slide 30

Slide 30 text

Cache Invalidation „There are only two hard things in Computer Science: cache invalidation and naming things.“ 
 
 Phil Karlton

Slide 31

Slide 31 text

Cache Invalidation – Removing or marking cached resources as stale or outdated – Using mechanisms like cache control headers, conditional requests – Or by explicitly purging cached entries

Slide 32

Slide 32 text

HTTP/2 200 OK … Cache-Control: public, max-age=240, s-maxage=240 … Deliver awesome, robust, reliable websites with Sulu CMS GET https://sulu.io/

Slide 33

Slide 33 text

HTTP/2 200 OK … Cache-Control: public, max-age=240, s-maxage=600 X-Cache: HIT … Deliver awesome, robust, reliable websites with Sulu CMS GET https://sulu.io/

Slide 34

Slide 34 text

HTTP/2 200 OK … Cache-Control: public, max-age=240, s-maxage=240 X-Cache: HIT X-Reverse-Proxy-TTL: 86400 … Deliver awesome, robust, reliable websites with Sulu CMS GET https://sulu.io/

Slide 35

Slide 35 text

HTTP/2 200 OK … Cache-Control: public, max-age=240, s-maxage=240 X-Cache: HIT X-Reverse-Proxy-TTL: 86400 X-Cache-Tags: 22a92d46, cf4a07fe … Deliver awesome, robust, reliable websites with Sulu CMS GET https://sulu.io/

Slide 36

Slide 36 text

HTTP/2 200 Purged 200 Purged … PURGE https://sulu.io/

Slide 37

Slide 37 text

HTTP/2 200 Banned 200 Banned … BAN https://sulu.io/ 
 X-Cache-Tags: 22a92d46

Slide 38

Slide 38 text

FOSHttpCacheBundle This bundle offers tools to improve 
 HTTP caching with Symfony. https://github.com/FriendsOfSymfony/FOSHttpCacheBundle

Slide 39

Slide 39 text

But how to deliver content tailored to visitors at lightning speed?

Slide 40

Slide 40 text

Vary HTTP Header The Vary HTTP header is used to create 
 cache variations

Slide 41

Slide 41 text

Vary HTTP Header – The Vary HTTP response header describes the parts of the request message aside from the method and URL that influenced the content of the response – Most often, this is used to create a cache key when content negotiation is in use – Vary: Accept-Encoding, , ... https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

Facts & Figures – GU recipe portal – Visits: ~6,8 m – PIs: ~12,6 m – Recipes: ~40.000 API – GU Recipes Küchengötter https://www.kuechengoetter.de

Slide 44

Slide 44 text

Facts & Figures – GU recipe portal – Visits: ~6,8 m – PIs: ~12,6 m – Recipes: ~40.000 API – GU Recipes Küchengötter https://www.kuechengoetter.de

Slide 45

Slide 45 text

Facts & Figures – GU recipe portal – Visits: ~6,8 m – PIs: ~12,6 m – Recipes: ~40.000 API – GU Recipes Küchengötter https://www.kuechengoetter.de

Slide 46

Slide 46 text

Varnish The free, open source software that enables super fast delivery of HTTP based content.

Slide 47

Slide 47 text

Varnish – High-Performance HTTP Caching – Load Balancing – Feature complete – Edge Side Includes (ESI) – Purging and Banning – Grace Mode – … – Flexible Configuration Language

Slide 48

Slide 48 text

Varnish Con fi guration Language The Varnish Configuration Language (VCL) is a domain- specific programming language used by Varnish to control request handling, routing, caching, and several other aspects.

Slide 49

Slide 49 text

Varnish Con fi guration Language https://www.varnish-software.com/developers/tutorials/varnish-configuration-language-vcl/

Slide 50

Slide 50 text

# /etc/varnish/default.vcl vcl 4.1; acl invalidators { "localhost"; } backend default { .host = "host.docker.internal"; .port = "8000"; } sub vcl_recv { if (req.method == "PURGE") { if (!client.ip ~ invalidators) { return (synth(405, "Not allowed")); } return (purge); } }

Slide 51

Slide 51 text

# /etc/varnish/default.vcl # … sub vcl_backend_response { set beresp.grace = 24h; if (beresp.http.X-Reverse-Proxy-TTL) { set beresp.ttl = std.duration(beresp.http.X-Reverse-Proxy-TTL + "s", 0s); unset beresp.http.X-Reverse-Proxy-TTL; } } sub vcl_deliver { if (resp.http.X-Cache-Debug) { if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } } else { unset resp.http.X-Reverse-Proxy-TTL; unset resp.http.X-Cache-Tags; } }

Slide 52

Slide 52 text

# /etc/varnish/default.vcl vcl 4.1; # see docs about redis here https://github.com/carlosabalde/libvmod-redis import redis; # see docs about cookie here https://github.com/varnish/varnish-modules import cookie; # ... sub vcl_init { # VMOD configuration: simple case, keeping up to 
 # one Redis connection per Varnish worker thread. new db = redis.db( location="127.0.0.1:6379", type=master, connection_timeout=500, shared_connections=false, max_connections=1); }

Slide 53

Slide 53 text

# /etc/varnish/default.vcl sub vcl_recv { # ... // Extract SESSIONID Header, validate it and if its valid get cache group from redis set req.http.X-ABO = ""; cookie.parse(req.http.cookie); set req.http.X-SESSION-ID = cookie.get("PHPSESSID"); if (req.http.X-SESSION-ID ~ "[0-9a-zA-z,-]+") { db.command("GET"); db.push("SESSION_ID_" + req.http.X-SESSION-ID); db.execute(); set req.http.X-ABO = db.get_string_reply(); } # ... }

Slide 54

Slide 54 text

class RecipeController extends AbstractController { public function detail(Request $request): Response { // ... $response = $this->render('recipe.html.twig', [ 'recipe' => $recipe, ]); if ($recipe->isPlus()) { $response->setVary(‚X-ABO'); } return $response; } }

Slide 55

Slide 55 text

Thank you!