Problem/Motivation
We added this in [#2222293]. But it really makes very little sense, because each block should automatically get the cache contexts it must be varied by. Back then, that seemed an impossibility. But a few weeks ago, #2429257: Bubble cache contexts landed, which made it a reality.
(Bubbling also is sufficient for custom blocks — thanks to fields' and filters' ability to associate cache tags and cache contexts.)
If we're asking the user to select the contexts by which a block should be varied, we've already failed: the user shouldn't even have to make that choice. It should happen automatically, based on the contents of the block.
Proposed resolution
Remove the UI.
Remaining tasks
TBD
User interface changes
Remove this confusing UI.
API changes
- The config schema is changed.
BlockBase::getRequiredCacheContexts()
is gone, butBlockPluginInterface
is unaffected. So the typically used base class is affected, so most blocks are affected, but not those that implement the interface directly.
Beta phase evaluation
Issue priority | Major because 1) despite being horrible for usability in the first place, 2) it also makes caching less efficient than it can be, and 3) it ends up being a lie to the user: we present the configured cache contexts as the only cache contexts being varied by, but the contents of the block may (and almost always do) bubble additional cache contexts, thus making the UI not only unusable, but also a lie that we cannot possibly explain clearly. |
---|---|
Prioritized changes | The main goal of this issue is usability + performance |
Disruption | Disruptive for custom/contributed modules in a minor way: if they provide blocks, and they extend the BlockBase class to do so, they'll have to rename getRequiredCacheContexts() to getCacheContexts() . |
Comment | File | Size | Author |
---|---|---|---|
#25 | block_cacheability-2428805-25.patch | 39.79 KB | Wim Leers |
Comments
Comment #1
Wim LeersComment #2
dawehnerAre you sure you can do that for very complex blocks like a mini panel or a view?
Comment #3
Wim LeersJust like #2099137: Entity/field access and node grants not taken into account with core cache contexts must be aware of the fields they contain and appropriately incorporate the cache contexts defined by the field formatters into the cache ID of the rendered entity, the same must be done for other things, like Views.
In any case: it'd be better to not force the "select cache contexts" UI on every block; it'd be a big step forward to only do it for e.g. Views blocks. Because surely, getting there (to what I describe above), that is a lot of work.
Comment #4
Wim LeersComment #5
effulgentsia CreditAttribution: effulgentsia commentedI actually see two options here:
Comment #6
Wim LeersI don't see how we are ever going to be able to explain option 2 clearly to the end user. That would be an interesting contrib project (block_max_cache_contexts or something like that), but it doesn't seem appropriate for Drupal core.
Comment #7
andypostAlso there's related mate issue and a part of that is #2231551: Clarify block cache settings configuration form
Comment #8
Wim LeersThanks, I didn't know about that one! We could simply close that issue if we indeed remove this (indeed too hard to use) UI.
Comment #9
BerdirUh, where?
D7 had the cache key in hook_block_info(), but that is not visible in the UI anywhere?
So far, what I've seen is that there are use cases where being able to set this manually are useful, but the bubbling might actually break that. The cases that I've seen so far are mostly about menus, menus can vary by url/user/roles but don't have to:
* It is very common to have static expanded menu trees in site footers. Because they are always expanded, there's no need for them to be cached separately for each page. So this allows me to cache them globally. The cache context bubbling might have already broken that, not sure what kind of contexts a menu tree currently adds and will add once we cache it?
* Same for menus where you know that they don't change for different users. For example the main navigation of your site might change based on the URL, but it has no access protection on any links, because everything is public, so again, you can disable the user/roles cache context and have better caching for it.
A way to keep supporting those use cases would be great. Doing it some sort of alter hook (like hook_block_view_alter()) would be fine with me, except that that is currently a bit tricky, as you'd have to AFAIK add a pre render callback, so you can alter it between getting generated in the pre render callback and it getting cached. But I can live with that.
Also, the fact that we have more and more cache contexts is problematic and making that list longer.
Views blocks were mentioned, not sure what will happen with those? Will they really be intelligent enough to figure out the right contexts with bubbling or do we still need it for them? So we'd need a system for certain plugins to opt-in to showing them?
Or we could have something like a cache context advanced mode that you would have to explicitly enable, so that the UI by default would just show a checkbox?
Comment #10
Wim LeersMust be misremembering :)
Yes, and no:
True! The real problems here are:
user.roles
cache context with all menu blocks. This is patently wrong. (And it's my fault, BTW.) As you say: some menus don't vary per role. Yet others have menu links that are actually per-user, in which case per-role is not even sufficient.NOTE: you cannot choose to not vary per-role anyway. So you currently aren't able to override this via the UI anyway
The way to keep supporting that is to do it automatically, thanks to cache context bubbling :) Cache context bubbling ensures we vary by the minimal set of cache contexts necessary, hence minimizing the number of cache item variations, and thus maximizing the cache hit ratio.
Exactly; it's becoming overwhelming. And it'll only get worse in contrib.
They will be smart enough. Each Views plugin/handler specifies its cache contexts. See #231837: Just white screen after activating it; and see all
\Drupal\views\Plugin\CacheablePluginInterface
implementations specifically. This is not yet 100% done, but we're actively working towards this, see #2451679: Validate cache contexts (+ cache contexts in some views plugins wrong), #2381277: Make Views use render caching and remove Views' own "output caching" and #2452317: Let views result cache use cache contexts, for example.There shouldn't be a need for this anymore. Render arrays must say which context they vary by. Cache context bubbling ensures that the render cached blocks are varied by those contexts. The bubbled cache contexts are by definition the cache contexts that must be varied by.
Comment #11
Wim LeersInitial patch. I've tried to update all affected tests too, but I probably will have missed some.
Comment #12
Wim LeersUpdated IS.
Comment #14
Wim LeersTo encourage developers even more to make their blocks cacheable, the above patch changed
BlockBase::getCacheMaxAge()
to return a non-zero value.This change caused the test failures above. The solution is simple: the blocks that aren't cacheable should indicate that by returning a max-age of zero. I did this for all blocks that you can see in the interdiff, and added todos linking to the issues that allow those blocks to be made cacheable.
I did make the necessary changes to make form blocks cacheable (I ensured the right cache tags are present on it), because that was only a few LoC.
This will be green.
Comment #15
Wim LeersComment #16
BerdirHm.
Removing the cache contexts from the UI is great.
But I'm not fully convinced that removing the max age setting as well is too. Part of the reason is that I'm a bit scared about a change like that. But I think there are also valid examples where cache tag based invalidations are either not possible or are doing too much.
Some examples:
* I have a views block that's displaying the most read nodes, based on statistics.module. Viewing a node obviously doesn't clear any cache tags and I definitely wouldn't want that to happen. So, instead, I set block caching to 1h, because otherwise it would only get updated if a node actually changes. I don't think there's any alternative to max age here, and that's OK. But since it's a generic views block, I need a generic UI to configure that.
* A similar example is a list of most commented nodes. That might be possible with cache tags, there certainly are cache tags invalidated when a new comment is posted. But on a highly frequented website, you might have a lot of new comments and don't want to invalidate your cached frontpage every time someone writes a new comment. So again, using a time based cache invalidation is the easiest option. There are others, but they're not trivial, because in my example, that list is actually provided by search_api/solr, so I would have to invalidate after the node has been re-indexed, not when the comment is posted.
That example actually gave me an interesting idea. Both blocks would also be invalidated when nothing changed of course. What would be really interesting would be a cache tag invalidation with a delay mechanism. For example, invalidating a cache tag would actually only be triggered 1h later, any additional invalidation within that timeframe would be ignored. But that's not something that we have right now and I don't think we could build that in core.
So, back to this issue. I think I would prefer to keep the max-age option, but I'm interested in other opinions.
I know that your main goal is to make D8 fast by default, and not rely on manual configuration by users. Which currently is required, because max age defaults to never. I totally agree that's bad. What if we change the *default* max age to permanent (with a way for plugins to override/change that)? Then blocks are still cached by default, but without losing the ability to tune it for special use cases.
Comment #17
Wim LeersAgreed. But in those cases, it's up to the code providing the block to set the appropriate max-age. The main reason we even had the cache contexts & max-age in the UI, was for custom blocks. But custom blocks now inherit the cache contexts of the fields they contain, and the cache contexts and max-age of the filters that are applied to the body text. So that use case is completely gone.
So then:
This has also crossed my mind a long time ago, but like you, I dismissed it for now because it doesn't belong in core, and because we have more urgent matters to deal with first. But, yes, absolutely: very fascinating :)
This patch already does exactly that :)
Comment #18
Wim Leers#2458349: Route's access result's cacheability not applied to the response's cacheability is now also blocked on this.
Comment #19
Wim LeersFrom IRC:
\Drupal\comment\Plugin\views\field\StatisticsLastCommentName
. To communicate the cacheability of a View, we already have\Drupal\views\Plugin\CacheablePluginInterface
. It'd just be a matter of extending that plugin with agetCacheMaxAge()
method (or rather, replacing::isCacheable()
with that) and then we'd have the necessary data.Another reason we should remove the max-age support: because it's not something that's part of blocks themselves; it's part of
BlockBase
! Hence the config schema and config are actually written againstBlockBase
instead ofBlockPluginInterface
, which is very, very wrong.If that still doesn't convince you, then let's remove blocks' configurable max-age in a separate issue then, since we have agreement on removing block cache contexts.
(Attached patch is just chasing HEAD.)
Comment #20
Wim LeersDiscussed this with @Berdir and @catch in IRC.
We all think that we can and should remove a configurable max-age from blocks. But not in this issue, because to do it, would require users to be able to configure the max-age in the Views UI (which would then apply to a Views block, but also other displays of a view). I.e. it belongs on the block itself. That'd also require extending Views'
CacheablePluginInterface
to specify a max-age (::getCacheMaxAge()
), probably replacing::isCacheable()
in the process.So, assigning this to myself to reroll accordingly.
Comment #21
Wim LeersDone.
Per @Berdir in IRC, he was +1 to changing the default max-age value for
BlockBase
-based blocks toCache::PERMANENT
(which he also said at the bottom of #16). This allowed me to keep the interdiff smaller.Comment #22
BerdirThis function should have died in fire a long time ago :(
That said, shouldn't we also add the list cache tag, for now?
Thanks for adding the max-age UI back :)
Comment #23
Wim Leers:D
Yes. (The only reason I'm touching it, is because I broke the forum blocks along the way; I already added
node_list
toForumBlockBase::getCacheTags()
.)Comment #24
Wim LeersIS updated.
And for #20 & #22: created #2458763: Remove the ability to configure a block's cache max-age to remove the configurable max-age from
BlockBase
.Comment #25
Wim Leers#2458413: BlockViewBuilder should specify cache contexts even for uncacheable blocks landed, reroll to fix a small conflict.
Comment #26
Fabianx CreditAttribution: Fabianx commentedThis needs to be max_age due to CMI restrictions / naming scheme right?
Comment #27
Fabianx CreditAttribution: Fabianx commentedThis is RTBC, but lets follow-up on the documentation of max-age on the block page:
"The maximum time this block may be cached."
This should read something like:
"The maximum time this block may be cached. Note that this affects the cacheability of the whole page, too."
Or something to that affect.
I found this by visual testing the changes here and they look good.
Comment #28
Wim LeersComment #30
catchCommitted/pushed to 8.0.x, thanks!
Comment #31
dawehner.
Comment #32
Wim LeersYay!
This unblocked:
And it allowed me to close #2231551: Clarify block cache settings configuration form.