making symofny shine with varnish - symfonycon madrid 2014

39
Making Symfony shine with Varnish

Upload: barel-barelon

Post on 08-Jul-2015

1.665 views

Category:

Internet


1 download

DESCRIPTION

Making Symofny shine with Varnish - SymfonyCon Madrid 2014 by Carlos Granados

TRANSCRIPT

Page 1: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine

with Varnish

Page 2: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

About me

Carlos Granados

Page 3: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

About me

Page 4: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Do we need a cache accelerator?

• Symfony is FAST considering all the features it provides• See my talk in last year’s deSymfony conference in Madrid (in

Spanish):

http://www.desymfony.com/ponencia/2013/porque-symfony2-es-rapido

Page 5: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Our case: clippingbook.com

Page 6: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Our case: clippingbook.com

• We were able to handle 100 req/sec• But this was not enough to handle our load, specially when

doing Facebook promotions• We chose Symfony because of its lower costs of development

and manteinance, not for its performance• We do not want to renounce to any Symfony features (ORM,

Twig templates, ...)• We could have scaled vertically or horizontally but chose to

implement a caching strategy first

Page 7: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

The solution: Varnish

• The solution: install Varnish Cache• Varnish Cache is a web application accelerator also known as a

caching HTTP reverse proxy• It sits in front of your HTTP server and caches its responses,

serving content from the cache whenever possible.• Result: we can now serve 10000 req/sec, a 100x improvement

Page 8: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

What we will not cover

• How HTTP caching works. For more information see:

http://tools.ietf.org/pdf/rfc2616.pdf (HTTP 1.1 specification, see section 13 for caching)http://symfony.com/doc/current/book/http_cache.html (HTTP caching chapter in the Symfony Book)

• Basic Varnish installation and configuration. See Fabien’s talk:

http://www.desymfony.com/ponencia/2012/varnish

Page 9: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

What we will cover

• Why Varnish• Quick overview of Varnish configuration• Varnish 4. What’s new• Using Varnish with Symfony:

• Backends• URL normalization• Cookies and sessions• Internacionalization• Serving content for different devices• Defining caching headers• Cache invalidation• Cache validation• Edge Side Includes (ESI)

Page 10: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Why Varnish?

PROs• Varnish is really fast and highly configurable• It is well documented in the Symfony documentation• There are some bundles which help you interact with it• Fabien’s talk provided very good information on how to use it

CONs• Varnish documentation not too good / VCL can be cryptic• It does not handle SSL• Only runs on 64 bit machines

Page 11: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Varnish configuration overview

• Varnish uses VCL, a DSL similar to C or Perl• Configuration saved to a file, usually /etc/varnish/default.vcl• Translated into C, compiled and linked => fast• Uses a number of subroutines which are called at specific times

during the handling of the request. For example vcl_recv• These functions return a value which defines the next action that

the system will take. For example fetch• There is a default VCL code for each function which is executed if

no value is returned• We have some objects which represent the request (req), the

response (resp), the backend request (bereq), the backend response (beresp) and the object in the cache (obj)

sub vcl_miss {

return (fetch);

}

Page 12: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Request flow

Page 13: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Request flow

• A request is received (vcl_recv) and we decide if we want to look it up in the cache (hash) or not (pass)

• If we do not look it up in the cache (vcl_pass) we fetch the response from the backend (fetch) and don´t store it in the cache

• If we want to look it up, we create a hash for the content (vcl_hash) and then look it up (lookup)

• If we find it in the cache (vcl_hit) we deliver it (deliver)• If we don’t find it in the cache (vcl_miss) we fetch the response

from the backend (fetch)• If we need to fetch the content, we build a request for the backend

and send it (vcl_backend_fetch)• We receive a response from the back end (vcl_backend_response),

decide if we want to cache it and deliver it (deliver)• We finally deliver the response to the client (vcl_deliver)

Page 14: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Varnish 4: what’s new

• Different threads are used for serving client requests and backend requests

