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, but BlockPluginInterface 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

Reference: https://www.drupal.org/core/beta-changes
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().
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Issue tags: +D8 cacheability
dawehner’s picture

Are you sure you can do that for very complex blocks like a mini panel or a view?

Wim Leers’s picture

Just 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.

Wim Leers’s picture

effulgentsia’s picture

I actually see two options here:

  • Either remove the selection UI, as the issue title currently suggests.
  • Or, change the meaning of the UI. I.e., have it control the maximum stuff that the block is cached on, but if once bubbling is performed, it turns out the block doesn't require one of those contexts, don't vary the cache on it (since it's unnecessary), and if the block has descendant elements that require an additional context (that wasn't selected in that UI), then have those elements automatically turned into #post_render_cache placeholders.
Wim Leers’s picture

I 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.

andypost’s picture

Also there's related mate issue and a part of that is #2231551: Clarify block cache settings configuration form

Wim Leers’s picture

Thanks, I didn't know about that one! We could simply close that issue if we indeed remove this (indeed too hard to use) UI.

Berdir’s picture

because D7 had it also

Uh, 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?

Wim Leers’s picture

Uh, where?

Must be misremembering :)

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.

Yes, and no:

  • Yes, bubbling would cause more cache contexts to be associated than the ones you selected.
  • No, because bubbling only adds cache contexts that are actually necessary.

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.
[…]
Same for menus where you know that they don't change for different users […]

True! The real problems here are:

  1. We currently associate the 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
  2. #1805054: Cache localized, access filtered, URL resolved, and rendered menu trees will ensure that the cache contexts associated with each rendered link (the access result + the generated URL) are bubbled. Once that is the case, we can remove that silly per-role cache context mentioned in point 1, and we'll automatically only vary by the cache contexts that we need to vary by.

A way to keep supporting those use cases would be great.

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.

Also, the fact that we have more and more cache contexts is problematic and making that list longer.

Exactly; it's becoming overwhelming. And it'll only get worse in contrib.

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?

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.

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?

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.

Wim Leers’s picture

Title: Remove the ability to select a block's cache contexts » Remove the ability to configure a block's cacheability
Status: Active » Needs review
Issue tags: +D8 upgrade path
FileSize
39.89 KB

Initial patch. I've tried to update all affected tests too, but I probably will have missed some.

Wim Leers’s picture

Issue summary: View changes

Updated IS.

Status: Needs review » Needs work

The last submitted patch, 11: block_cacheability-2428805-11.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
46.29 KB
7.93 KB

To 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.

Wim Leers’s picture

Issue summary: View changes
Berdir’s picture

Hm.

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.

Wim Leers’s picture

But I think there are also valid examples where cache tag based invalidations are either not possible or are doing too much.

Agreed. 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:

  • I have a views block that's displaying the most read nodes, based on statistics.module. — it's the responsibility of the Statistics + Views integration to set the appropriate max-age.
  • A similar example is a list of most commented nodes. […] 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 — indeed, but this sort of thing would be up to Views (or a Views plugin) to provide
  • In general, I agree that it's useful to override cacheability metadata for some kinds of blocks. But it's only necessary on relatively advanced/high-traffic websites. And it's definitely not something that's useful for all blocks. So IMO this belongs in contrib, or specific implementations. (For example in the Statistics example, it IMO belongs in the Statistics module, since that is so very clearly related to time and hence to max-age).

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.

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 :)

[…] 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)?

This patch already does exactly that :)

Wim Leers’s picture

Wim Leers’s picture

FileSize
46.29 KB

From IRC:

(12:04:03) berdir: WimLeers: (block cacheability UI) No the patch doesn't do that. my suggestion is to keep the UI for max age and default *that* to permanent
(12:05:31) berdir: WimLeers: "it's up to the code providing the block to set the appropriate max-age" The statistics example is a generic view block, not something from statistics.module
  • First line: Aha. I indeed intend to remove the max-age UI.
  • Second line: but the statistics Views integration comes from \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 a getCacheMaxAge() 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 against BlockBase instead of BlockPluginInterface, 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.)

Wim Leers’s picture

Title: Remove the ability to configure a block's cacheability » Remove the ability to configure a block's cache contexts
Assigned: Unassigned » Wim Leers
Status: Needs review » Needs work

Discussed 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.

Wim Leers’s picture

Assigned: Wim Leers » Unassigned
Status: Needs work » Needs review
FileSize
39.46 KB
15.1 KB

Done.

Per @Berdir in IRC, he was +1 to changing the default max-age value for BlockBase-based blocks to Cache::PERMANENT (which he also said at the bottom of #16). This allowed me to keep the interdiff smaller.

Berdir’s picture

+++ b/core/modules/node/node.module
@@ -188,15 +188,17 @@ function node_entity_view_display_alter(EntityViewDisplayInterface $display, $co
 function node_title_list(StatementInterface $result, $title = NULL) {

This 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 :)

Wim Leers’s picture

FileSize
39.77 KB
1.06 KB

This function should have died in fire a long time ago :(

:D

That said, shouldn't we also add the list cache tag, for now?

Yes. (The only reason I'm touching it, is because I broke the forum blocks along the way; I already added node_list to ForumBlockBase::getCacheTags().)

Wim Leers’s picture

Issue summary: View changes

IS 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.

Wim Leers’s picture

FileSize
39.79 KB
Fabianx’s picture

+++ b/core/config/schema/core.data_types.schema.yml
@@ -295,12 +295,6 @@ block_settings:
         max_age:
           type: integer
           label: 'Maximum age'

This needs to be max_age due to CMI restrictions / naming scheme right?

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community

This 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.

Wim Leers’s picture

  • catch committed 938e2f6 on
    Issue #2428805 by Wim Leers: Remove the ability to configure a block's...
catch’s picture

Committed/pushed to 8.0.x, thanks!

dawehner’s picture

Status: Reviewed & tested by the community » Fixed

.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.