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

Caching the uncacheable with Varnish - PHP London 2020

Thijs Feryn
February 06, 2020

Caching the uncacheable with Varnish - PHP London 2020

This talk was presented at the February 2020 edition of the PHP London meetup group.

This presentation is about caching personalized data in Varnish, while still maintaining a good hit rate.

See https://feryn.eu/speaking/caching-uncacheable-varnish-php-london-meetup-2020/ for more info.

Thijs Feryn

February 06, 2020
Tweet

More Decks by Thijs Feryn

Other Decks in Technology

Transcript

  1. CONTROLLING THE CACHE WITH HTTP Expires: Sat, 09 Sep 2017

    14:30:00 GMT Cache-control: public, max-age=3600, s-maxage=86400 Cache-control: private, no-cache, no-store Vary: Accept-Language
  2. vcl 4.1; sub vcl_recv { if (req.http.Cookie) { set req.http.Cookie

    = ";" + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1="); set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.Cookie == "") { unset req.http.Cookie; } else { return (pass); } } } Only keep certain cookies
  3. sub vcl_recv { if (req.http.Cookie) { set req.http.Cookie = ";"

    + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); set req.http.Cookie = regsuball(req.http.Cookie, ";(language)=", "; \1="); set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.cookie ~ "^\s*$") { unset req.http.cookie; return(pass); } return(hash); } } sub vcl_hash { hash_data(regsub( req.http.Cookie, "^.*language=([^;]*);*.*$", "\1" )); } Cookie cache variation
  4. vcl 4.1; import cookieplus; sub vcl_recv { cookieplus.keep("language"); cookieplus.write(); }

    sub vcl_hash { hash_data(cookieplus.get("language")); } VMOD_COOKIE
  5. ESI ✓ PLACEHOLDER ✓ PARSED BY VARNISH ✓ OUTPUT IS

    A COMPOSITION OF BLOCKS ✓ STATE PER BLOCK ✓ TTL PER BLOCK
  6. sub vcl_recv { set req.http.Surrogate-Capability = "key=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; } }
  7. ✓SERVER-SIDE ✓STANDARDIZED ✓PROCESSED ON THE “EDGE”, NOT IN THE BROWSER

    ✓GENERALLY FASTER -SEQUENTIAL* -ONE FAILS, ALL FAIL -LIMITED IMPLEMENTATION IN VARNISH -NOT THAT COMMON ESI PARALLEL IN ENTERPRISE
  8. vcl 4.1; import cookieplus; import redis; import synthbackend; sub vcl_init

    { new db = redis.db( location="redis:6379", type=master, connection_timeout=500, shared_connections=false, max_connections=1); } sub vcl_recv { set req.http.X-Login = "false"; set req.http.x-session = cookieplus.get("PHPSESSID","guest"); if(req.http.x-session != "guest") { db.command("EXISTS"); db.push("sf_s"+cookieplus.get("PHPSESSID")); db.execute(); if(db.get_integer_reply() == 1) { set req.http.X-Login = "true"; } } } SYNTHETIC HTTP
  9. sub vcl_backend_fetch { if(bereq.url == "/session") { if(bereq.http.X-Login != "true")

    { set bereq.backend = synthbackend.from_string("{}"); return(fetch); } db.command("EVAL"); db.push({" local session = redis.call('GET', KEYS[1]) if session == nil then return '{}' end local result = string.gsub(session, '[%c]', '') local username = string.gsub(result,'.+Userusername\";s:[0-9]+:\"([^\"]+)\";.+','%1') if username == nil then return '{}' end return '{"username":"'.. username ..'"}' "}); db.push(1); db.push("sf_s"+cookieplus.get("PHPSESSID")); db.execute(); set bereq.backend = synthbackend.from_string(db.get_string_reply()); } } sub vcl_backend_response { if(bereq.url == "/session") { set beresp.http.Content-Type = "application/json; charset=utf-8"; set beresp.ttl = 3600s; set beresp.http.vary = "x-session"; } } REDIS LUA CODE
  10. USER GET /session Cookie: PHPSESSID=7er3hjKal8u235c87u6ih0vz8Y HTTP/1.1 200 OK Content-Length: 31

    Content-Type: application/json; charset=utf-8 X-Varnish: 163854 196622 Age: 27 {"username":"thijs"} VARNISH
  11. WHAT DO YOU DO WITH IT? ✓ AJAX CALL ✓

    LOAD VIA ESI AND PROCESS VIA LOCAL JAVASCRIPT ✓ EDGESTASH
  12. vcl 4.1; import edgestash; backend default { .host = "1.1.1.1";

    .port = "80"; } sub vcl_backend_response { if (bereq.url == "/session") { edgestash.index_json(); } else if (bereq.url == "/") { edgestash.parse_response(); } } sub vcl_deliver { if (req.url == "/" && edgestash.is_edgestash()) { edgestash.add_json_url("/session"); edgestash.execute(); } } EDGESTASH
  13. vcl 4.1; import edgestash; import std; backend default { .host

    = "1.1.1.1"; } sub vcl_recv { set req.http.Surrogate-Capability={"edgestash="EDGESTASH/2.1""}; } sub vcl_backend_response { if(beresp.http.Link) { std.collect(beresp.http.Link,","); } if(beresp.http.Link ~ "<([^>]+)>; rel=edgestash") { set beresp.http.x-edgestash-json-urls = regsuball(beresp.http.Link,"(?(?=<[^>]+>; rel=edgestash)<([^>]+)>; rel=edgestash|<([^>]+)>; rel=[a-z]+, )","\1"); } if(beresp.http.Surrogate-Control) { std.collect(beresp.http.Surrogate-Control); } if(beresp.http.Surrogate-Control ~ {".*="EDGESTASH/2\.[0-9]+".*"}) { edgestash.parse_response(); } } EDGESTASH
  14. {{ edgestash('#username', '/session') }} Welcome {{ edgestash('username') }} {{ edgestash('/username')

    }} {{ edgestash('^username') }} Welcome guest {{ edgestash('/username') }
  15. import deviceatlas; sub vcl_recv { set req.http.MobilePhone = "no"; if

    (deviceatlas.lookup_int(req.http.User-Agent, "isMobilePhone")) { set req.http.MobilePhone = "yes"; } } Device detection
  16. vcl 4.0; import mmdb; backend default { .host = "192.0.2.11";

    .port = "8080"; } # create a database object sub vcl_init { new geodb = mmdb.init("/path/to/db"); } sub vcl_recv { # retrieve the name of the request's origin set req.http.Country-Name = geodb.country_name(client.ip); # if the country doesn't come from Germany or Belgium, deny access if (req.http.Country-Name != "Germany" || req.http.Country-Name != "Belgium") { return (synth(403, "Sorry, only available in Germany and Belgium")); } } Geo blocking
  17. vcl 4.0; import http; backend default { .host = "origin";

    .port = "80"; } sub vcl_recv { set req.http.X-prefetch = http.varnish_url("/"); } sub vcl_backend_response { if (beresp.http.Link ~ "<.+>.*(prefetch|next)") { set bereq.http.X-link = regsub(beresp.http.Link, "^.*<([^>]*)>.*$", "\1"); set bereq.http.X-prefetch = regsub(bereq.http.X-prefetch, "/$", bereq.http.X-link); http.init(0); http.req_copy_headers(0); http.req_set_url(0, bereq.http.X-prefetch); http.req_send_and_finish(0); } } Pre-fetching