• This split allows Varnish to refresh content in the background while serving stale content quickly to the client.

• Varnish now correctly handles cache validation, sending If-None-Match and If-Modified-Since headers and processing Etag and Last-Modified headers

Page 15: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Varnish 4: what’s changed

• req.request is now req.method (for example POST)• vcl_fetch is now vcl_backend_response• We have a new vcl_backend_fetch function• To mark responses as uncacheable (hit for pass) we now use beresp.uncacheable = true

• The purge function is no longer available. You purge content by returning purge from vcl_recv

• vcl_recv must now return hash instead of lookup• vcl_hash must now return lookup instead of hash• vcl_pass must now return fetch instead of pass• Backend restart is now retry• Logging tools like varnishlog now have a new filtering language

which means their syntax has changed (-m option => -q)

Page 16: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Load balancing: backends

backend back1 {

.host = "back1.clippingbook.com";

.port = "80";

}

backend back2 {

.host = "back2.clippingbook.com";

.port = "80";

}

sub vcl_init {

new backs = directors.hash();

backs.add_backend(back1,1);

backs.add_backend(back2,1);

}

sub vcl_recv {

set req.backend_hint = backs.backend(client.identity);

}

Page 17: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Load balancing: backends

• Varnish includes a health check mechanism and can exclude backends which are not healthy

• There are other load balancing mechanisms: random, round-robin, url-based (or build your own)

• BUT if you are using the standard file-based session save mechanism of Symfony the only method safe to use is hashbased on client ip or client session cookie

• Even this can lead to problems if one server turns unhealthy and Varnish has to redirect to another backend

• Our recommendation: switch to a shared session server using a database (PdoSessionHandler), Memcached (MemcachedSessionHandler) or Redis (ScnRedisBundle)

Page 18: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

URL normalization

• In vcl_hash we calculate a hash to look up the content in the cache. By default it uses the URL + the host (or IP)

• We want to normalize this URL/host in order to avoid having repeated content in the cache

• Convert the host to lowercase using std.tolower• Remove www from the host if present• Normalize all the query parameters using std.querysort• Use RouterUnslashBundle to redirect all URLs to the version not

ending in /• Note that this hash does not include Vary content

sub vcl_hash {

set req.http.host = std.tolower(req.http.host);

set req.http.host = regsub(req.http.host, "^www\.", "");

set req.url = std.querysort(req.url);

}

Page 19: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cookies and sessions

• Varnish by default will not cache anything which has a cookie• Symfony sets a PHPSESSID cookie in almost all responses• By default no content will be cached!• We want to pass the PHPSESSID cookie to the backend but still

cache some pages even if it is set• We must not cache any page where this cookie produces a

different response: logged users, forms (CSRF), flashes• We do not want to cache any page for logged in users• Most cookies are used by the client side and can be ignored• There are some cookies which produce a different response but

it is the same for all users => we can Vary on them• We want to clear all cookies for static content

Page 20: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cookies and sessions

sub vcl_recv {

set req.http.X-cookie = req.http.cookie;

if (!req.http.Cookie ~ "Logged-In") {

unset req.http.Cookie;

}

if (req.url ~ "\.(png|gif|jpg|css|js|html)$") {

unset req.http.cookie;

}

}

sub vcl_hash {

set req.http.cookie = req.http.X-cookie;

if (req.http.cookie ~ "hide_newsletter=") {

set req.http.X-Newsletter = 1;

}

}

sub vcl_pass {

set req.http.cookie = req.http.X-cookie;

}

Page 21: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cookies and sessions

sub vcl_backend_response {

if (!beresp.http.Vary) {

set beresp.http.Vary = "X-Newsletter";

} elseif (beresp.http.Vary !~ "X-Newsletter") {

set beresp.http.Vary = beresp.http.Vary + ", X-

Newsletter";

}

if (bereq.url ~ "\.(png|gif|jpg|css|js|html)$") {

unset beresp.http.set-cookie;

}

}

