Problem/Motivation
It seems that, in general, renderable components and/or plugins like blocks and field items are generally limited regarding reusing instances of them on the same page multiple times, due to the behavior of the caching system. All objects holding renderable data and implementing CacheableDependencyInterface
seem to be affected by this. I'm trying to describe the limitation with the following simplified example:
A given render array (element) represents an HTML tag with a unique HTML id being randomly generated.
The only requirement of the element is, that it must have a unique HTML id when it's being rendered.
The element will be returned by a block plugin, i.e. a class which implements BlockPluginInterface
.
The block plugin does not depend or relate on any data / configuration of the system, i.e. no users, no nodes, no URL etc - it's just there to output the element.
The only requirement of the block plugin is, that an instance of it must be globally reusable.
An instance of the block has been created and placed via the block layout.
A node of a certain type has an entity reference field to blocks. The teaser mode of the node outputs the entity reference field.
There are now multiple nodes which reference to the same block instance. A view on a page lists these nodes in teaser view mode.
In the list, the block outputs the element multiple times, but cached. As a result, the HTML id is not unique and thus doesn't meet its corresponding requirement.
A way to meet both requirements (from element and block plugin) is to set the block plugin's max-age
cache to 0. This means that the block doesn't allow any caching. Because of the render cache's "bubbling" behavior, this would imply that all parent render elements won't be cacheable. This means, when an instance of the block plugin is getting rendered inside a field item, all the field items, the whole teaser view, the whole list view, the region where this list view is getting rendered and its further parents won't be cacheable in the render cache.
For block plugins, you could use hook_block_build_alter() to remove its generated cache key for "passing through" the render array. This can be useful e.g. for blocks which
- are cheap to render and
- most likely have invalid cache records when their parent elements have invalid cache records
Proposed resolution
Enable objects implementing CacheableDependencyInterface
to deliver some sort of cache passthrough, i.e. they just take the bubbled caching metadata from their child elements and pass them through (or say "bubble up") to their parent elements. This way, these objects are just further parts of the whole render structure, but their parent elements (in the above example the field items, teaser view mode, list views and regions etc.) may be still cacheable.
I'm not sure whether it's clear what I've written here. Feel free to ask for more details. Of course it's also possible that I miss or misunderstood an important part of the caching system here.
Comments
Comment #2
mxhRegarding field items, the problem might become relevant if you have a field item list with multiple items which have all the same value. There also might be something like an HTML id which must be unique, regardless of the given value of the item itself.
Regarding the above example: Yes, I'm aware of the block field module, but this wouldn't solve the elementary problem of this issue report.
Comment #3
BerdirDifferent placements aren't going to re-use the same instance of a block plugin.
What you are describing can be a problem, but you should definitely be able to rely on the fact that a block placement, another block placement and a reference in a node (I'd recommend using block field or so for that not an entity reference but that's another topic) have different instances that are *not* re-used.
If you want to have non-cacheable parts within something that is cached, then look into lazy builders: https://md-systems.github.io/drupal-8-caching/#/10/1. Blocks rendered wth the block view builder already automatically put within a lazy builder, so they can be placeholdered when they can't be cached.
Comment #4
mxhYes, but the described problem contains a requirement that instances must be reusable.
This would solve the problem in particular for blocks, but not in general.
Yes, the placeholder concept for lazy builders might solve it. Thanks for this tip, I'll try it out.
Comment #5
mxhComment #6
mxhUsing lazy builders should be the way to go for this scenario.
Comment #7
mxhUnfortunately I have to revoke my comment #6. Lazy builders may help, but not for any scenario. In addition, lazy builders will be called and executed at anytime, although not necessarily needed. Reusing instances like blocks shouldn't be a force for "uncacheability". Something like a cache passthrough mentioned in the proposed solution would something more useful, eg. something like
Cache::PARENT
. Relating this one #2888838: Optimize render caching as the reults of the discussion there might have impacts on this issue.Comment #8
mxhComment #10
Wim LeersPlaceholders/lazy builders do not imply uncacheability. It's totally possible that a lazy builder tries to render something, and that something happens to already be cached in the Render Cache: a cache hit is totally possible.
Comment #12
mxhComment #13
mxhWhen plugins implementing CacheableDependencyInterface could determine whether they should have their own cache records, this would enable developers to reduce the amount of I/O between application and cache backend. There are blocks and other items which are cheap to render, but are very likely to be invalidated very often.