Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011

12,794 views
12,511 views

Published on

In the first part of the presentation we see how Symfony2 implements HTTP cache.

In the second one there's an explanation of why application cache layers suck, why nerly every php application does caching in the less productive way and how you benefit from HTTP cache from this point of view.

Published in: Technology

Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011

  1. 1. HTTP caching and Symfony2Be lazy, be ESI Alessandro Nadalin May, 13 2011
  2. 2. Symfony2 sucks at caching
  3. 3. because it doesnt have an application caching layer
  4. 4. high five, thats great
  5. 5. Sf2 embraces the HTTP caching specification and thats not because
  6. 6. Fabien is lazy
  7. 7. thats because
  8. 8. Fabien kicks asses
  9. 9. HTTP cache is a King in Symfony2
  10. 10. Caching in Symfony2
  11. 11. Caching is, obviously, a matter of the response
  12. 12. since the response is an object, we have some basic OO stuff to handle HTTP cache
  13. 13. Creating a responsepublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); return $res;}
  14. 14. Creating a responsepublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); return $res;}
  15. 15. Creating a responsepublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); return $res;}
  16. 16. Creating a responsepublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); return $res;}
  17. 17. Creating a responsepublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); return $res;}
  18. 18. Creating a responsepublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); return $res;}
  19. 19. Expiration
  20. 20. Expirationpublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); $date = new DateTime(); $date->modify(+600 seconds); $res->setExpires($date); return $res;}
  21. 21. Expirationpublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); $date = new DateTime(); $date->modify(+600 seconds); $res->setExpires($date); return $res;}
  22. 22. Expirationpublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); $date = new DateTime(); $date->modify(+600 seconds); $res->setExpires($date); return $res;}
  23. 23. Expiration through Cache-Controlpublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); $res->setMaxAge(600); $res->setSharedMaxAge(600); return $res;}
  24. 24. Expiration through Cache-Controlpublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); $res->setMaxAge(600); $res->setSharedMaxAge(600); return $res;}
  25. 25. Expiration through Cache-Controlpublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); $res->setMaxAge(600); $res->setSharedMaxAge(600); return $res;}
  26. 26. Expiration through Cache-Controlpublic function indexAction(){ $res = $this->render(MyBundle:Default:home.html.twig, array( title => My blog, )); $res->setMaxAge(600); $res->setSharedMaxAge(600); return $res;} Shared caches, like Symfony2 reverse proxy
  27. 27. Validation
  28. 28. Validationpublic function articleAction($id){ $article = $this->get(entity_manager)->query($id)... $res = $this->render(MyBundle:Default:article.html.twig, array( article => $article, )); $etag = "article:{$article->getId()}:{$article->getVersion()}"; $res->setETag($etag); $res->setLastModified($article->getModifiedAt()); return $res;
  29. 29. Validationpublic function articleAction($id){ $article = $this->get(entity_manager)->query($id)... $res = $this->render(MyBundle:Default:article.html.twig, array( article => $article, )); $etag = "article:{$article->getId()}:{$article->getVersion()}"; $res->setETag($etag); $res->setLastModified($article->getModifiedAt()); return $res;
  30. 30. Validationpublic function articleAction($id){ $article = $this->get(entity_manager)->query($id)... $res = $this->render(MyBundle:Default:article.html.twig, array( article => $article, )); $etag = "article:{$article->getId()}:{$article->getVersion()}"; $res->setETag($etag); $res->setLastModified($article->getModifiedAt()); return $res;
  31. 31. Validationpublic function articleAction($id){ $article = $this->get(entity_manager)->query($id)... $res = $this->render(MyBundle:Default:article.html.twig, array( article => $article, )); $etag = "article:{$article->getId()}:{$article->getVersion()}"; $res->setETag($etag); $res->setLastModified($article->getModifiedAt()); return $res;
  32. 32. Validationpublic function articleAction($id){ $article = $this->get(entity_manager)->query($id)... $res = $this->render(MyBundle:Default:article.html.twig, array( article => $article, )); $etag = "article:{$article->getId()}:{$article->getVersion()}"; Datetime object $res->setETag($etag); $res->setLastModified($article->getModifiedAt()); return $res;
  33. 33. Automagically get a Datetime from Doctrine2namespace AcmeBlogBundleEntity;/** * @orm:Entity * @orm:Table(name="article") */class Article{ /** * @orm:Column(type="datetime", name="modified_at") */ protected $modifiedAt;
  34. 34. Automagically get a Datetime from Doctrine2namespace AcmeBlogBundleEntity;/** * @orm:Entity * @orm:Table(name="article") */class Article{ /** * @orm:Column(type="datetime", name="modified_at") */ protected $modifiedAt;
  35. 35. but hey, you say
  36. 36. whats the advatage of using validation if we always hit the DB and create a new Response?
  37. 37. Validation, the right waypublic function articleAction($id){ $res = new Response() $etag = Memcache::get("Article:{$id}:etag"); $lastModified = Memcache::get("Article:{$id}:last_modified"); $res->setETag($etag); $res->setLastModified($lastModified); if ($res->isNotModified($this->get(request))) { return $res; } $article = $this->get(entity_manager)->query($id)... ...
  38. 38. Validation, the right waypublic function articleAction($id){ $res = new Response() $etag = Memcache::get("Article:{$id}:etag"); $lastModified = Memcache::get("Article:{$id}:last_modified"); $res->setETag($etag); $res->setLastModified($lastModified); if ($res->isNotModified($this->get(request))) { return $res; } $article = $this->get(entity_manager)->query($id)... ...
  39. 39. Validation, the right waypublic function articleAction($id){ $res = new Response() $etag = Memcache::get("Article:{$id}:etag"); $lastModified = Memcache::get("Article:{$id}:last_modified"); $res->setETag($etag); $res->setLastModified($lastModified); if ($res->isNotModified($this->get(request))) { return $res; } $article = $this->get(entity_manager)->query($id)... ...
  40. 40. Validation, the right waypublic function articleAction($id){ $res = new Response() $etag = Memcache::get("Article:{$id}:etag"); $lastModified = Memcache::get("Article:{$id}:last_modified"); $res->setETag($etag); $res->setLastModified($lastModified); if ($res->isNotModified($this->get(request))) { return $res; } $article = $this->get(entity_manager)->query($id)... ...
  41. 41. relax
  42. 42. Calculating an Etag, or a date, is cheaper than generating a full MVC response
  43. 43. Control your power
  44. 44. Additional managementpublic function articleAction($id){ $res = new Response() $res->setVary(array( Accept-Encoding, )); $res->setPublic(); $res->setNotModified(); ...
  45. 45. Varying the responsepublic function articleAction($id){ $res = new Response() $res->setVary(array( Accept-Encoding, )); $res->setPublic(); $res->setNotModified(); ...
  46. 46. Cacheable by all cachespublic function articleAction($id){ $res = new Response() $res->setVary(array( Accept-Encoding, )); $res->setPublic(); $res->setNotModified(); ...
  47. 47. or by local onlypublic function articleAction($id){ $res = new Response() $res->setVary(array( Accept-Encoding, )); $res->setPrivate(); $res->setNotModified(); ...
  48. 48. Good old 304public function articleAction($id){ $res = new Response() $res->setVary(array( Accept-Encoding, )); $res->setPrivate(); $res->setNotModified(); ...
  49. 49. Stalepublic function articleAction($id){ $res = new Response() $res->setVary(array( Accept-Encoding, )); $res->setPrivate(); $res->expire(); ...
  50. 50. but hey, you say
  51. 51. HTTPs cache fails when dealing with really dynamic pages,because consumers will always have to hit the origin server,although a part of the page would be cacheable ( header and footer, for example )
  52. 52. no bueno, you say
  53. 53. NOPE
  54. 54. ESI was built for that http://www.w3.org/TR/esi-lang<esi:include src="http://mysite.com/twitterfeeds.html" />
  55. 55. ESI and Symfony2# controller$response->setSharedMaxAge(3600);# app/config/config.ymlframework: esi: { enabled: true }# template{% render ...:twitterFeeds with {}, {standalone: true} %}# fragment controller$response->setSharedMaxAge(15);
  56. 56. ESI and Symfony2# controller$response->setSharedMaxAge(3600);# app/config/config.ymlframework: esi: { enabled: true }# template{% render ...:twitterFeeds with {}, {standalone: true} %}# fragment controller$response->setSharedMaxAge(15);
  57. 57. ESI and Symfony2# controller$response->setSharedMaxAge(3600);# app/config/config.ymlframework: esi: { enabled: true }# template{% render ...:twitterFeeds with {}, {standalone: true} %}# fragment controller$response->setSharedMaxAge(15);
  58. 58. ESI and Symfony2# controller$response->setSharedMaxAge(3600);# app/config/config.ymlframework: esi: { enabled: true }# template{% render ...:twitterFeeds with {}, {standalone: true} %}# fragment controller$response->setSharedMaxAge(15);
  59. 59. ESI and Symfony2# controller$response->setSharedMaxAge(3600);# app/config/config.ymlframework: esi: { enabled: true }# template{% render ...:twitterFeeds with {}, {standalone: true} %}# fragment controller$response->setSharedMaxAge(15);
  60. 60. Use Symfony2 reverse proxy
  61. 61. Use Symfony2 reverse proxy (slower)
  62. 62. or Varnishhttp://www.varnish-cache.org/
  63. 63. Varnish configurationbackend apache { .host = "127.0.0.1"; .port = "8080";}sub vcl_recv { unset req.http.Accept-Encoding; unset req.http.Vary; set req.http.Surrogate-Capability = "abc=ESI/1.0";}sub vcl_fetch { esi; ...
  64. 64. Varnish configurationbackend apache { .host = "127.0.0.1"; tell Symfony2 .port = "8080"; youre behind a reverse proxy} able to handle the ESI specificationsub vcl_recv { unset req.http.Accept-Encoding; unset req.http.Vary; set req.http.Surrogate-Capability = "abc=ESI/1.0";}sub vcl_fetch { esi; ...}
  65. 65. Why HTTP caching is so important?
  66. 66. Ask yourself:as a developer, what do I want on my application?
  67. 67. EvolveLoose coupling Work less
  68. 68. EvolveBecause you want your platform to extensible Loose coupling Work less
  69. 69. EvolveBecause you want your platform to extensible Loose couplingBecause you want it to be easy to integrate with, evolve, plugand mantain Work less
  70. 70. EvolveBecause you want your platform to extensible Loose couplingBecause you want it to be easy to integrate with, evolve, plugand mantain Work lessBecause every LoC is bug-prone and our man-day is a hard-to-scale cost
  71. 71. enters our Hero #1
  72. 72. enters our Hero #2
  73. 73. http://www.lullabot.com/articles/a-beginners-guide-to-caching-data
  74. 74. 2007
  75. 75. 2011?
  76. 76. it supportsHTTP caching! http://drupal.org/node/147310
  77. 77. it supportsHTTP caching! http://drupal.org/node/147310 ( people is supposed to clap their hands here )
  78. 78. but
  79. 79. wait.... how?
  80. 80. Default headersExpires = Sun, 19 Nov 1978 05:00:00 GMT,Cache-Control = no-cache, must-revalidate,ETag = $_SERVER[REQUEST_TIME],
  81. 81. Default headersExpires = Sun, 19 Nov 1978 05:00:00 GMT,Cache-Control = no-cache, must-revalidate,ETag = $_SERVER[REQUEST_TIME],
  82. 82. Default headersExpires = Sun, 19 Nov 1978 05:00:00 GMT,Cache-Control = no-cache, must-revalidate,ETag = $_SERVER[REQUEST_TIME],
  83. 83. Default headersExpires = Sun, 19 Nov 1978 05:00:00 GMT,Cache-Control = no-cache, must-revalidate,ETag = $_SERVER[REQUEST_TIME],
  84. 84. is that even legal?
  85. 85. "but you can redefine them!"
  86. 86. drupal_add_http_header()
  87. 87. function drupal_add_http_header(){ ... ... drupal_send_headers($headers);}
  88. 88. so, whatdrupal_send_headers() can do so evil?
  89. 89. header() , of course
  90. 90. which means
  91. 91. drupal_add_http_header(Dumb-Header, Im batman!);...// other logic...drupal_add_http_header(Dumb-Header, Im not);var_dump(headers_list());
  92. 92. drupal_add_http_header(Dumb-Header, Im batman!);...// other logic...drupal_add_http_header(Dumb-Header, Im not);var_dump(headers_list());array 0 => string X-Powered-By: PHP/5.3.2-1ubuntu4.7 1 => string Cache: Im not
  93. 93. drupal_add_http_header(Dumb-Header, I-m batman!);...// other logic...drupal_add_http_header(Dumb-Header, false);var_dump(headers_list());
  94. 94. drupal_add_http_header(Dumb-Header, I-m batman!);...// other logic...drupal_add_http_header(Dumb-Header, false);var_dump(headers_list());array 0 => string X-Powered-By: PHP/5.3.2-1ubuntu4.7 1 => string Cache: Im batman
  95. 95. drupal_add_http_header(Dumb-Header, I-m batman!);...// other logic...drupal_add_http_header(Dumb-Header, );var_dump(headers_list());
  96. 96. drupal_add_http_header(Dumb-Header, I-m batman!);...// other logic...drupal_add_http_header(Dumb-Header, );var_dump(headers_list());array 0 => string X-Powered-By: PHP/5.3.2-1ubuntu4.7 1 => string Cache:
  97. 97. or
  98. 98. you can use header_remove() ( PHP 5.3 )
  99. 99. and create a new class to manage/keep track ofheaders and caching directives
  100. 100. but were lazy andwe dont want to reinvent the wheel
  101. 101. GoalsWork less evolve loose coupling
  102. 102. GoalsWork less evolve loose coupling
  103. 103. everything is done for us! :) but....
  104. 104. tmp files, cache tables, procedural code... mmmmh.... gotta be something better
  105. 105. Frameworks
  106. 106. symfony ONE
  107. 107. Cache is used for compiling routes, autoloading, ...
  108. 108. Cache is used for compiling routes, autoloading, ... ...but also for storing the view
  109. 109. GoalsWork less evolve loose coupling
  110. 110. GoalsWork less evolve loose coupling
  111. 111. at least because we use a framework
  112. 112. HTTP
  113. 113. GoalsWork less evolve loose coupling
  114. 114. Less work
  115. 115. because the hard work is delegated to the browser/proxy
  116. 116. Evolve
  117. 117. because cache is abstracted from the application
  118. 118. Loose coupling
  119. 119. because caching is bound to the protocol, HTTP, notto your implementation ( Sf, RoR, Django )
  120. 120. Alessandro Nadalin
  121. 121. Alessandro Nadalin
  122. 122. Alessandro Nadalin odino.org
  123. 123. Alessandro Nadalin odino.org @_odino_
  124. 124. Alessandro Nadalin odino.org @_odino_ http://joind.in/talk/view/2988
  125. 125. Alessandro Nadalin odino.org @_odino_ http://joind.in/talk/view/2988 Thanks!
  126. 126. Creditshttp://www.flickr.com/photos/snakphotography/5004775320/sizes/o/in/photostream/ http://www.flickr.com/photos/ashatenbroeke/4367373081/sizes/z/in/photostream/ http://www.flickr.com/photos/juanpg/3333385784/sizes/z/in/photostream/ http://www.flickr.com/photos/congvo/301678287/sizes/l/in/photostream/ http://www.flickr.com/photos/adamrice/280300202/sizes/l/in/photostream/ http://www.flickr.com/photos/tomer_a/541411897/sizes/o/in/photostream/ http://www.flickr.com/photos/subpra/4514008262/sizes/l/in/photostream/ http://www.flickr.com/photos/lippincott/2539720043/sizes/l/in/photostream/ http://www.flickr.com/photos/rawryder/5086090931/sizes/l/in/photostream/ http://www.flickr.com/photos/robboudon/5312731161/sizes/l/in/photostream/ http://www.flickr.com/photos/bc-burnslibrary/4158243488/sizes/o/in/photostream/http://www.flickr.com/photos/13606325@N08/2416993706/sizes/o/in/photostream/ http://www.flickr.com/photos/neothezion/5135841069/sizes/l/in/photostream/ http://www.flickr.com/photos/planetschwa/2494067809/ http://www.flickr.com/photos/thomasthomas/258931782/ http://www.flickr.com/photos/jungle_boy/220181177/sizes/l/in/photostream/ http://www.flickr.com/photos/ramduk/3708039504/sizes/z/in/photostream/ http://www.flickr.com/photos/sashawolff/3228711025/sizes/l/in/photostream/

×