sub vcl_deliver {

set resp.http.Vary = regsub(resp.http.Vary, "X-

Newsletter", "Cookie");

}

Page 22: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cookies and sessions

• To create the Logged-In cookie we define a kernel.responselistener, injecting the security.context and adding/removing the cookie as needed

Page 23: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cookies and sessions

public function onKernelResponse (FilterResponseEvent $event)

{

$response = $event->getResponse();

$request = $event->getRequest();

if ($this->context->getToken() && $this->context-

>isGranted('IS_AUTHENTICATED_FULLY')) {

if (!$request->cookies->has('Logged-In')) {

$cookie = new Cookie ('Logged-In','true');

$response->headers->setCookie($cookie);

}

} else {

if ($request->cookies->has('Logged-In')) {

$response->headers->clearCookie('Logged-In');

}

}

}

Page 24: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Internacionalization

• If you return different content depending on a header, use the Vary header. A common case is returning different content based on the Accept-Language header

• But you should normalize it or your cache won’t be efficient

if (req.http.Accept-Language) {

if (req.http.Accept-Language ~ "en") {

set req.http.Accept-Language = "en";

} elsif (req.http.Accept-Language ~ "es") {

set req.http.Accept-Language = "es";

} else {

unset req.http.Accept-Language

}

}

• This is a bit simplistic. Use https://github.com/cosimo/varnish-accept-language

• Varnish will automatically take care of Accept-Encoding

Page 25: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Device detection

• Another case may be device detection. We want to normalize the user-agent and Vary on it. We can use https://github.com/varnish/varnish-devicedetect

include "devicedetect.vcl";

sub vcl_recv { call devicedetect; } #sets X-UA-Device header

sub vcl_backend_response {

if (!beresp.http.Vary) {

set beresp.http.Vary = "X-UA-Device";

} elseif (beresp.http.Vary !~ "X-UA-Device") {

set beresp.http.Vary = beresp.http.Vary + ", X-UA-

Device";

}

}

sub vcl_deliver {

set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device",

"User-Agent");

}

Page 26: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Device detection

• We can copy this X-UA-Device header to the user-agent header (but we are losing information)

sub vcl_backend_fetch {

set bereq.http.user-agent = bereq.http.X-UA-Device;

}

• Else we can use the X-UA-Device directly. If, for example, we use LiipThemeBundle, we can configure it:

liip_theme:

autodetect_theme: acme.device.detector

• acme.device.director is a service which implements the Liip\ThemeBundle\Helper\DeviceDetectionInterface

interface and which uses X-UA-Device to choose a theme

Page 27: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Defining caching headers

• Set them directly in the Response object

$response->setSharedMaxAge(600);

$response->setPublic();

$response->setVary('Accept-Language');

• Use SensioFrameworkExtraBundle and the @Cache annotation

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;

/**

* @Cache(smaxage="600")

* @Cache(public=true)

* @Cache(vary={"Accept-Language"})

*/

Page 28: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Defining caching headers

• Use FOSHttpCacheBundle to set them in your config file

fos_http_cache:

cache_control:

rules:

-

match:

attributes: {route: ^book_list$ }

headers:

cache_control: { public: true, s_maxage: 600 }

-

match:

