The 'node_list' cache tag pitfall in Drupal 8 views

The node_list cache tag is generated automatically when we create a view that displays nodes entity type in Drupal 8. This cache tag will invalidate the cache of all views that list any kind of nodes (page, article, ....) when we make a CUD (create, update, delete) action on any kind of nodes.

This is a very good cache invalidation strategy since when we modify a node through a CUD action, the cache of every views that display nodes will be invalidate to reflect this new change. Yes, the improvements in the new Drupal8 Cache API are amazing! Thanks to all the contributors (like Wim Leers) to make this possible.

node_list cache tag views drupal9

(to see the cache tags in your header response, just enable your settings.local.php)

So far so good, but... What would happen if we have a high traffic web site with hundred of different node bundles and hundred of views displaying different kind of nodes?

In that case, if we modify a node (let's say a page), the cache of every views listing page nodes (but also views listing article nodes and other node bundles) will be invalidated. This means that if we modify a page node, the cache of all views displaying article nodes will also be invalided. This could be a serious performance issue and especially when we have a lot of updates like in a newspaper web site.

How can we invalidate the view caches in a more specific way, let's say only for nodes of the same type?

To do so, we'll use a two steps process:

- Place a custom cache tag for all views displaying the same node type, like node:type:page for all views listing page nodes, node:type:article for views listing articles nodes and so on...
- Invalidate these custom cache tags when a CUD operation is performed on a specific node type with a hook_node_presave() 

Add custom cache tags with the Views Custom Cache Tags contrib module

Luckily, to solve the node-list cache tag problem, the community made a contrib module that let us to place custom cache tags in our views: the Views Custom Cache Tags contrib module. This module allows us to define custom cache tags for any view we build.

In this case, we are going to place in our views a custom cache tags for each type of node we are displaying:
node:type:article for views listing articles
node:type:page for views listing pages
node:type:<your custom node type> ....

First, we are going to download and install the module.

# Download the module with composer
composer require drupal/views_custom_cache_tag
# Install the module with DC
drupal moi views_custom_cache_tag 

Then we could create two block views, one listing 5 articles (name it Block Articles) and one listing 5 pages (name it Block Pages). Next we place a custom cache tag node:type:article in the view that list articles (Block Articles). We do the same, but with an other custom cache tag like  node:type:page in the view listing pages (Block Pages).

Let's see how to do it for the view (block) listing articles:

1. Edit the view
2. Go to ADVANCED and click on 'Tag based' in Caching

custom cache tags views drupal8

3. Then click on Custom Tag based

custom cache tags views drupal 8

4. Insert the custom cache tag for this node type. In our case, as we are listing articles, we introduce node:type:article. For views listing other kind of nodes, we'll introduce node:type:<node-type>. Don't forget to click on Apply when you're done.

custom cache tags views drupal8

5. Save the view

custom cache tags views Drupal 8

When we have placed custom cache tags in all views listing nodes, we can now move to the second step, invalidate these tags when needed.

Invalidate custom cache tags with a hook_node_presave

Now we need to invalidate these custom cache tags when a CUD action is performed on a specific node type thanks to the hook_node_presave.

To do that, we're going to create a module. You can download the code of this module here.

Let's first create our module with Drupal Console as follow:

drupal generate:module  --module="Invalidate custom cache tags" --machine-name="kb_invalidate_custom_cache_tags" --module-path="modules/custom" --description="Example to invalidate views custom cache tags" --core="8.x" --package="Custom"  --module-file --dependencies="views_custom_cache_tag"  --no-interaction

Then we enable the module, we can do it also with Drupal Console:

drupal moi kb_invalidate_custom_cache_tags

Now we can edit our kb_invalidate_custom_cache_tags.module and place the following hook:

// For hook_ENTITY_TYPE_presave.
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;

/**
 * Implements hook_ENTITY_TYPE_presave().
 *
 * Invalid cache tags for node lists.
 */
function kb_invalidate_custom_cache_tags_node_presave(EntityInterface $entity) {
  $cache_tag = 'node:type:' . $entity->getType();
  Cache::invalidateTags([$cache_tag]);
}

Yes, we still have hooks in Drupal8... This hook will be fired each time a node is created or updated. We first retrieve the node type (page, article, ...) with $entity->getType() and create the variable $cache_tag with this value, this variable will correspond to our custom tag for this kind of node. Next we mark this cache tag in all bins as invalid with Cache::invalidateTags([$cache_tag]);. So the cache of every view with this custom cache tag will be invalidated.

In this case, if we insert or update a node of type article, the views custom cache tag will be node:type:article and the cache of all views with this custom cache tag will be invalidated. The cache of the views with other custom cache tags like node:type:page will remain valid. This was just what we were looking for! Thank you Drupal!

Recap.

In order to avoid the cache of all node views to be invalidated when we make a CUD operation on a node, we need the general node_list cache tag to be replaced by a more specific custom cache tag like node:type:<node-type>.

Thanks the Views Custom Cache Tags contrib module we can now insert custom cache tags in our views based on the node type listed in the view like node:type:article, node:type:page and so on.

Next, we mark these custom cache tags as invalid when we insert or update a specific node type thanks to the hook_ENTITY_TYPE_presave hook we've placed on our custom module.

Voilà! If you have an other kind of strategy to face the node_list problem, please share it with us in the comments.