Display Cache

Caseledde's picture

A way to skip the render process of all displayed entities.

For a project, we needed a caching solution which is not time-based and deals with logged in users. So we can't use Varnish and the caching methods of Views and Panels.
During the developement we built a module called Display Cache.

The idea

An entity, which does not change, does not need to render again and again.

So we provide a wrapper function, which calls entity_view() and drupal_render() and caches the result.

Next time the function is called, just the cached html will be returned.

Every time an entity changes in any way, the cache entries for this entity will be flushed.

Views and Panels integration

We always use Panels and Views in our projects, so the next step was to provide plugins for ctools and views. These plugins call the wrapper function.

With the combination of the wrapper function and the plugins we were able to skip the render process of all entites.

So, what is your opinion?

I created a sandbox project with a little more explanation: http://drupal.org/sandbox/Caseledde/1970904

Comments

Great!

fmizzell's picture

Entity rendering is crazy slow, so I have a very similar solution for one of my sites, and it makes a huge difference. So, needless to say I think this is great, but caching is hard, and as soon as you have any fanciness going on with your entities, things start becoming less useful. A couple of problematic things that I found are:

  1. relations: if you use entity reference or relation module to create more complex entity structures, the display cache might not be as useful. For example, we would like parent-entity caches to be rebuilt when children-entities change. In a more robust set up you would like for a parent cache to be rebuilt when a specific piece of data from a child changes. The point is that robust caching is hard :).
  2. authenticated users: Some times entities are displayed differently depending on who is viewing them. So a one cache per view mode approach will not work (unless you're view modes are user and role aware).
  3. only useful for embedded entities: Entities from which you call entity_view directly will benefit from this, but node pages, user pages, pages created by modules like eck will not benefit from display caching, unless they purposefully offer integration for the module, or if the pages are overridden by panels.

Anyways, it sounds like a lot of work has gone into this, so I will probably drop my custom module. I just hope that there are plenty of places to hook in so that other modules can add the kind of functionality needed to address the previously given problematic cases.

Looking forward to checking the code.

I've been working on caching

arnoldbird's picture

I've been working on caching whole lists of entities in my entity_lister module. After reading fmizzell's post I saw I hadn't been caching with roles in mind -- oops. So, I fixed this by checking the user's role(s), imploding them into a comma-separated string and adding them to my cache ID. Looks like the same approach would work in display_cache_get_rendered_entity().

@Caseledde, I will keep an eye on your progress to see what I can learn. Just as you have done, I avoid the time-based approach. I delete the cache entry whenever there are relevant content changes. As your module progresses I may provide an optional integration for those who would rather cache at the individual entity level, rather than (or in addition to?) the list level. Looks like it would be quite easy for me to provide that option.

Thx

Thanks for your

Caseledde's picture

Thanks for your responses.

Yep, caching is hard and I know the problems you have mentioned.

  1. relations: We had exactly this issue during our project. We used a simple custom module to solve that. It seems to be very hard to find a generic solution to all issues.
  2. authenticated users: You just mentioned that one solution could be that view modes are user and role aware. The problems are the double configurations. The second option is to use tokens in templates and replace them with token_replace(). But for that you need a developer. Another solution could be to implement the cache id with roles in mind. This seems to be the best solution.
  3. only useful for embedded entities: The problem is replacing entity_view() and entity_render() everywhere they are called. In core it seems impossible. I don't know how eck implemented this. This is the reason why display cache provides only panels and views so far.

Here is just a little code snippet to show how we solve the relations problem:

<?php
/**
* Implements hook_node_insert().
*/
function my_module_node_insert($node) {
 
my_module_node_save($node);
}

/**
* Implements hook_node_update().
*/
function my_module_node_update($node) {
 
my_module_node_save($node);
}

/**
* Shared saving routine between my_module_node_insert() and
* my_module_node_update().
*/
function my_module_node_save($node) {

  if (
$node->type === 'sub_content') {
   
$field = 'field_reference';
  }
  else {
    return;
  }

 
$item = field_get_items('node', $node, $field);
  if (!empty(
$item)) {

   
$parent_id = $item[0]['target_id'];

   
// Change parents changed date to flush caches from entitycache and display_cache.
   
$parent = node_load($parent_id);
   
$parent->changed = $node->changed;
   
node_save($parent);
  }
}
?>

Cache by roles

Caseledde's picture

Display Cache now provides independent cache entries for distinct role sets. It is configurable for each plugin instance and can be used in display_cache_get_rendered_entity($entity_type, $entity_id, $view_mode, $roles = array()) as well.

$roles is optional. If $roles is set it has to be an array with a set of roles.

Update

Caseledde's picture

After a complete refactoring, some facts has changed:

  • Caching provided by core: The caching mechanisms are moved to core caching abilities. This means, that entity_view() is supported directly and the plugins are obsolete.
  • Improved cache granularity: Cache per-url, per-role and per-user are supported now.

Quick question ...

fmizzell's picture

First of all, beautiful! But I saw this code:

$entity = entity_key_array_by_property(array($entity), $info['entity keys']['id']);
$render_array = $info['entity view callback']($entity, $view_mode, $langcode, $entity_type);
$render_array = $render_array[$entity_type][$entity_id];
cache_set($cid, $render_array, 'cache_display_cache');

So we are actually not skipping the rendering step, just the building of the render array. Shouldn't we render that array, and save the html in the cache?

The caching of the renderable

Caseledde's picture

The caching of the renderable array ist just additional and just possible if entity_view() is called. It does not work with node_view() for example.

The caching of the rendered html is located in display_cache_entity_view_alter() and display_cache_field_attach_view_alter().

These functions add the '#cache'-attribute to the renderable array. Thus the core is responsible for the caching:

<?php
$build
['#cache'] = array(
     
'keys' => $keys,
     
'bin' => 'cache_display_cache',
     
'granularity' => $display_settings['granularity'],
    );
?>

The real caching mechanism is located in drupal_render():

<?php
// Try to fetch the element's markup from cache and return.
 
if (isset($elements['#cache'])) {
   
$cached_output = drupal_render_cache_get($elements);
    if (
$cached_output !== FALSE) {
      return
$cached_output;
    }
  }
?>

Ahhh

fmizzell's picture

I thought that the #cache key was something internal to display_cache, but now I see it is some functionality provided by core. Great!

During my testing I noticed that nothing was getting cached b/c entity_entity_info_alter was being called after display_entity_info_alter. I noticed you took care of making sure that entity_view_alter is called last, but I am guessing that the same should be done for entity_info_alter.

I guess I should move this kind of stuff to the issue queue :)

Ready for review

Caseledde's picture

I added this module to the issue cue for drupal.org project applications.

http://drupal.org/node/2001710

I would be glad to all reviews.

Contributed Module Ideas

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds:

Hot content this week