$30 off During Our Annual Pro Sale. View Details »

Developing cacheable PHP applications

Developing cacheable PHP applications

This talk was delivered at the Drupal NYC meetup on July 22nd.

See https://feryn.eu/speaking/developing-cacheable-php-applications-2/ for more details

Thijs Feryn

July 22, 2019
Tweet

More Decks by Thijs Feryn

Other Decks in Technology

Transcript

  1. DEVELOPING CACHEABLE
    PHP APPLICATIONS
    Thijs Feryn

    View Slide

  2. Slow websites
    SUCK

    View Slide

  3. WEB PERFORMANCE IS AN
    ESSENTIAL PART OF THE
    USER EXPERIENCE

    View Slide

  4. SLOW ~ DOWN

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. THROWING
    SERVERS
    AT THE PROBLEM

    View Slide

  9. MO' MONEY
    MO' SERVERS
    MO' PROBLEMS

    View Slide

  10. IDENTIFY SLOWEST PARTS

    View Slide

  11. OPTIMIZE

    View Slide

  12. AFTER A WHILE YOU HIT THE LIMITS

    View Slide

  13. CACHE

    View Slide

  14. HI, I'M THIJS

    View Slide

  15. I'M AN
    EVANGELIST
    AT

    View Slide

  16. View Slide

  17. View Slide

  18. I'M @THIJSFERYN

    View Slide

  19. View Slide

  20. WE'RE HOSTING A MEETUP
    ✓ THURSDAY JULY 25TH
    ✓ 6PM - 9PM
    ✓ BARCADE, CHELSEA, NYC
    ✓ RSVP !
    https://info.varnish-software.com/varnish-nyc-meetup-2019

    View Slide

  21. View Slide

  22. DON’T RECOMPUTE
    IF THE DATA
    HASN’T CHANGED

    View Slide

  23. View Slide

  24. View Slide

  25. REVERSE CACHING PROXY

    View Slide

  26. NORMALLY
    USER SERVER

    View Slide

  27. WITH REVERSE CACHING PROXY
    USER PROXY SERVER

    View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. HTTP CACHING MECHANISMS
    Expires: Sat, 23 July 2019 00:30:00 GMT
    Cache-control: public, max-age=3600,
    s-maxage=86400
    Cache-control: private, no-cache, no-store

    View Slide

  32. IN AN IDEAL WORLD

    View Slide

  33. ✓STATELESS
    ✓WELL-DEFINED TTL
    ✓CACHE / NO-CACHE PER RESOURCE
    ✓CACHE VARIATIONS
    ✓CONDITIONAL REQUESTS
    ✓PLACEHOLDERS FOR NON-CACHEABLE CONTENT
    ✓EDGE-SIDE LOGIC FOR PERSONALIZED CACHING
    IN AN IDEAL WORLD

    View Slide

  34. REALITY
    SUCKS

    View Slide

  35. View Slide

  36. View Slide

  37. TIME TO LIVE

    View Slide

  38. CACHE VARIATIONS

    View Slide

  39. LEGACY

    View Slide

  40. WHAT IF WE COULD
    DESIGN OUR
    SOFTWARE WITH
    HTTP CACHING IN
    MIND?

    View Slide

  41. CACHING STATE OF MIND
    ✓ PORTABILITY
    ✓ DEVELOPER EMPOWERMENT
    ✓ CONTROL
    ✓ CONSISTENT CACHING BEHAVIOR

    View Slide

  42. View Slide

  43. CACHE-CONTROL

    View Slide

  44. Cache-Control: public, s-maxage=500

    View Slide

  45. namespace App\Controller;
    use Symfony\Component\HttpFoundation\Request;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class DefaultController extends Controller
    {
    /**
    * @Route("/", name="home")
    */
    public function index()
    {
    return $this
    ->render('index.twig')
    ->setSharedMaxAge(500)
    ->setPublic();
    }
    }

    View Slide

  46. Cache-Control: private, no-store

    View Slide

  47. /**
    * @Route("/private", name="private")
    */
    public function private()
    {
    $response = $this
    ->render('private.twig')
    ->setPrivate();
    $response->headers->addCacheControlDirective('no-store');
    return $response;
    }

    View Slide

  48. CONDITIONAL
    REQUESTS

    View Slide

  49. ONLY FETCH PAYLOAD THAT HAS CHANGED

    View Slide

  50. HTTP/1.1 200 OK

    View Slide

  51. OTHERWISE:
    HTTP/1.1 304 NOT MODIFIED

    View Slide

  52. CONDITIONAL REQUESTS
    HTTP/1.1 200 OK
    Host: localhost
    Etag: 7c9d70604c6061da9bb9377d3f00eb27
    Content-type: text/html; charset=UTF-8
    Hello world output
    GET / HTTP/1.1
    Host: localhost

    View Slide

  53. CONDITIONAL REQUESTS
    HTTP/1.1 304 Not Modified
    Host: localhost
    Etag: 7c9d70604c6061da9bb9377d3f00eb27
    GET / HTTP/1.1
    Host: localhost
    If-None-Match: 7c9d70604c6061da9bb9377d3f00eb27

    View Slide

  54. CONDITIONAL REQUESTS
    HTTP/1.1 200 OK
    Host: localhost
    Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT
    Content-type: text/html; charset=UTF-8
    Hello world output
    GET / HTTP/1.1
    Host: localhost

    View Slide

  55. CONDITIONAL REQUESTS
    HTTP/1.1 304 Not Modified
    Host: localhost
    Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT
    GET / HTTP/1.1
    Host: localhost
    If-Last-Modified: Fri, 22 Jul 2016 10:11:16 GMT

    View Slide

  56. Cache-Control: public, max-age=100,
    s-maxage=500, stale-while-revalidate=20

    View Slide

  57. QUICKLY

    View Slide

  58. EARLY

    View Slide

  59. Store &
    retrieve
    Etag

    View Slide

  60. namespace App\EventListener;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpKernel\Event\GetResponseEvent;
    use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
    use SymfonyBundles\RedisBundle\Redis\Client as RedisClient;
    class ConditionalRequestListener
    {
    protected $redis;
    public function __construct(RedisClient $redis)
    {
    $this->redis = $redis;
    }
    protected function isModified(Request $request, $etag)
    {
    if ($etags = $request->getETags()) {
    return in_array($etag, $etags) || in_array('*', $etags);
    }
    return true;
    }
    ...
    src/EventListener/ConditionalRequestListener.php

    View Slide

  61. {
    $this->redis = $redis;
    }
    protected function isModified(Request $request, $etag)
    {
    if ($etags = $request->getETags()) {
    return in_array($etag, $etags) || in_array('*', $etags);
    }
    return true;
    }
    public function onKernelRequest(GetResponseEvent $event)
    {
    $request = $event->getRequest();
    $etag = $this->redis->get('etag:'.md5($request->getUri()));
    if(!$this->isModified($request,$etag)) {
    $event->setResponse(Response::create('Not Modified',Response::HTTP_NOT_MODIFIED));
    }
    }
    public function onKernelResponse(FilterResponseEvent $event)
    {
    $response = $event->getResponse();
    $request = $event->getRequest();
    $etag = md5($response->getContent());
    $response->setEtag($etag);
    if($this->isModified($request,$etag)) {
    $this->redis->set('etag:'.md5($request->getUri()),$etag);
    }
    }
    }
    src/EventListener/ConditionalRequestListener.php

    View Slide

  62. CONTENT COMPOSITION
    & PLACEHOLDERS

    View Slide

  63. View Slide

  64. Shopping
    cart or account
    information

    View Slide

  65. SESSION
    COOKIE
    NO CACHE

    View Slide

  66. CODE
    RENDERS
    SINGLE
    HTTP
    RESPONSE

    View Slide

  67. LOWEST
    COMMON
    DENOMINATOR:
    NO CACHE

    View Slide

  68. PLACEHOLDERS

    View Slide

  69. AJAX

    View Slide

  70. Non-cached
    AJAX call

    View Slide

  71. EDGE SIDE INCLUDES

    View Slide

  72. EDGE SIDE INCLUDES
    ✓ PLACEHOLDER
    ✓ W3C STANDARD
    ✓ PROCESSED "ON THE EDGE" (E.G. VARNISH)
    ✓ OUTPUT IS A COMPOSITION OF BLOCKS
    ✓ STATE PER BLOCK
    ✓ TTL PER BLOCK

    View Slide

  73. VARNISH Surrogate-Capability: key="ESI/1.0"
    Surrogate-Control: content="ESI/1.0"

    BACKEND
    Parse ESI placeholders
    VARNISH

    View Slide

  74. Non-cached ESI
    placeholder

    View Slide

  75. ESI VS AJAX

    View Slide

  76. EDGE SIDE INCLUDES
    ✓ SERVER-SIDE
    ✓ STANDARDIZED
    ✓ PROCESSED ON THE
    “EDGE”, NOT IN THE
    BROWSER
    ✓ GENERALLY FASTER
    - SEQUENTIAL 

    (ONLY PARALLEL IN ENTERPRISE VERSION)
    - ONE FAILS, ALL FAIL
    - LIMITED IMPLEMENTATION
    IN VARNISH

    View Slide

  77. AJAX
    ✓ CLIENT-SIDE
    ✓ COMMON KNOWLEDGE
    ✓ PARALLEL PROCESSING
    ✓ GRACEFUL DEGRADATION
    - PROCESSED BY THE
    BROWSER
    - EXTRA ROUNDTRIPS
    - SOMEWHAT SLOWER

    View Slide

  78. COMPOSITION AT THE VIEW LAYER

    View Slide

  79. /**
    * @Route("/", name="home")
    */
    public function index()
    {
    return $this
    ->render('index.twig')
    ->setPublic()
    ->setSharedMaxAge(500);
    }
    /**
    * @Route("/header", name="header")
    */
    public function header()
    {
    $response = $this
    ->render('header.twig')
    ->setPrivate();
    $response->headers->addCacheControlDirective('no-store');
    return $response;
    }
    /**
    * @Route("/footer", name="footer")
    */
    public function footer()
    {
    $response = $this->render('footer.twig');
    $response
    ->setSharedMaxAge(500)
    ->setPublic();
    return $response;
    }
    /**
    * @Route("/nav", name="nav")
    */
    public function nav()
    {
    $response = $this->render('nav.twig');
    $response
    ->setVary('X-Login',false)
    ->setSharedMaxAge(500)
    ->setPublic();
    return $response;
    }
    Controller
    action per
    fragment

    View Slide


  80. {{ include('header.twig') }}



    {{ include('nav.twig') }}



    {% block content %}{% endblock %}



    {{ include('footer.twig') }}



    {{ render_esi(url('header')) }}



    {{ render_esi(url('nav')) }}



    {% block content %}{% endblock %}



    {{ render_esi(url('footer')) }}


    View Slide









  81. An example page Rendered at 2017-05-17 16:57:14

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris consequat orci eget libero
    sollicitudin,…




    View Slide

  82. CACHE VARIATIONS

    View Slide

  83. HOW DO YOU IDENTIFY AN
    OBJECT IN CACHE?

    View Slide

  84. THE URL IDENTIFIES
    THE OBJECT IN CACHE

    View Slide

  85. WHAT IF THE CONTENT
    OF A URL VARIES
    BASED ON THE VALUE
    OF A REQUEST
    HEADER?

    View Slide

  86. CACHE VARIATIONS
    HTTP/1.1 200 OK
    Host: localhost
    Content-Language: en
    Content-type: text/html; charset=UTF-8
    Hello world output
    GET / HTTP/1.1
    Host: localhost
    Accept-Language: en, nl, de

    View Slide

  87. Vary: Accept-Language
    Request
    header
    value
    Response
    header

    View Slide

  88. WHAT IF YOU CAN'T
    LEVERAGE HTTP?

    View Slide

  89. WRITE VCL CODE

    View Slide

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

    View Slide

  91. 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,
    ";(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);
    }
    }
    }
    Cookie
    example

    View Slide

  92. View Slide

  93. VARNISH ENTERPRISE
    ✓ CLIENT SSL TERMINATION
    ✓ BACKEND SSL CONNECTIONS
    ✓ PARALLEL ESI
    ✓ MASSIVE STORAGE ENGINE
    ✓ ENCRYPTION
    ✓ THROTTLING
    ✓ RATE LIMITING
    ✓ PREFETCHING
    ✓ GEOLOCATION
    ✓ AUTHENTICATION
    ✓ EDGESTASH
    ✓ CUSTOM STATISTICS
    ✓ ADMIN MODULE
    ✓ SUPPORT
    ✓ HIGH AVAILABILITY

    View Slide

  94. EDGE CONTENT
    TRANSFORMATION

    View Slide

  95. HTTP/1.1 200 OK
    Accept-Ranges: bytes
    Age: 11
    Cache-Control: max-age=30, public, s-maxage=100
    Content-Type: text/html; charset=UTF-8
    Date: Thu, 11 Jul 2019 13:47:57 GMT
    X-Varnish: 262420 262415
    Hello Thijs
    Hello {{username}}
    USER
    VARNISH
    ENTERPRISE
    ORIGIN
    GET /liked HTTP/1.1
    Cookie:PHPSESSID=21779b3a7a75fca1851954b9810faa26
    GET sf_s21779b3a7a75fca1851954b9810faa26

    View Slide

  96. HTTPS://AWS.AMAZON.COM/QUICKSTART/ARCHITECTURE/VARNISH/

    View Slide

  97. View Slide

  98. View Slide

  99. WE'RE HOSTING A MEETUP
    ✓ THURSDAY JULY 25TH
    ✓ 6PM - 9PM
    ✓ BARCADE, CHELSEA, NYC
    ✓ RSVP !
    https://info.varnish-software.com/varnish-nyc-meetup-2019

    View Slide

  100. View Slide

  101. HTTPS://FERYN.EU
    HTTPS://TWITTER.COM/THIJSFERYN
    HTTPS://INSTAGRAM.COM/THIJSFERYN

    View Slide