Going to production on a Raspberry Pi
Who am I?
Jérémy DERUSSÉ
Web Technical Leader
AptusHealth
@jderusse
How to get fast response
from a Symfony application?
you can't
How to get fast response
from a Symfony application?
your backend
you can't
Solution
- using http shared cache
- describe in
RFC-2616RFC-7234
Integration in Symfony
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* @Cache(smaxage="3600")
*/
public function indexAction()
{
// ...
}
Advanced usage with FriendsOfSymfony/FOSHttpCacheBundle
Issue #1
Unsharable resource
Reason
- private resources (ie. invoice, shopping cart, ...)
- restricted area
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
Reason
There are only two hard things in Computer Science: cache invalidation and naming things.
Phil Karlton
Cache models
Validation model
- etag
- last-modified
drawback
- Kernel booted
- hard to implement
Expiration model
- expires
- cache-control
drawback
- cannot be invalidate
cache model
In the real world
- Life time is not predictable
- Backend can't validate every cache HIT
cache model
In the real world
- Life time is not predictable
- Backend can't validate every cache HIT
BUT
- Page is build on top of resources
- Backend knows when resources change
- Backend can tell varnish to invalidate things
- Varnish can partialy invalidate cache based on queries
Varnish tags
curl "http://varnish.myapp.com"
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: Foo,Bar
HTTP/1.1 200 OK
Cache-Control: public, s-maxage=3600
X-Cache-Tags: Foo
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: Post42,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
- Collect displayed entities
- Generate resource identifier
- Tag response
Invalidate cache
Automate Tagging
Tagging Response - 1. Collect displayed entities
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)
{
$entity = $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 App\EventListener;
use App\Tag\TagExtractorInterface;
use FOS\HttpCacheBundle\Handler\TagHandler;
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());
}
//...
}
Automate Tagging
Tagging Response - 3. Tag response
namespace App\EventListener;
use App\Tag\TagExtractorInterface;
use FOS\HttpCacheBundle\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);
}
//...
}
Automate Tagging
Tagging Response
- Collect displayed entities
- Generate resource identifier
- Tag response
Invalidate cache
- Listen changes
- Generate resource identifier
- Call varnish
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 $entity) {
// TODO
}
foreach ($uow->getScheduledEntityDeletions() as $entity) {
// TODO
}
}
}
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 $entity) {
$tags += $this->tagExtractor->extract($entity);
}
foreach ($uow->getScheduledEntityDeletions() as $entity) {
$tags += $this->tagExtractor->extract($entity);
}
}
}
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(array_keys($tags));
}
}
Automate Tagging
Tagging Response
- Collect displayed entities
- Generate resource identifier
- Tag response
Invalidate cache
- Listen changes
- Generate resource identifier
- Call varnish
Enjoy
Demo
Software
- marmelab/admin-on-rest
- friendsofsymfony/rest-bundle
- friendsofsymfony/http-cache-bundle
- jms/serializer-bundle
- symfony/symfony - env=dev
- doctrine/doctrine-bundle - fetch=lazy
Demo
Demo
Hardware
- cluster of 5 ARM machines
- docker
swarmkubernetesvanilla - 100$
Silver bullet?
Works well when
- Read >> Write
- Know resources used to build response
Drawback
- Operations are not Atomic
- Application must handle writes
- Slows writes
Thank You
Questions
SfLive 2017
By Jérémy Derussé
SfLive 2017
- 1,144