Problem/Motivation

#2232375: Support CacheOptionalInterface in BlockViewBuilder, use it in language switcher block to prevent max-age 0 bubbling introduces support for blocks to implement said interface, in that issue just for the language switcher.

I think there are a number of blocks that could benefit from this as well, where we can safely assume that rendering them is faster than fetching it from the cache backend.

Essentially blocks that just render out straight markup, with minimal to no theming and no IO.

Such as \Drupal\system\Plugin\Block\SystemPoweredByBlock and \Drupal\system\Plugin\Block\SystemMessagesBlock probably as well.

This is similar to #2935804: Add cache context exclusion list to RenderCache::set() except it's not cache context based but more explicit.

Proposed resolution

Add the interface.

Remaining tasks

Figure out how to decide to which blocks we want to add it.

User interface changes

None.

API changes

Issue fork drupal-3516051

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

berdir created an issue. See original summary.

berdir’s picture

Status: Needs review » Postponed
catch’s picture

Not the same thing but given they both affect SystemMessagesBlock adding the placeholder strategy denylist issue too - both are based on the system messages block being extremely cheap to render overall.

berdir’s picture

Status: Postponed » Active

berdir’s picture

Status: Active » Needs work

Added it to powered, messages and also page title at first but reverted that again because page title is already not cached further up, so this has no meaning there, we never get so far.

We'll want to review render times vs cache load times for this. I did some tests before around this. Performance tests are of course "happy", as in fewer cache lookups. But they don't measure if it's actually a good thing in terms of memory/cpu, at least not as assertions.

berdir’s picture

Component: language system » block.module
Priority: Major » Normal

Tempted to won't fix this. I profiled this in two ways, and both show that the cached blocks are actually faster even for something as basic as the powered by block. what I didn't account for is that each block also includes a block template and the full twig rendering around that.

I tested this with the following script that I executed with drush scr:


use Drupal\block\Entity\Block;

$block = Block::load($extra[0]);

$renderer = \Drupal::service('renderer');
$view_builder = \Drupal::entityTypeManager()->getViewBuilder('block');
foreach(range(1, 1000) as $i) {
  $build = $view_builder->view($block);
  $renderer->renderPlain($build);
}

With xhprof, Drupal\Core\Render\Renderer::doRender takes around 420ms for a cached block such as olivero_site_branding. and 650ms for a non-cached block (olivero_powered). But the profiling overhead needs to be considered.

I also tried out https://github.com/sharkdp/hyperfine, which is why I did this as a CLI script. Saw a video about that recently, it's pretty neat as it can compare multiple runs with parameters, automatically calculates averages and accounts for variation. That gave me this output:

Benchmark 1: ddev drush scr local/cached_block.php -- olivero_powered
  Time (mean ± σ):     591.7 ms ±  12.6 ms    [User: 79.6 ms, System: 18.2 ms]
  Range (min … max):   570.3 ms … 611.2 ms    10 runs

Benchmark 2: ddev drush scr local/cached_block.php -- olivero_messages
  Time (mean ± σ):     638.9 ms ±  16.4 ms    [User: 80.4 ms, System: 17.0 ms]
  Range (min … max):   619.9 ms … 669.6 ms    10 runs

Benchmark 3: ddev drush scr local/cached_block.php -- olivero_help
  Time (mean ± σ):     513.1 ms ±   6.6 ms    [User: 80.4 ms, System: 16.5 ms]
  Range (min … max):   499.8 ms … 523.3 ms    10 runs

Benchmark 4: ddev drush scr local/cached_block.php -- olivero_site_branding
  Time (mean ± σ):     514.2 ms ±  12.0 ms    [User: 78.3 ms, System: 17.8 ms]
  Range (min … max):   502.5 ms … 536.8 ms    10 runs

Summary
  ddev drush scr local/cached_block.php -- olivero_help ran
    1.00 ± 0.03 times faster than ddev drush scr local/cached_block.php -- olivero_site_branding
    1.15 ± 0.03 times faster than ddev drush scr local/cached_block.php -- olivero_powered
    1.25 ± 0.04 times faster than ddev drush scr local/cached_block.php -- olivero_messages

So, the two cached blocks are the same, which makes sense, and faster than powered. The script might not 100% reflect real world usage, the renderPlain() for example also already does the lazy builder within the messages block, which is why that is slower, but I don' think it's that far off.

What I did just notice is that I did run this with redis. I repeated it without redis and got this:

Benchmark 1: ddev drush scr local/cached_block.php -- olivero_powered
  Time (mean ± σ):     572.8 ms ±   7.9 ms    [User: 78.5 ms, System: 19.5 ms]
  Range (min … max):   562.8 ms … 586.4 ms    10 runs

Benchmark 2: ddev drush scr local/cached_block.php -- olivero_messages
  Time (mean ± σ):     626.2 ms ±  13.8 ms    [User: 79.4 ms, System: 17.7 ms]
  Range (min … max):   604.7 ms … 647.8 ms    10 runs

Benchmark 3: ddev drush scr local/cached_block.php -- olivero_help
  Time (mean ± σ):     557.0 ms ±   8.7 ms    [User: 79.6 ms, System: 15.8 ms]
  Range (min … max):   547.0 ms … 568.9 ms    10 runs

Benchmark 4: ddev drush scr local/cached_block.php -- olivero_site_branding
  Time (mean ± σ):     550.6 ms ±  13.6 ms    [User: 77.9 ms, System: 17.8 ms]
  Range (min … max):   532.7 ms … 575.9 ms    10 runs

Summary
  ddev drush scr local/cached_block.php -- olivero_site_branding ran
    1.01 ± 0.03 times faster than ddev drush scr local/cached_block.php -- olivero_help
    1.04 ± 0.03 times faster than ddev drush scr local/cached_block.php -- olivero_powered
    1.14 ± 0.04 times faster than ddev drush scr local/cached_block.php -- olivero_messages

In other words, it's actually redis (2.x) that gives the cached blocks the clear advantage, and it's pretty equal with the database backend, but I'm still not sure that makes sense then. That backs my results in https://www.md-systems.ch/en/blog/2025-09-12/performance-and-maintenance.... If the cache is slower than in my scenario with ddev, that might change things again? RedisBackend::get() only makes up 8% of the total time.

I also tested with asserts on and off and it didn't seem to make a meaningful difference.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.