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

Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi

Grâce aux tags Varnish, j'ai switché ma prod sur Raspberry Pi

Le moyen le plus rapide d'obtenir une réponse d'un Backend est de ne pas l'appeler ;-) Une solution fournie par les "reverse-proxy" me direz-vous, mais pas si simple d'invalider le cache... Ce talk aborde une fonctionnalité méconnue de Varnish: les tags. Nous verrons comment en tirer partie via les "event listeners" d'une application Symfony standard. Au menu, un cluster de Rasberry Pi, une API, et des données toujours fraîches sous la milliseconde

Jérémy Derussé

May 18, 2017
Tweet

More Decks by Jérémy Derussé

Other Decks in Programming

Transcript

  1. Going to production on
    a Raspberry Pi with
    varnish tags

    View Slide

  2. Who am I?
    Jérémy DERUSSÉ
    Web Technical Leader
    author of Symfony Lock Component 3.3
    @jderusse

    View Slide

  3. How to get fast responses
    from a PHP application?
    you can't

    View Slide

  4. How to get fast responses
    from a PHP application?
    your backend
    you can't

    View Slide

  5. Solution
    using http shared cache
    describe in RFC-2616 RFC-7234

    View Slide

  6. Integration in Symfony
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
    /**
    * @Cache(smaxage="3600")
    */
    public function indexAction()
    {
    // ...
    }
    Advanced usage with FriendsOfSymfony/FOSHttpCache

    View Slide

  7. Issue #1
    Unsharable resources
    private resources (ie. invoice, shopping cart, ...)
    per role representations
    Solution
    vary cache on user/role/other
    Repousser les limites : HTTP cache et utilisateurs connectés

    View Slide

  8. Issue #2
    Cache invalidation
    “ There are only two hard things in Computer Science: cache
    invalidation and naming things.
    Phil Karlton

    View Slide

  9. Cache models
    Validation model
    etag
    last-modified
    drawbacks
    application is booted
    hard to implement
    Expiration model
    expires
    cache-control
    drawback
    no control on invalidation

    View Slide

  10. cache model
    In a real world
    Backend can't validate every cache HIT
    Life time is not predictable
    BUT
    Varnish can be requested to partially invalidate responses
    Backend knows when resources change
    Responses are build on top of resources

    View Slide

  11. curl -I "http://varnish.myapp.com/c"
    curl -I "http://varnish.myapp.com/b"
    curl -I "http://varnish.myapp.com/a"
    HTTP/1.1 200 OK
    Cache-Control: public, s-maxage=3600
    X-Cache-Tags: Foo,Bar
    HTTP/1.1 200 OK
    Cache-Control: public, s-maxage=3600
    X-Cache-Tags: Foo
    Varnish tags
    curl \
    -X "BAN" \
    -H "X-Cache-Tags: Foo" \
    "http://varnish.myapp.com"
    HTTP/1.1 200 OK
    Cache-Control: public, s-maxage=3600
    X-Cache-Tags: Bar,Qux

    View Slide

  12. Varnish tags
    curl "http://varnish.myapp.com/posts/42"
    HTTP/1.1 200 OK
    Cache-Control: public, s-maxage=86400
    X-Cache-Tags: Post:42,Author:12,Comment:314,Comment:1337
    {
    "id": 42,
    "title": "My blog post.",
    "body": "Lorem Ipsum.",
    "author": {
    "id": 12,
    "username": "jderusse"
    },
    "comments": [
    {
    "id": 314,
    "message": "Wow such post"
    },
    {
    "id": 1337,
    "message": "much performance"
    }
    ]
    }

    View Slide

  13. Automate Tagging
    Tagging Response
    1. Collect displayed resources
    2. Generate resource identifier
    3. Tag response

    View Slide

  14. Automate Tagging
    Tagging Response - 1. Collect displayed resources
    namespace App\EventListener;
    use JMS\Serializer\EventDispatcher\Events;
    use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
    use JMS\Serializer\EventDispatcher\ObjectEvent;
    class SerializationTagListener implements EventSubscriberInterface
    {
    public function onPostSerialize(ObjectEvent $event)
    {
    $resource = $event->getObject();
    // TODO
    }
    public static function getSubscribedEvents()
    {
    return [
    [
    'event' => Events::POST_SERIALIZE,
    'format' => 'json',
    'method' => 'onPostSerialize',
    ],
    ];
    }
    }

    View Slide

  15. Automate Tagging
    Tagging Response - 2. Generate resource identifier
    namespace App\EventListener;
    use App\Tag\TagExtractorInterface;
    use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
    use JMS\Serializer\EventDispatcher\ObjectEvent;
    class SerializationTagListener implements EventSubscriberInterface
    {
    private $tagExtractor;
    public function __construct(TagExtractorInterface $tagExtractor)
    {
    $this->tagExtractor = $tagExtractor;
    }
    public function onPostSerialize(ObjectEvent $event): void
    {
    //...
    $tags = $this->tagExtractor->extract($event->getObject());
    }
    //...
    }

    View Slide

  16. Automate Tagging
    Tagging Response - 3. Tag response
    namespace App\EventListener;
    use App\Tag\TagExtractorInterface;
    use FOS\HttpCache\Handler\TagHandler;
    use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
    use JMS\Serializer\EventDispatcher\ObjectEvent;
    class SerializationTagListener implements EventSubscriberInterface
    {
    private $tagExtractor;
    private $tagHandler;
    public function __construct(TagExtractorInterface $tagExtractor, TagHandler $tagHandler)
    {
    $this->tagExtractor = $tagExtractor;
    $this->tagHandler = $tagHandler;
    }
    public function onPostSerialize(ObjectEvent $event): void
    {
    $tags = $this->tagExtractor->extract($event->getObject());
    $this->tagHandler->addTags($tags);
    }
    //...
    }

    View Slide

  17. Automate Tagging
    Tagging Response
    1. Collect displayed resources
    2. Generate resource identifier
    3. Tag response
    Invalidate cache
    1. Listen changes
    2. Generate resource identifier
    3. Call varnish

    View Slide

  18. Automate Tagging
    Invalidate Cache - 1. Listen changes
    namespace App\EventListener;
    use Doctrine\Common\EventSubscriber;
    use Doctrine\ORM\Event\OnFlushEventArgs;
    use Doctrine\ORM\Events;
    class DoctrineInvalidationTagListener implements EventSubscriber
    {
    public function getSubscribedEvents()
    {
    return [Events::onFlush];
    }
    public function onFlush(OnFlushEventArgs $eventArgs)
    {
    $uow = $eventArgs->getEntityManager()->getUnitOfWork();
    foreach ($uow->getScheduledEntityUpdates() as $resource) {
    // TODO
    }
    foreach ($uow->getScheduledEntityDeletions() as $resource) {
    // TODO
    }
    }
    }

    View Slide

  19. Automate Tagging
    Invalidate Cache - 2. Generate resource identifier
    namespace App\EventListener;
    use App\Tag\TagExtractorInterface;
    use Doctrine\Common\EventSubscriber;
    class DoctrineInvalidationTagListener implements EventSubscriber
    {
    private $tagExtractor;
    public function __construct(TagExtractorInterface $tagExtractor)
    {
    $this->tagExtractor = $tagExtractor;
    }
    public function onFlush(OnFlushEventArgs $eventArgs)
    {
    $uow = $eventArgs->getEntityManager()->getUnitOfWork();
    $tags = [];
    foreach ($uow->getScheduledEntityUpdates() as $resource) {
    $tags = array_merge($tags, $this->tagExtractor->extract($resource));
    }
    foreach ($uow->getScheduledEntityDeletions() as $resource) {
    $tags = array_merge($tags, $this->tagExtractor->extract($resource));
    }
    // TODO
    }
    }

    View Slide

  20. Automate Tagging
    Invalidate Cache - 3. Call varnish
    namespace App\EventListener;
    use App\Tag\TagExtractorInterface;
    use Doctrine\Common\EventSubscriber;
    use FOS\HttpCache\Handler\TagHandler;
    class DoctrineInvalidationTagListener implements EventSubscriber
    {
    private $tagExtractor;
    public function __construct(TagExtractorInterface $tagExtractor, TagHandler $tagHandler)
    {
    $this->tagExtractor = $tagExtractor;
    $this->tagHandler = $tagHandler;
    }
    public function onFlush(OnFlushEventArgs $eventArgs)
    {
    // ...
    $this->tagHandler->invalidateTags($tags);
    }
    }

    View Slide

  21. Automate Tagging
    Tagging Response
    1. Collect displayed resources
    2. Generate resource identifier
    3. Tag response
    Invalidate cache
    1. Listen changes
    2. Generate resource identifier
    3. Call varnish
    Enjoy

    View Slide

  22. Silver bullet?
    Works well when
    HIT >> MISS
    Read >> Write
    Application knows resources used to build
    response
    Drawback
    Operations are not Atomic
    B ackend handles writes
    Backend knows infrastructure
    It slows writes

    View Slide

  23. Demo
    - env=dev
    - fetch=lazy
    symfony/symfony
    doctrine/orm
    marmelab/admin-on-rest

    View Slide

  24. Demo
    Raspberry Pi Orange Pi
    docker
    NGINX
    PHP7-FPM
    Varnish
    9.99$

    View Slide

  25. Demo

    View Slide

  26. ab -n 8000 -c 26 192.168.1.17:81/comments/1
    This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net
    Licensed to The Apache Software Foundation, http://www.apache.org/
    Server Software: nginx/1.10.3
    Server Hostname: 192.168.1.16
    Server Port: 80
    Document Path: /comments/1
    Document Length: 576 bytes
    Concurrency Level: 26
    Time taken for tests: 26.912 seconds
    Complete requests: 200
    Failed requests: 0
    Total transferred: 193400 bytes
    HTML transferred: 115200 bytes
    Requests per second: 7.43 [#/sec] (mean)
    Time per request: 3498.553 [ms] (mean)
    Time per request: 134.560 [ms] (mean, across all concurrent
    Transfer rate: 7.02 [Kbytes/sec] received
    Connection Times (ms)
    min mean[+/-sd] median max
    Connect: 1 2 1.4 1 6
    Processing: 546 3342 1156.7 3020 7204
    Waiting: 546 3342 1156.7 3020 7204
    Total: 550 3344 1156.5 3021 7205
    in numbers
    ab -n 8000 -c 26 192.168.1.17/comments/1
    This is ApacheBench, Version 2.3 <$Revision: 1757674 $>
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net
    Licensed to The Apache Software Foundation, http://www.apache.org/
    Server Software: nginx/1.10.3
    Server Hostname: 192.168.1.17
    Server Port: 80
    Document Path: /comments/1
    Document Length: 576 bytes
    Concurrency Level: 26
    Time taken for tests: 2.340 seconds
    Complete requests: 8000
    Failed requests: 0
    Total transferred: 8948504 bytes
    HTML transferred: 4608000 bytes
    Requests per second: 3418.52 [#/sec] (mean)
    Time per request: 7.606 [ms] (mean)
    Time per request: 0.293 [ms] (mean, across all concurrent
    Transfer rate: 3734.21 [Kbytes/sec] received
    Connection Times (ms)
    min mean[+/-sd] median max
    Connect: 0 2 0.8 2 7
    Processing: 2 5 3.3 5 55
    Waiting: 2 5 3.3 4 55
    Total: 2 8 3.3 7 56
    app varnish

    View Slide

  27. Thank You

    View Slide

  28. Questions?
    @jderusse
    https://joind.in/talk/0a509

    View Slide

  29. Credits
    http://linuxgizmos.com/10-dollar-orange-pi-one-pits-quad-core-cortex-a7-against-pi-zero/
    http://toutsurlesbisounours.centerblog.net/rub-bisounours-3eme-generation-.html
    https://rcgo.com.br/recursos.html

    View Slide