path: ^/info/*$

headers:

cache_control: { public: true, s_maxage: 3600 }

vary: Accept-Language

Page 29: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cache invalidation

• First use case: update pages when you deploy new code• If it is a minor and non-BC breaking change, just wait for the

cache expiration headers to do their job. • You may need to use some cache busting mechanism like the assets_version parameter for cache validation

• If it is a major or BC-breaking change, we just bite the bullet and clear the whole cache by restarting Varnish

service varnish restart

• Downtime is almost inexistent but you will lose all your cached content

• If this is important, you may want to build a cache warmer which preloads all your important urls into the cache

Page 30: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cache invalidation

• Second use case: a more granular approach: invalidate individual pages when the underlying data changes

• We can use FOSHttpCacheBundle. First configure Varnish:

acl invalidators {

"back1.clippingbook.com";

"back2.clippingbook.com";

}

sub vcl_recv {

if (req.method == "PURGE") {

if (!client.ip ~ invalidators) {

return (synth(405, "Not allowed"));

}

return (purge);

}

if (req.http.Cache-Control ~ "no-cache" && client.ip ~

invalidators) {

set req.hash_always_miss = true;

}

}

Page 31: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cache invalidation

• We then need to configure a Varnish server in Symfony:

fos_http_cache:

proxy_client:

varnish:

servers: xxx.xxx.xxx.xxx #IP of Varnish server

base_url: clippingbook.com

• We can now invalidate or refresh content programatically

$cacheManager = $container ->

get('fos_http_cache.cache_manager');

$cacheManager->invalidatePath('/books');

$cacheManager->refreshRoute('book_show', array('id' =>

$bookId));

$cacheManager->flush(); //optional

Page 32: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cache invalidation

• We can also use annotations:

use FOS\HttpCacheBundle\Configuration\InvalidatePath;

/**

* @InvalidatePath("/books")

* @InvalidateRoute("book_show", params={"id" =

{"expression"="id"}})")

*/

public function editBookAction($id)

{

}

• This needs that SensioFrameworkExtraBundle is available and, if we use expressions, that the ExpressionLanguage component is installed

Page 33: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cache invalidation

• Finally, we can set up invalidation in our config file:

fos_http_cache:

invalidation:

rules:

-

match:

attributes:

_route: "book_edit|book_delete"

routes:

book_list: ~

book_show: ~

Page 34: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cache validation

• Varnish 4 now supports cache validation

• You should be setting the Etag and/or Last-Modified headers, which now Varnish understands and supports

• Expiration wins over validation so while the cache is not stale Varnish will not poll your backend to validate it

• But once the content expires it will call the backend with the If-None-Match and/or If-Modified-Since headers

• You can use these to determine if you want to send back a304: Not Modified response

• If you do, Varnish will continue serving the content from the cache

Page 35: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Cache validation

public function showBookAction($id, $request)

{

$book = ...;

$response = new Response();

$response->setETag($book->computeETag());

$response->setLastModified($book->getModified());

$response->setPublic();

if ($response->isNotModified($request)) {

return $response; //returns 304

}

... generate and return full response

}

Page 36: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Edge Side Includes (ESI)

• ESI allows you to have different parts of the page which have different caching strategies. Varnish will put the page together

• To work with Symfony you have to instruct Varnish to send a special header advertising this capability and to respond to the header sent back by Symfony when there is ESI content

sub vcl_recv {

set req.http.Surrogate-Capability = "abc=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;

}

}

Page 37: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Edge Side Includes (ESI)

• Now you need to tell Symfony to enable ESI• If you are going to reference a controller when including ESI

content you need to enable the FragmentListener so that it generates URLs for the ESI fragments

• Finally you need to list the Varnish servers as trusted proxies

framework:

esi: { enabled: true }

fragments: { path: /_fragment }

trusted_proxies: [xxx.xxx.xxx.xxx, yyy.yyy.yyy.yyy ]

#IPs of Varnish servers

Page 38: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Edge Side Includes (ESI)

• In the main controller for the page, set the shared max age

public function indexAction()

{

... generate response

$response->setSharedMaxAge(600);

return $response;

}

• In your template use the render_esi helper to print ESI content

{{ render_esi(controller('...:news', { ’num': 5 })) }}

{{ render_esi(url('latest_news', { ’num': 5 })) }}

• You can now specify a different cache policy for your fragment

public function newsAction()

{

... generate response

$response->setSharedMaxAge(60);

return $response;

}

Page 39: Making Symofny shine with Varnish - SymfonyCon Madrid 2014

Making Symfony shine with Varnish

Thanks!

¡Gracias! - Thanks!

Any questions?

[email protected]@carlos_granados

https://joind.in/talk/view/12942