building and rendering entities can be slow
render arrays have a #cache property!
function render_example_arrays() {
$interval = 60;
$page_array = array(
t('cache demonstration') => array(
'#markup' => t('time: ') . time(),
'#cache' => array(
'keys' => array('render_example', 'cache', 'demo'),
'bin' => 'cache',
'expire' => time() + $interval,
'granularity' => DRUPAL_CACHE_PER_ROLE,
),
),
);
return $page_array;
}
function render_example_arrays() {
$interval = 60;
$page_array = array(
t('cache demonstration') => array(
'#markup' => '',
'#pre_render' => array('render_example_cache_pre_render'),
'#cache' => array(
'keys' => array('render_example', 'cache', 'demo'),
'bin' => 'cache',
'expire' => time() + $interval,
'granularity' => DRUPAL_CACHE_PER_ROLE,
),
),
);
return $page_array;
}
function render_example_cache_pre_render($element) {
$element['#markup'] = t('time: ') . time(),
// @see http://drupal.org/node/914792
$element['#children'] = $element['#markup'];
return $element;
}
http://omnifik.com/blog/render-arrays-and-cache-drupal-7
https://www.acquia.com/blog/drupal-8-performance-render-caching
demo!
very simple site
core, features, views
devel_generate to populate it
50 article nodes
1 page node with 300 comments
function render_cache_demo_render_cache_entity_cache_info_alter
(&$cache_info, $entity, $context) {
$cache_info['render_cache_render_to_markup'] = TRUE;
}
drush --yes pm-disable render_cache
# make sure we have warm caches
ab -n 1 -c 1 http://rendercache.local/ > /dev/null
ab -n 30 -c 5 http://rendercache.local/
drush --yes pm-enable render_cache_views render_cache_comments
# make sure we have warm caches
ab -n 1 -c 1 http://rendercache.local/ > /dev/null
ab -n 30 -c 5 http://rendercache.local/
drupal.org/node/15365 (220 comments)
Includes integration with comments, context, ds, nodes, views
Works with custom entities
(if they use entity_view() from entity API)
Hooks:
hook_render_cache_entity_default_cache_info_alter($cache_info, $context)
hook_render_cache_entity_cache_info_alter($cache_info, $entity, $context)
hook_render_cache_entity_hash_alter($hash, $entity, $cache_info, $context)
hook_render_cache_entity_cid_alter($cid_parts, $entity, $cache_info, $context)
cache_info defaults:
return array(
'bin' => 'cache_render',
'expire' => CACHE_PERMANENT,
// Use per role to support contextual and its safer anyway.
'granularity' => DRUPAL_CACHE_PER_ROLE,
'keys' => array(),
// Special keys that are only related to our implementation.
'render_cache_render_to_markup' => FALSE,
);
hash defaults:
How it was implemented in d.o:
https://www.drupal.org/node/2299547
/**
* Implements hook_render_cache_entity_default_cache_info_alter().
*/
function drupalorg_render_cache_entity_default_cache_info_alter(&$cache_info, $context) {
// Disable entity_view() render caching by default to avoid side effects.
$cache_info['granularity'] = DRUPAL_NO_CACHE;
}
/**
* Implements hook_render_cache_entity_cache_info_alter().
*/
function drupalorg_render_cache_entity_cache_info_alter(&$cache_info, $entity, $context) {
// Always render_to_markup if possible.
$cache_info['render_cache_render_to_markup'] = TRUE;
// Check if render caching should be enabled for comments.
if (variable_get('drupalorg_render_cache_comment_enabled', TRUE)
&& $context['entity_type'] == 'comment'
&& $context['view_mode'] == 'full') {
$cache_info['granularity'] = DRUPAL_CACHE_PER_ROLE;
// Store that this needs special comment processing.
$cache_info['drupalorg_comment_processing'] = TRUE;
}
}
/**
* Implements hook_render_cache_entity_hash_alter().
*/
function drupalorg_render_cache_entity_hash_alter(&$hash, $entity, $cache_info, $context) {
if (!empty($cache_info['drupalorg_comment_processing'])) {
// Comment is displaying users, so need to add this to the hash, entity is already added.
$hash['comment_uid'] = $entity->uid;
if (!empty($entity->uid)) {
$hash['comment_uid_modified'] = entity_modified_last_id('user', $entity->uid);
}
$hash['is_viewer'] = $entity->uid == $GLOBALS['user']->uid;
$node = node_load($entity->nid);
$hash['is_author'] = $entity->uid == $node->uid;
$hash['is_new'] = !empty($entity->new);
$hash['first_new'] = !empty($entity->first_new);
// @todo Maybe disable caching for preview?
$hash['in_preview'] = isset($entity->in_preview);
// @todo Maybe disable caching for threaded comments?
$hash['divs'] = isset($entity->divs) ? $entity->divs : '';
$hash['divs_final'] = isset($entity->divs_final) ? $entity->divs_final : '';
}
}
(in 7.x-1.x...)
Embedded entities
Think in Entities
Permanent cache
X-Drupal-Cache-Tags
header + reverse proxies#post_render_cache
… clears the entire page cache on node save!
cache_clear_all('foo:content:id', $bin);
cache_clear_all('foo:content:', $bin, TRUE);
cache_clear_all('*', $bin, TRUE);
⬇
“How to clear all entries containing node 42?”
⬇
Impossible!
A cache tag is a string
$cache_tags = array('node_view', 'node:5', 'user:3');
(It used to be much more complex.)
Adding a cache tag to a render array
$element['#cache']['tags'][] = 'user:' . $user->id();
Every entity has a cache tag
EntityInterface::getCacheTag()
$user->getCacheTag()
$node->getCacheTag()
$term->getCacheTag()
Not only content entities, but also config entities:
$menu->getCacheTag()
$tour->getCacheTag()
…
Adding a cache tag to a render array
$element['#cache']['tags'][] = 'user:' . $user->id();
↓
$element['#cache']['tags'] = Cache::mergeTags(
$element['#cache']['tags'],
$user->getCacheTag()
);
Adding many cache tags to a render array
$element['#cache']['tags'] = Cache::mergeTags(
$element['#cache']['tags'],
$node->getCacheTag(),
$user->getCacheTag(),
$term->getCacheTag(),
…
);
Invalidating cache tags
$entity->save();
Or, manually:
Cache::invalidateTags($entity->getCacheTag())
Helpful exceptions
Cache::invalidateTags(array('something' => TRUE));
… already does this for you!
$build = entity_view(Node::load(5));
// Then, once the entity is rendered, all relevant cache tags are present:
drupal_render($build);
$build['#cache']['tags'] == array(
'filter_format:basic_html',
'node:5',
'node_view',
'taxonomy_term:15',
'taxonomy_term:23',
'user:12'
);
And this is what allows automatic render caching of entities!
Just like JavaScript events!
<html>
</html>
<html data-cache-tags="[all the cache tags from all the regions]">
</html>
X-Drupal-Cache-Tags
headerbartik_content block:bartik_footer block:bartik_powered block:bartik_search block:bartik_tools block_plugin:search_form_block block_plugin:system_main_block block_plugin:system_menu_block__footer block_plugin:system_menu_block__tools block_plugin:system_powered_by_block block_view filter_format:basic_html menu:account menu:footer menu:main menu:tools node:1 node:2 node_view rendered taxonomy_term:1 taxonomy_term:2 theme:bartik theme_global_settings user:1 user_view
↓
Purge per cache tag on reverse proxies (Varnish, CDN…)!
(Instantaneous invalidation of all occurrences of a block, an article …)
Purge module 8.x-2.x, by Niels van Mourik
#post_render_cache
#post_render_cache
CommentDefaultFormatter
:
$callback = 'comment.post_render_cache:renderForm';
$context = array(
'entity_type' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
'field_name' => $field_name,
);
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
$output['comment_form'] = array(
'#post_render_cache' => array(
$callback => array(
$context,
),
),
'#markup' => $placeholder,
);
#post_render_cache
/**
* #post_render_cache callback; replaces placeholder with comment form.
*
* @param array $element
* The renderable array that contains the to be replaced placeholder.
* @param array $context
* An array with the following keys:
* - entity_type: an entity type
* - entity_id: an entity ID
* - field_name: a comment field name
*
* @return array
* A renderable array containing the comment form.
*/
public function renderForm(array $element, array $context) {
$comment = …; // Use the data in $context to create an empty comment.
$markup = drupal_render($this->entityFormBuilder->getForm($comment));
$callback = 'comment.post_render_cache:renderForm';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
$element['#markup'] = str_replace($placeholder, $markup, $element['#markup']);
return $element;
}
Drupal 8 is slow!
True. But we're still optimizing it. Too much change pre-beta.
(Drupal rendering a page ~ building a ship)
A work-in-progress view of what I am working on with render_cache-7.x-2.x-dev.
'block:whos_online:u:%user'
'block:more_info:%page'
array('{1234-5678}' => 'block:whos_online:%user');
GET: /fragment/1234-5678
X-Drupal-Request: /node/1
function hook_render_cache_block_cache_info_alter(&$cache_info, $object, $context) {
if ($context['module'] == 'rc_site') {
$cache_info['granularity'] = DRUPAL_CACHE_CUSTOM;
$cache_info['render_strategy'][] = 'big_pipe';
}
}
$ drush -y en rc_site render_cache_big_pipe
github.com/wimleers/talk-render-caching-in-drupal-7-and-8