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

Common scenarios in VCL - Varnish Summit CPH 2016

Common scenarios in VCL - Varnish Summit CPH 2016

Slides for my "Common scenarios in VCL" talk at the 2016 Varnish Summit in Copenhagen. See http://info.varnish-software.com/copenhagen-summit-2016 for more information

Thijs Feryn

April 21, 2016
Tweet

More Decks by Thijs Feryn

Other Decks in Technology

Transcript

  1. Common
    scenarios
    in VCL
    By Thijs Feryn

    View Slide

  2. Hi, I’m Thijs

    View Slide

  3. I’m
    @ThijsFeryn
    on Twitter

    View Slide

  4. I’m an
    Evangelist
    At

    View Slide

  5. I’m a
    at
    board member

    View Slide

  6. 149 th
    talk

    View Slide

  7. This is my first
    time
    in København

    View Slide

  8. Slow websites suck

    View Slide

  9. Varnish

    View Slide

  10. Better end-user experience

    View Slide

  11. Easy peasy
    right?

    View Slide

  12. Not
    really

    View Slide

  13. There are rules

    View Slide

  14. ✓Only GET & HEAD
    ✓No authorization headers
    ✓No cookies
    ✓No set-cookies
    ✓Valid cache-control/expires
    headers
    When does Varnish
    cache? Some rules …

    View Slide

  15. It’s all
    about
    state

    View Slide

  16. Lots of
    developers
    don’t follow
    those rules

    View Slide

  17. Cookies
    everywhere

    View Slide

  18. Cache-control quoi?

    View Slide

  19. Out of the box
    These are the main reasons
    why Varnish will not work

    View Slide

  20. Write VCL

    View Slide

  21. Varnish
    Configuration
    Language

    View Slide

  22. Programming
    the
    Varnish
    behaviour

    View Slide

  23. Varnish flow

    View Slide

  24. View Slide

  25. Hooks

    View Slide

  26. VCL
    Recv
    VCL
    Hash
    VCL
    Miss
    VCL
    Hit
    VCL
    Backend
    Response
    VCL
    Deliver
    VCL
    Purge
    VCL
    Error
    VCL
    Pipe
    VCL
    Pass
    VCL
    Synth
    VCL
    Backend
    Error

    View Slide

  27. ✓vcl_recv: receive request
    ✓vcl_hash: compose cache key
    ✓vcl_miss: not found in cache
    ✓vcl_hit: found in cache
    ✓vcl_pass: don’t store in cache
    ✓vcl_pipe: bypass cache
    ✓vcl_backend_fetch: connect to
    backend
    ✓vcl_backend_response: response
    from backend
    ✓vcl_backend_error: backend fetch
    failed

    View Slide

  28. ✓vcl_purge: after successful purge
    ✓vcl_synth: send synthetic output
    ✓vcl_deliver: return data to client
    ✓vcl_init: initialize VMODs
    ✓vcl_fini: discard VMODs

    View Slide

  29. vcl 4.0;
    backend default {
    .host = "127.0.0.1";
    .port = "8080";
    }
    Minimal VCL

    View Slide

  30. vcl 4.0;
    backend default {
    .host = "127.0.0.1";
    .port = "80";
    .max_connections = 300;
    .first_byte_timeout = 300s;
    .connect_timeout = 5s;
    .between_bytes_timeout = 2s;
    }
    More backend work

    View Slide

  31. vcl 4.0;
    backend default {
    .host = "127.0.0.1";
    .port = "80";
    .max_connections = 300;
    .first_byte_timeout = 300s;
    .connect_timeout = 5s;
    .between_bytes_timeout = 2s;
    .probe = {
    .url = "/";
    .interval = 5s;
    .timeout = 1s;
    .window = 5;
    .threshold = 3;
    }
    }
    Even more backend work

    View Slide

  32. vcl 4.0;
    backend default {
    .host = "127.0.0.1";
    .port = "80";
    .max_connections = 300;
    .first_byte_timeout = 300s;
    .connect_timeout = 5s;
    .between_bytes_timeout = 2s;
    .probe = {
    .request =
    "HEAD / HTTP/1.1"
    "Host: localhost"
    "Connection: close"
    "User-Agent: Varnish Health Probe";
    .interval = 5s;
    .timeout = 1s;
    .window = 5;
    .threshold = 3;
    }
    }
    Even more backend work

    View Slide

  33. Load balancing

    View Slide

  34. vcl 4.0;
    import directors;
    backend server1 {
    .host = “1.2.3.4”;
    .port = "80";
    .max_connections = 300;
    .first_byte_timeout = 300s;
    .connect_timeout = 5s;
    .between_bytes_timeout = 2s;
    .probe = {
    .request =
    "HEAD / HTTP/1.1"
    "Host: localhost"
    "Connection: close"
    "User-Agent: Varnish Health Probe";
    .interval = 5s;
    .timeout = 1s;
    .window = 5;
    .threshold = 3;
    }
    }
    Loadbalancing: server 1

    View Slide

  35. backend server2 {
    .host = “1.2.3.5”;
    .port = "80";
    .max_connections = 300;
    .first_byte_timeout = 300s;
    .connect_timeout = 5s;
    .between_bytes_timeout = 2s;
    .probe = {
    .request =
    "HEAD / HTTP/1.1"
    "Host: localhost"
    "Connection: close"
    "User-Agent: Varnish Health Probe";
    .interval = 5s;
    .timeout = 1s;
    .window = 5;
    .threshold = 3;
    }
    }
    Loadbalancing: server 2

    View Slide

  36. sub vcl_init {
    new vdir = directors.round_robin();
    vdir.add_backend(server1);
    vdir.add_backend(server2);
    }
    sub vcl_recv {
    set req.backend_hint = vdir.backend();
    }
    Loadbalancing: round robin

    View Slide

  37. sub vcl_init {
    new vdir = directors.random();
    vdir.add_backend(server1,2);
    vdir.add_backend(server2,3);
    }
    sub vcl_recv {
    set req.backend_hint = vdir.backend();
    }
    Loadbalancing: random

    View Slide

  38. sub vcl_init {
    new vdir = directors.fallback();
    vdir.add_backend(server1);
    vdir.add_backend(server2);
    }
    sub vcl_recv {
    set req.backend_hint = vdir.backend();
    }
    Loadbalancing: fallback

    View Slide

  39. sub vcl_init {
    new vdir = directors.hash();
    vdir.add_backend(server1);
    vdir.add_backend(server2);
    }
    sub vcl_recv {
    set req.backend_hint = vdir.backend(req.url);
    }
    Loadbalancing: URL hash

    View Slide

  40. sub vcl_init {
    new vdir = directors.hash();
    vdir.add_backend(server1);
    vdir.add_backend(server2);
    }
    sub vcl_recv {
    set req.backend_hint = vdir.backend(client.identity);
    }
    Loadbalancing: IP hash

    View Slide

  41. sub vcl_recv {
    if(req.url ~ “^/products”) {
    set req.backend_hint = server1;
    } else {
    set req.backend_hint = server2;
    }
    }
    Conditional loadbalacning

    View Slide

  42. Normalize

    View Slide

  43. vcl 4.0;
    import std;
    sub vcl_recv {
    set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
    set req.url = std.querysort(req.url);
    if (req.url ~ "\#") {
    set req.url = regsub(req.url, "\#.*$", "");
    }
    if (req.url ~ "\?$") {
    set req.url = regsub(req.url, "\?$", "");
    }
    if (req.restarts == 0) {
    if (req.http.Accept-Encoding) {
    if (req.http.User-Agent ~ "MSIE 6") {
    unset req.http.Accept-Encoding;
    } elsif (req.http.Accept-Encoding ~ "gzip") {
    set req.http.Accept-Encoding = "gzip";
    } elsif (req.http.Accept-Encoding ~ "deflate") {
    set req.http.Accept-Encoding = "deflate";
    } else {
    unset req.http.Accept-Encoding;
    }
    }
    }
    }
    Normalize

    View Slide

  44. Static assets

    View Slide

  45. vcl 4.0;
    sub vcl_recv {
    if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|
    flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|
    ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|
    txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
    unset req.http.Cookie;
    return (hash);
    }
    }
    sub vcl_backend_response {
    if (bereq.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|
    flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|
    ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|
    txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
    unset beresp.http.set-cookie;
    }
    if (bereq.url ~ "^[^?]*\.(7z|avi|bz2|flac|flv|gz|mka|mkv|mov|mp3|mp4|
    mpeg|mpg|ogg|ogm|opus|rar|tar|tgz|tbz|txz|wav|webm|xz|zip)(\?.*)?$") {
    unset beresp.http.set-cookie;
    set beresp.do_stream = true;
    set beresp.do_gzip = false;
    }
    }
    Cache static assets

    View Slide

  46. Do you really
    want to cache
    static assets?

    View Slide

  47. Nginx or
    Apache can
    be fast
    enough for
    that

    View Slide

  48. vcl 4.0;
    import std;
    sub vcl_recv {
    if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|
    flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|
    ogg|ogm|opus|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|
    txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
    unset req.http.Cookie;
    return (pass);
    }
    }
    Don’t cache static assets

    View Slide

  49. URL whitelist/blacklist

    View Slide

  50. sub vcl_recv {
    if (req.url ~ "^/status\.php$" ||
    req.url ~ "^/update\.php$" ||
    req.url ~ "^/admin$" ||
    req.url ~ "^/admin/.*$" ||
    req.url ~ "^/user$" ||
    req.url ~ "^/user/.*$" ||
    req.url ~ "^/flag/.*$" ||
    req.url ~ "^.*/ajax/.*$" ||
    req.url ~ "^.*/ahah/.*$") {
    return (pass);
    }
    }
    URL blacklist

    View Slide

  51. sub vcl_recv {
    if (req.url ~ "^/products/?"
    return (hash);
    }
    }
    URL whitelist

    View Slide

  52. Those damn
    cookies again!

    View Slide

  53. vcl 4.0;
    sub vcl_recv {
    set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "_gat=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", "");
    if (req.http.cookie ~ "^\s*$") {
    unset req.http.cookie;
    }
    }
    Remove tracking cookies

    View Slide

  54. vcl 4.0;
    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, ";(PHPSESSID)=", "; \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;
    }
    }
    }
    Only keep session cookie

    View Slide

  55. 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" ));
    }
    Language cookie cache variation

    View Slide

  56. Alternative
    language
    cache
    variation

    View Slide

  57. sub vcl_hash {
    hash_data(req.http.Accept-Language);
    }
    Language cookie cache variation
    Or just send a
    “Vary:Accept-Language”
    header

    View Slide

  58. sub vcl_hash {
    hash_data(req.http.Cookie);
    }
    Hash all cookies

    View Slide

  59. Edge Side Includes

    View Slide

  60. 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;
    }
    }
    Edge Side Includes

    View Slide

  61. header("Cache-Control: public,must-revalidate,s-maxage=10");
    echo "Date in the ESI tag: ".date('Y-m-d H:i:s').'
    ';
    header("Cache-Control: no-store");
    header(“Surrogate-Control: content='ESI/1.0'");
    echo ''.PHP_EOL;
    echo "Date in the main page: ".date('Y-m-d H:i:s').'
    ';
    Main page
    ESI frame:
    esi.php
    Cached for
    10 seconds
    Not cached

    View Slide

  62. ESI_xmlerror No ESI processing, first char
    not 'Expects HTML/
    XML tags

    View Slide

  63. -p feature=+esi_disable_xml_check
    Add as
    startup option

    View Slide

  64. Control
    Time
    To
    Live

    View Slide

  65. sub vcl_backend_response {
    set beresp.ttl = 3h;
    }
    Control Time To Live

    View Slide

  66. sub vcl_backend_response {
    if (beresp.ttl <= 0s || beresp.http.Set-Cookie ||
    beresp.http.Vary == "*") {
    set beresp.ttl = 120s;
    set beresp.uncacheable = true;
    return (deliver);
    }
    }
    Control Time To Live

    View Slide

  67. Debugging

    View Slide

  68. sub vcl_deliver {
    if (obj.hits > 0) {
    set resp.http.X-Cache = "HIT";
    } else {
    set resp.http.X-Cache = "MISS";
    }
    }
    Debugging

    View Slide

  69. Anonymize

    View Slide

  70. sub vcl_deliver {
    unset resp.http.X-Powered-By;
    unset resp.http.Server;
    unset resp.http.X-Drupal-Cache;
    unset resp.http.X-Varnish;
    unset resp.http.Via;
    unset resp.http.Link;
    unset resp.http.X-Generator;
    }
    Anonymize

    View Slide

  71. Purging

    View Slide

  72. acl purge {
    "localhost";
    "127.0.0.1";
    "::1";
    }
    sub vcl_recv {
    if (req.method == "PURGE") {
    if (!client.ip ~ purge) {
    return (synth(405, “Not allowed.”));
    }
    return (purge);
    }
    }
    Purging

    View Slide

  73. acl purge {
    "localhost";
    "127.0.0.1";
    "::1";
    }
    sub vcl_backend_response {
    set beresp.http.x-url = bereq.url;
    set beresp.http.x-host = bereq.http.host;
    }
    sub vcl_deliver {
    unset resp.http.x-url;
    unset resp.http.x-host;
    }
    sub vcl_recv {
    if (req.method == "PURGE") {
    if (!client.ip ~ purge) {
    return (synth(405, "Not allowed"));
    }
    if(req.http.x-purge-regex) {
    ban("obj.http.x-host == " + req.http.host + " && obj.http.x-url ~ " +
    req.http.x-purge-regex);
    } else {
    ban("obj.http.x-host == " + req.http.host + " && obj.http.x-url == " +
    req.url);
    }
    return (synth(200, "Purged"));
    }
    }
    Banning

    View Slide

  74. Banning
    curl -XPURGE -H "x-purge-regex:/products"
    "http://example.com"
    curl -XPURGE "http://example.com/products"

    View Slide

  75. Grace mode

    View Slide

  76. sub vcl_backend_response {
    set beresp.grace = 6h;
    }
    Grace mode

    View Slide

  77. Cheers, hope
    this helps!

    View Slide

  78. View Slide