Going to production on
a Raspberry Pi with
varnish tags
Who am I?
Jérémy DERUSSÉ
Web Technical Leader
AptusHealth
@jderusse
How to get fast responses
from a Symfony application?
you can't
How to get fast responses
from a Symfony application?your backend
you can't
Solution
using http shared cache
describe in RFC-2616 RFC-7234
Integration in Symfony
use SensioBundleFrameworkExtraBundleConfigurationCache;
/**
* @Cache(smaxage="3600")
*/
public function indexAction()
{
// ...
}
Advanced usage with FriendsOfSymfony/FOSHttpCacheBundle
Issue #1
Unsharable resources
private resources (ie. invoice, shopping cart, ...)
per role representations
Solution
vary cache on user/role/other
see SfLive 2015 Jérôme Vieilledent & David Buchmann
Repousser les limites : HTTP cache et utilisateurs connectés
Issue #2
Cache invalidation
“ There are only two hard things in Computer Science: cache
invalidation and naming things.
Phil Karlton
Cache models
Validation model
etag
last-modified
drawbacks
application is booted
hard to implement
Expiration model
expires
cache-control
drawback
no control on invalidation
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
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
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"
}
]
}
Automate Tagging
Tagging Response
1. Collect displayed resources
2. Generate resource identifier
3. Tag response
Automate Tagging
Tagging Response - 1. Collect displayed resources
namespace AppEventListener;
use JMSSerializerEventDispatcherEvents;
use JMSSerializerEventDispatcherEventSubscriberInterface;
use JMSSerializerEventDispatcherObjectEvent;
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',
],
];
}
}
Automate Tagging
Tagging Response - 2. Generate resource identifier
namespace AppEventListener;
use AppTagTagExtractorInterface;
use FOSHttpCacheBundleHandlerTagHandler;
use JMSSerializerEventDispatcherEventSubscriberInterface;
use JMSSerializerEventDispatcherObjectEvent;
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());
}
//...
}
Automate Tagging
Tagging Response - 3. Tag response
namespace AppEventListener;
use AppTagTagExtractorInterface;
use FOSHttpCacheBundleHandlerTagHandler;
use JMSSerializerEventDispatcherEventSubscriberInterface;
use JMSSerializerEventDispatcherObjectEvent;
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);
}
//...
}
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
Automate Tagging
Invalidate Cache - 1. Listen changes
namespace AppEventListener;
use DoctrineCommonEventSubscriber;
use DoctrineORMEventOnFlushEventArgs;
use DoctrineORMEvents;
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
}
}
}
Automate Tagging
Invalidate Cache - 2. Generate resource identifier
namespace AppEventListener;
use AppTagTagExtractorInterface;
use DoctrineCommonEventSubscriber;
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
}
}
Automate Tagging
Invalidate Cache - 3. Call varnish
namespace AppEventListener;
use AppTagTagExtractorInterface;
use DoctrineCommonEventSubscriber;
use FOSHttpCacheHandlerTagHandler;
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);
}
}
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
Silver bullet?
Works well when
HIT >> MISS
Read >> Write
Application knows resources used to build
response
Drawback
Operations are not Atomic
Backend handles writes
Backend knows infrastructure
It slows writes
Demo
Software
- env=dev
- fetch=lazy
symfony/symfony
doctrine/doctrine-bundle
friendsofsymfony/rest-bundle
jms/serializer-bundle
friendsofsymfony/http-cache-bundle
marmelab/admin-on-rest
Demo
Hardware
Raspberry Pi Orange Pi
docker
MySQL
NGINX
PHP7-FPM
Varnish
$9.59
Demo
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
Thank You
Questions?
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://de.wikipedia.org/wiki/Carambar
https://rcgo.com.br/recursos.html

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

  • 1.
    Going to productionon a Raspberry Pi with varnish tags
  • 2.
    Who am I? JérémyDERUSSÉ Web Technical Leader AptusHealth @jderusse
  • 3.
    How to getfast responses from a Symfony application? you can't
  • 4.
    How to getfast responses from a Symfony application?your backend you can't
  • 5.
    Solution using http sharedcache describe in RFC-2616 RFC-7234
  • 6.
    Integration in Symfony useSensioBundleFrameworkExtraBundleConfigurationCache; /** * @Cache(smaxage="3600") */ public function indexAction() { // ... } Advanced usage with FriendsOfSymfony/FOSHttpCacheBundle
  • 7.
    Issue #1 Unsharable resources privateresources (ie. invoice, shopping cart, ...) per role representations Solution vary cache on user/role/other see SfLive 2015 Jérôme Vieilledent & David Buchmann Repousser les limites : HTTP cache et utilisateurs connectés
  • 8.
    Issue #2 Cache invalidation “There are only two hard things in Computer Science: cache invalidation and naming things. Phil Karlton
  • 9.
    Cache models Validation model etag last-modified drawbacks applicationis booted hard to implement Expiration model expires cache-control drawback no control on invalidation
  • 10.
    cache model In areal 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
  • 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
  • 12.
    Varnish tags curl "http://varnish.myapp.com/posts/42" HTTP/1.1200 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" } ] }
  • 13.
    Automate Tagging Tagging Response 1.Collect displayed resources 2. Generate resource identifier 3. Tag response
  • 14.
    Automate Tagging Tagging Response- 1. Collect displayed resources namespace AppEventListener; use JMSSerializerEventDispatcherEvents; use JMSSerializerEventDispatcherEventSubscriberInterface; use JMSSerializerEventDispatcherObjectEvent; 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', ], ]; } }
  • 15.
    Automate Tagging Tagging Response- 2. Generate resource identifier namespace AppEventListener; use AppTagTagExtractorInterface; use FOSHttpCacheBundleHandlerTagHandler; use JMSSerializerEventDispatcherEventSubscriberInterface; use JMSSerializerEventDispatcherObjectEvent; 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()); } //... }
  • 16.
    Automate Tagging Tagging Response- 3. Tag response namespace AppEventListener; use AppTagTagExtractorInterface; use FOSHttpCacheBundleHandlerTagHandler; use JMSSerializerEventDispatcherEventSubscriberInterface; use JMSSerializerEventDispatcherObjectEvent; 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); } //... }
  • 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
  • 18.
    Automate Tagging Invalidate Cache- 1. Listen changes namespace AppEventListener; use DoctrineCommonEventSubscriber; use DoctrineORMEventOnFlushEventArgs; use DoctrineORMEvents; 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 } } }
  • 19.
    Automate Tagging Invalidate Cache- 2. Generate resource identifier namespace AppEventListener; use AppTagTagExtractorInterface; use DoctrineCommonEventSubscriber; 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 } }
  • 20.
    Automate Tagging Invalidate Cache- 3. Call varnish namespace AppEventListener; use AppTagTagExtractorInterface; use DoctrineCommonEventSubscriber; use FOSHttpCacheHandlerTagHandler; 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); } }
  • 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
  • 22.
    Silver bullet? Works wellwhen HIT >> MISS Read >> Write Application knows resources used to build response Drawback Operations are not Atomic Backend handles writes Backend knows infrastructure It slows writes
  • 23.
  • 24.
    Demo Hardware Raspberry Pi OrangePi docker MySQL NGINX PHP7-FPM Varnish $9.59
  • 25.
  • 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
  • 27.
  • 28.
  • 29.