Friday, September 2, 2016

Introduction to Symfony HTTP Cache

You can start using the HTTP Gateway Cache that is shipped with Symfony by editing app.php file:

    $kernel = new AppKernel('prod', true);
    $kernel->loadClassCache();
    // wrap the default AppKernel with the AppCache one
    $kernel = new AppCache($kernel);

I've put the debug mode to be able to see the X-Symfony-Cache header in Firebug. You may want to delete the cache for prod environment before  refreshing the page. When loading the page, you can see in Firebug the Response headers:

    Cache-Control       no-cache
    X-Symfony-Cache   GET /: miss

So we do not have any Cache control directives set and our reverse proxy says it cannot find the path "/" in the list of cached information.

Let's make the response cacheable using Cache Expiration strategy.

In controller:

    $response =  $this->render('default/index.html.twig');
    $response->setSharedMaxAge(30);

    // (optional) set a custom Cache-Control directive
    $response->headers->addCacheControlDirective('must-revalidate', true);
    return $response;

Delete the cache and reload the page:

 First time: loading time is 4.28s according to FireBug
 Response headers:

     Age 0
     X-Symfony-Cache GET / : miss, store

 Reload second time: loading time is 82ms

     Age 9
     X-Symfony-Cache GET/ : fresh

 Reload third time loading time is 1.86 s

    Age 0
    X-Symfony-Cache GET / : stale, invalid, store

Things work as expected, the response time lowered to 82ms.


Now I will be testing the HTTP Cache Validation strategy.

     /**
     * @Route("/", name="homepage_symfony")
     */    
    public function indexAction(Request $request)
    {
        $text = "Welcome!";
        $response = new Response(); 
        $response->setETag(md5($text)); 
        $response->setPublic(); // make sure the response is public/cacheable
        if ($response->isNotModified($request)) { 
            return $response; 
        };

        $response->setContent($this->renderView('default/index.html.twig', array('content'         => $text)));
        return $response;
   }


First delete cache. Than reload page in browser:

First time:
X-Symfony-Cache  GET /: miss, store

Second time:
X-Symfony-Cache  GET /: stale, valid, store

At this moment there is not a big improvement as we are not avoiding any complex logic, just that instead of rendering the page with Twig we are serving it from cache.


Expiration and Validation 

You can of course use both validation and expiration within the same Response. As expiration wins over validation, you can easily benefit from the best of both worlds. In other words, by using both expiration and validation, you can instruct the cache to serve the cached content, while checking back at some interval (the expiration) to verify that the content is still valid. (from Symfony doc)

I will add setSharedMaxAge(30)to the controller resulting:

public function indexAction(Request $request)
{
    $text = "Bun venit pe pagina";
    $response = new Response();
    $response->setETag(md5($text));
    $response->setPublic(); // make sure the response is public/cacheable
    $response->setSharedMaxAge(30);
    if ($response->isNotModified($request)) {  
          return $response;
    }; 
   $response->setContent($this->renderView('default/index.html.twig', array('content' => $text)));
    return $response;
}

For 30 seconds the cache will be fresh and the page will be served from cache. After that using the ETag the freshness of the cache will checked and if it is still fresh the controller will return a 304 status with an empty content to the HTTP Cache which will serve again the response from cache.

First time loading the page:

Age               0
Cache-Control  public, s-maxage=30
Etag    "9c8751de832e5a472655e87731c49419-gzip"
X-Symfony-Cache  GET /: miss, store

Second Time:

Age   5
X-Symfony-Cache   GET /: fresh

After more than 30s

Age     5 
X-Symfony-Cache GET /: stale, valid, store


No comments:

Post a Comment