/ Article

Caching in Drupal 8

November 22, 2017 6 Min Read

Drupal 8 has evolved in many ways over its predecessor. Some of the prominent features are the inclusion of the widely used views module into core, configuration management, and an easy to use translation service. However, today we are going to dive into one of the other major updates of Drupal 8 that provides a huge boost to performance and perceived performance: the cache layer.

Caching in Drupal 8 has received a much needed overhaul. It has become highly customizable and dynamic, and now has the ability to cache individual blocks or items on a page. This means that we can control exactly when a block on a page has its cache cleared instead of clearing the entire page cache.

Diving deeper into caching

There are three main components to the cache layer in Drupal 8:

  • Cache tags
  • Cache contexts
  • Cache max-age

Cache tags

Cache tags are for dependencies on data that are managed by Drupal, and are the easiest way to control cache. As an example, let’s consider a site with a news story content type and view page. This site has a list of three nodes that are displayed in a block on the homepage. How would the homepage cache (more specifically the news block cache) be cleared if we added a news story to the top of this list?

The answer is cache tags!

Cache tags are used to inform the system when the news block would need to be invalidated. The homepage might have cache tags specific to the page itself node:5, but this will not invalidate the entire homepage when a news story node:18 is added since the cache tag does not exist in the list of tags for the homepage. The news story block, on the other hand, would have specific tags that line up with the node ID’s that currently appear in the list node:16; node:17; node:18. So when we update or add a new news node, we will invalidate all caches that contain that particular cache tag. In this case node:18 tag will be invalidated, and thus the block that contains the tag node:18 will also be invalidated.

Cache contexts

Cache contexts are used when a dependency exists on an environment-specific object. Content is often reused across multiple pages and the cached object could be rendered differently based on the environment’s state.

In this example, we will take a look at how cache contexts can be used and important considerations for developers, by modifying the default breadcrumb in Drupal 8. By default, the breadcrumb only shows the path up to and including the parent page. For many websites, we also need to display the current page title in the breadcrumb. Here we have altered it to add the current page title to the end:

function theme_preprocess_breadcrumb(&$variables) {
  if (($node = \Drupal::routeMatch()->getParameter('node')) && $variables['breadcrumb']) {
    $variables['breadcrumb'][] = [ 'text' => $node->getTitle() ];
  }
}

Now the following page http://home.com/page-level-1/page-level-2/page-level-3 will have the following breadcrumb:

Home > Page Level 1 > Page Level 2 > Page Level 3

Great! We have the breadcrumb we are looking for. But we have not yet considered how this might have affected caching. Additionally at this point we have not changed anything to do with the caching of the breadcrumb. Drupal does not know how it should handle caching our new version of the breadcrumb and this can create some unexpected behaviours.

If we look at the cache contexts, which can be configured to be sent along in the response header, we see the breadcrumb is cached on the context of the parent url:

This means that once Page Level 3 is loaded, the breadcrumb is now cached based on the parent url. When a sibling page like Page Level 3-1 is loaded, the parent page has not changed, and thus the breadcrumb will be loaded from cache. This can give us unexpected behaviour due to the missing cache context.

When running on a cold cache (a cache that has been cleared or rebuilt) and you load Page Level 3-1, the proper breadcrumb is displayed. Home > Page Level 1 > Page Level 2 > Page Level 3-1

When you go to the sibling page Page Level 3, you will notice that the previous breadcrumb is still showing:

Home > Page Level 1 > Page Level 2 > Page Level 3-1

When we altered the breadcrumb we also needed to be aware of the cache settings on it. Since the breadcrumb is now unique to each specific page and not just to the parent page, we need to add the following to our preprocess_breadcrumb function:

function theme_preprocess_breadcrumb(&$variables) {
  if (($node = \Drupal::routeMatch()->getParameter('node')) && $variables['breadcrumb']) {
    $variables['breadcrumb'][] = [
      'text' => $node->getTitle()
    ];
    $variables['#cache']['contexts'][] = 'url.path';
  }
}

With http.response.debug_cacheability_headers enabled, we can now see that the url.path context is appearing in our response header.

This will then use the current path to cache the breadcrumb. Now, when running on a cold cache, we load page Page Level 3-1 and the proper breadcrumb is shown.

When loading the sibling Level 3, the proper breadcrumb is now showing for it as well.

Home > Page Level 1 > Page Level 2 > Page Level 3

This is a quick example of how cache contexts can alter the output of a page, and how developers must take this into consideration when working with Drupal 8.

Cache max-age

Cache max-age is used when there are time dependencies attached to the content. An example of a time dependence is the need to invalidate items when using feeds or timelines as the content changes periodically. For example, let’s say we are importing a feed into a block and we have the import set to every 30 minutes. We would want to add a max-age cache of 30 minutes to the block in order to invalidate it consistently after the feed is imported. Anytime your content has a dependency on time, you should consider checking or setting a Max-Age cache value.

Big Pipe

Drupal recently included an experimental module in core called Big Pipe. The Big Pipe concept is about loading page data as it becomes available instead of waiting for the entire response before showing anything. This will not affect the total response time, but will greatly reduce the perceived response time. The page will appear to load faster and portions of the content can be readable in less time. Below is a video showing the perceived speed of Big Pipe. Page data is displayed as it becomes ready, and placeholders are created for sidebar items that will be filled in as the data is available. This caching improvement applies to authenticated users viewing pages which have partially customized content.

Wrap up

This was a quick look into the new cache layer in Drupal 8. The cache layer provides three ways for us to declare how we want our feature or content to be cached. These components of the cache layer are a very important detail we need to take into account when developing for Drupal 8. The updates to the cache layer provide a very robust system and are a big stepping stone for Drupal 8’s performance. Don’t forget to take a closer look at the cache API and how it can help you store, retrieve, invalidate, and manage your own cache tags.

Resources:

/ Author

zu Crew

On behalf of the team