Nice performance using Sf2 cache wrapping Sf1 application


Published on

In collaboration with Emmanuel Cohen.

At a key moment for online press in France, a major French news company chooses PHP and Symfony to extend its popular web site. We will present the architecture we designed at Sensio Labs to meet a very good performance requirement. We used Symfony2 kernel wrapping symfony 1.4 and relied on loose-coupled applications serving content from heterogeneous backend sources.

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • I’m a project manager at Sensio Labs. I worked more than 5 years with Java, as a developper and architect. Then I switched to PHP 4 years ago.
  • I’m a web developer at sensio labs and I’m the lead developer on the whole project. I started programming with Amos Basic on Amiga 500 and then discovered C and PHP. You can find me on twitter, but don’t bother following me as I mostly tweet about stupid stuff and pandas.
  • This talk is about Online media, Caching and architecture, Edge Side Includes, symfony 1 and of course Symfony 2. Marc and I would like to talk to you about a project that we did for a client last year, back in Paris, with the precious help of Fabien. At that time, Fabien had already implemented, very  conveniently indeed, the concepts of Edge Side Include and HTTP cache in Symfony2. Which fitted exactly our client need, who was looking for a way to improve its cache handling for the next version of its web site. It is also a use case of the talk Fabien gave earlier about HTTP Cache. I will present our client, the project and the architecture we designed for him. Marc will explain the implementation.
  • Our client’s name is L’Express Magazine. It’s a general-interest magazine in France. It was created in 1953 . Due to its long and rich history, it is quite a famous media,both offline and online, in France. Today its online version, is the 3 rd online news web site, which makes it reasonably in need for performance.
  • This is from the existing web site, It’s a popular site. Talks about hot topics…
  • … Hot people (French cancan)
  • unknown people
  • Our project is an extension of the  existing  web site. It is a knowledge base of the cultural  scene  in France a way for the visitor to browse hot news and link it to cultural knowledge
  • And this is what it looks like Here is a page about a movie that is about a web site that you may have heard of… this is a typical page of our site displaying a pile of raw information about directors, publishers, news, critics, movie ratings… The user can browse all kinds of information linked to the movie, and relate it to articles of
  • From the very beginning of the project, our objectives are : * Performance We want to deliver as fast as possible while keeping their  cost of hosting  low. For that, we will use a little dose of Symfony2. * Our client has a vision for the future of the site : So they want our application to serve as a full-scale proof of concept  that they will use as a basis to build the next version of their web site. We have multiple entry points, that we do not entirely control, we have a need for performance But we need to keep it simple (and loosely-coupled) (we, at Sensio, are required to say simple as often as we can)
  • We need to take a few things into account 1. Various sources delivering XML content And we need to aggregate those sources 2. Today Symfony 2 can natively support the whole architecture. But we started the project in June last year. At that time Symfony 2 was not stable yet. So we decided to implement it in Sf1 while taking advantage of some of Sf2 features. 3. We need to stay as generic as possible, so as to be able to support future developments of our client
  • At the bottom, you have multiple sources of information, including a content-management system that was developped by the team at L'express. At L’Express, they have a technical team that works on web R&D and innovation. Ocari is the in-house content manager system. It is based on Symfony 1 and generates xml content. Last year at the Paris Symfony Live L'Express came to present us the way Ocari, their content manager is working, based on XML content management and schedulers that handle what should be displayed and when. They also have the project to port it on Symfony2 and to make it open source. 2. Ocari Culture is a backoffice, which journalists use to build their knowledge base. Both of these applications provide content in XML formats. And the contents can be linked : they can say for instance that the movie ‘The social network’ described in Ocari Culture is linked to this or this article from Ocari Content. --------------------- Then we need a layer that will aggregate contents. --------------------- The XML Server gathers all XML content,. * stores them, so the backoffices are not reached at runtime * Normalizes content using XSL stylesheets, since we got contents from different places * aggregates XML, if one content references another * serves them to the HTML renderer --------------------------- Each of the content is being used to render a fragment of a page That's HTML Renderer's job. Basically what HTML Renderer does is to request a content from the XML Server with its URI, and then render it with XSLT. Delivers HTML in fragments, to best benefit from cache and ESI mechanisms. We will see that later. ----------------------------- We have actually  four distinct applications in our architecture . ----------------------------- This way any data source can be smoothly integrated into a front that is dedicated to high performance page delivery.
  • What have we got ? 4 different apps that do very specialized things They are loosely coupled and we have a front layer that has its own logic and can be entirely independant of the admin
  • although we don't hit the admin at runtime, the whole fetching xml contents and the xslt processing is still heavy... So we need to address the performance issue, it is one of our goals we use two components from Symfony 2 to do that : One is the new cache component of Symfony 2 It is based on the HTTP RFC and that’s a good news the other is a component that deals with ESI ESI an Akamai mechanism that was made a RFC and is used to deliver fragments of pages instead of the whole page at once Those mechanisms are not brand new (HTTP RFC is 1998 and ESI RFC is from 2001).
  • The cache in Sf2 is based on HTTP cache. I invite you to visit SF2 handles HTTP headers of the request and response to assess the freshness of the cache In our case, as we only need expiration, we use the Cache-Control: s-maxage header.
  • So you’re using a standard (and an old and famous one, at that) and I will not come back to the good of using standards. And the good thing is Symfony cache loads the application only if needed. With sf1, the application is started first so the cache can be handled. --- With Sf2, only the HttpKernel is hit and can deliver the response according to its HTTP header, without the application being lit. if you response is in cache, the request will not hit your application. I will stop here with HTTP cache because Fabien will talk about it at length tomorrow.
  • But that’s not all. In fact we undergo two more inconvenients with cache : We are supposed to know what each page is composed of, in order to invalidate the right way, which, you probably know, is a hard thing to do. So for each fragment of the page that is invalid, you have to know which page it belongs to in order to invalidate the whole thing The second thing is that if you have elements that stay fresh longer, you do not want to regenerate them It's a pity that the layout should be re-delivered when it's still valid.
  • Then comes ESI in Symfony2. Edge Side Includes (ESI) is a simple markup language used to define Web page components for dynamic assembly. ESI is a standard, that was proposed by Akamai in the first place, in 2001. So you see it’s not really the latest news, but it’s extremely efficient.
  • Here is a typical page rendered by our architecture You can easily cut down the page into smaller fragments ( a box of information = component ) that may have different times to live, or expiration dates, or validation process. You can use ESI to decompose your whole page into those fragments, and you handle each fragment as if it was a standalone HTTP response. One include is in charge of rendering one fragment in the page. So what we use here is : HTML static Layouts in which we place esi includes Each include will be solved by the ESI component of Symfony 2 You can then manage different time to live in the same page .
  • Each fragment has its own ttl or validation rule => the fragments are selectively refreshed You do not have to worry about dependances. They are defined once and for all.
  • So How Esi Works ?
  • The client can be anything. The ESIs are processed on the client side You will need a proxy that handle ESI. Varnish handles it, Symfony2 can too.
  • So let’s see what happen when the client issues the first request ever on a web page ? Let’s say we want some informations about the Dogma movie. The client issues a request. The proxy get it and check if it has something in cache. As it is the first request, there is nothing in cache and the request is forwarded to the application. The app sends the response, composed of a HTML layout with esi tags inside. The response is also sent with a Cache-Control header setting the time to live of this response of 600 seconds. The proxy analyse the responds body and for each ESI tags found, checks the cache if there is a cached version of the response. As this is the first request, none of these ESI tags are stored in the cache and for each one, a request has to be issued to the application For each request, the application answer with an HTML response and optionnally Cache-Control header. In this example, the TTL is set differently for each modules of the page.
  • Massive performance gain !
  • Granularity: You can use expiration and/or validation for different parts of your website.
  • How to benefit from Sf 2 capabilities, in terms of cacheing ? Imagine you have your sf1 application ready, and then,  blam ! , sf2 comes in. You think about rebuild the whole thing, but you're lazy, like me, and your client has given you 3 days to finish the  whole thing.  Well. You wrap it. In our case, we only need to wrap our HTML Renderer application. Most of the time, it will not even be loaded. It’s the Symfony2 Kernel that handles client requests and solves ESI includes. In order to do that : * we need to tweak the Symfony2 controller. * You disable sf1 cache * you prevent it from rendering its response to the client * allow for multiple reentrance * and you use HttpKernel from Sf2 up front to handle it.
  • Symfony 1 is based on a filter chain execution. At the end, the sfRenderingFilter class sends the response directly to the client.
  • Symfony2 is divided into serveral components you can exploit individually
  • Symfony 1 is not able to give the response as a string, so we have to catch it with output buffering Finally, we build a Symfony2 response object and we specify that this content has to be parsed with ESI. Préciser que ça vient d’HttpKernel This method requires the ProjectConfiguration class of symfony 1.x We instanciate a sfApplicationConfiguration object with the given parameters from the front controller
  • In symfony 1, the strategy was to divide your page into partials. This is not longer valid when you want to leverage ESI. You have to transform your partial into standalone actions that will responds HTML
  • And now we’re happy
  • Or are we ? Because there’s still a long way to go
  • Nice performance using Sf2 cache wrapping Sf1 application

    1. 1. Nice performance using SF2 cache wrapping sf1 application Marc Weistroff Emmanuel Cohen Sf Live SF 2011 Tuesday, February 8 th 2011
    2. 2. Who we are <ul><li>Emmanuel Cohen </li></ul><ul><li>Project manager at Sensio Labs </li></ul><ul><li>4 year experience with PHP5 </li></ul><ul><li>5+ year experience with Java EE </li></ul><ul><li>Learned Basic on Amstrad CPC 6128 </li></ul>
    3. 3. Who we are <ul><li>Marc Weistroff </li></ul><ul><li>Developer at Sensio Labs since 11/2009 </li></ul><ul><li>Lead developer on this project </li></ul><ul><li>Experiences with C, and PHP from version 3 </li></ul><ul><li>Encountered programming with Amos Basic on Amiga 500 </li></ul>
    4. 4. This talk <ul><li>Symfony2 </li></ul><ul><li>symfony 1 </li></ul><ul><li>HTTP Cache </li></ul><ul><li>Edge Side Includes (ESI) </li></ul><ul><li>Web architecture </li></ul><ul><li>Online media </li></ul>
    5. 5. Our client <ul><li>L’Express Magazine (Express-Roularta Group) </li></ul><ul><li>Magazine created in 1953 </li></ul><ul><li> </li></ul><ul><li>Top 3 French online news </li></ul>
    6. 9. The project <ul><li>A cultural knowledge base </li></ul><ul><li>A bridge between hot news and cultural knowledge </li></ul><ul><li>An extension of </li></ul>
    7. 11. Technical objectives <ul><li>Performance </li></ul><ul><li>A full scale Proof-of-Concept for the future </li></ul><ul><li>Keep it Simple </li></ul>
    8. 12. Constraints <ul><li>Heterogeneous XML Sources </li></ul><ul><li>Solution based on sf1 </li></ul><ul><li>Adaptability (for future extensions) </li></ul>
    9. 13. How does this app work ?
    10. 14. The application : System design HTML Renderer XML Server OCARI Culture OCARI Content <ul><li>Stores </li></ul><ul><li>Normalizes </li></ul><ul><li>Aggregates </li></ul><ul><li>Serves XML </li></ul>Sources deliver heterogeneous data <ul><li>Renders HTML with XSL </li></ul><ul><li>Renders static layouts </li></ul>
    11. 15. So <ul><li>Highly specialized applications </li></ul><ul><li>Loose coupling </li></ul><ul><li>Front dedicated to delivering content fast </li></ul>
    12. 16. Addressing performance
    13. 17. Addressing performance <ul><li>With Symfony2 built-in Gateway : </li></ul><ul><li>HTTP cache </li></ul><ul><li>ESI </li></ul>
    14. 18. HTTP Cache <ul><li>HTTP RFC </li></ul><ul><li>Symfony2 uses HTTP cache headers to handle cache (Cache-Control : s-maxage, age, Last-Modified, …) </li></ul><ul><li> </li></ul>
    15. 19. HTTP Cache <ul><li>symfony 1 </li></ul><ul><li>Application cache </li></ul><ul><li>Symfony2 </li></ul><ul><li>light-weight HttpKernel supports cache </li></ul><ul><li>uses a standard </li></ul>
    16. 20. Cache Issues <ul><li>Find out what’s stale </li></ul><ul><li>Selective expiration (or validation) </li></ul>
    17. 21. Performance > Cache > ESI <ul><li>A markup language </li></ul><ul><li>W3C note from Akamai </li></ul><ul><li>http:// </li></ul><ul><li>http:// </li></ul>
    18. 22. ESI : A page in
    19. 23. ESI : Edge Side Includes <ul><li>Each fragment has its own ttl or validation rule => the fragments are selectively refreshed </li></ul><ul><li>Dependance is defined once and for all </li></ul>
    20. 24. How does ESI work?
    21. 25. What is an ESI? <ul><li>Represented by an HTML Tag </li></ul><ul><ul><li><esi:include src=“/movie/dogma/critics” /> </li></ul></ul><ul><li>Processed by a proxy </li></ul><ul><li>Transformed into HTTP Request to the application </li></ul><ul><li>Response is inserted in place of the esi tag </li></ul><ul><li>HTTP cache is handled independently for each esi tag </li></ul>
    22. 26. /movie/dogma
    23. 27. /movie/dogma
    24. 28. Html Fragment <ul><li>Pure HTML </li></ul><ul><li>Inserted in lieu of the <esi /> tag </li></ul><ul><li>No <html>, <body> or <head> tag in this case </li></ul>
    25. 29. What you need <ul><li>Any client! </li></ul><ul><li>A proxy that handles ESI (ie: Varnish or Symfony2) </li></ul><ul><li>An application that delivers HTML and HTTP cache headers </li></ul><ul><li>Serve HTML pages that contain ESI tags </li></ul>
    26. 30. First request ever Client Proxy Application /movie/dogma /movie/dogma miss Cache-Control: s-maxage=600 /movie/dogma/casting /movie/dogma/critics Cache-Control: s-maxage=3600 Cache-Control: s-maxage=300
    27. 31. Request at t+200 Client Proxy Application /movie/dogma hit
    28. 32. The application is never hit! As if the complete page was cached in the reverse proxy
    29. 33. Request at t+500 Client Proxy Application /movie/dogma /movie/dogma/critics Cache-Control: s-maxage=300 hit
    30. 34. The application is partially hit And has to build only a small fragment of the page
    31. 35. Pros <ul><li>You use HTTP cache, not the application one. </li></ul><ul><li>Performance improvements! </li></ul><ul><li>Granularity: Different cache strategy or TTL on different parts of your page </li></ul>
    32. 36. Cons <ul><li>Your app will be hit n times upon the very first request </li></ul><ul><li>Your app have to be designed to support the rendering of fragments </li></ul>
    33. 37. But our app is symfony 1 right ?
    34. 38. <ul><li>What to do with your symfony 1 application when you are dying to use Symfony2? </li></ul>Wrap it. Our app is symfony 1
    35. 39. Wrapping symfony 1 with Symfony2
    36. 40. Constraint <ul><li>We use the Symfony2 ESI/Cache proxy </li></ul><ul><li>All the call to our app is done in the same PHP process </li></ul>
    37. 41. symfony 1 needs tweaking in order to work around a few obstacles
    38. 42. Which ones ? <ul><li>It is not reentrant </li></ul><ul><ul><li>sfConfig singleton </li></ul></ul><ul><ul><li>sfContext singleton </li></ul></ul><ul><li>It sends the response directly to the client </li></ul><ul><ul><li>Filter chain execution </li></ul></ul><ul><ul><li>At the end, sfRenderingFilter sends the complete response </li></ul></ul>
    39. 43. What about Symfony2? <ul><li>Better architecture: Symfony2 is reentrant </li></ul><ul><li>Symfony2 is heavily based on interfaces </li></ul><ul><li>Symfony2 is divided into several components </li></ul><ul><li>HttpKernelHttpKernelInterface </li></ul>
    40. 44. HttpKernelHttpKernelInterface <ul><li>Lightweight (1 method) </li></ul><ul><li>Forces to implement a “handle” method that accepts a Symfony2 Request object and returns a Symfony2 Response object </li></ul><ul><li>Used by all the classes that act as a Kernel in Symfony2 (ie: HttpKernelHttpCache) </li></ul>
    41. 45. Addressing the issues <ul><li>Reentrance </li></ul><ul><ul><li>Override the super globals </li></ul></ul><ul><ul><li>Create a fresh new context each time </li></ul></ul><ul><li>Filter chain </li></ul><ul><ul><li>Replace sfRenderingFilter by a noRenderingFilter class </li></ul></ul><ul><li>Create a Symfony2 Response object and returns it </li></ul>
    42. 46. Final app architecture EsiCacheKernel SymfonyWrapperKernel symfony 1 application
    43. 47.
    44. 48. Then… You have to design your app to serve HTML fragments!
    45. 49. Designing your app <ul><li>Don’t think partials or components, think actions! </li></ul><ul><li>Actions render HTML fragments </li></ul><ul><li>Actions MUST define explicitly the response cache headers. </li></ul>
    46. 50. And now where happy
    47. 51. What to do next ?
    48. 52. Proxy ESI New response Or 304 URL-> {ETag} ETag-> lastmodified If Etag absent or stale Response with HTTP headers Etag LastModified Client Request with HTTP headers If-None-Match If-Modified-Since App New response Or 304 If cache entry is not fresh enough Cache Validation Optimizer
    49. 53. Contact @ L’Express <ul><li>Sébastien Angèle </li></ul><ul><li>[email_address] </li></ul><ul><li>Jérôme Macias </li></ul><ul><li>[email_address] </li></ul>
    50. 54. Questions ? <ul><li>Marc Weistroff </li></ul><ul><li>@futurecat </li></ul><ul><li>[email_address] </li></ul><ul><li> </li></ul><ul><li>Emmanuel Cohen </li></ul><ul><li>@emmanuelcohen </li></ul><ul><li>[email_address] </li></ul>
    51. 55. Thank you! Please rate this talk at