Problem/Motivation

#pre_render + #cache is great, but has one big disadvantage:

Items in listings (300 comments, 20 blocks) are gotten one item at a time.

That means 300 cache_get instead of one cache_get_multiple().

The previous block cache did a get_multiple per region and hence was faster.

Proposed resolution

Add a cache pre-fetching foreach () loop on all children to Renderer::render().

Possibly set the pre-warmed data on an internal #cache_data attribute temporarily and remove after the #cache check again.

Remaining tasks

- Write a patch

User interface changes

- None

API changes

- None, no API change at all. Pure performance optimization.

--

This is major because of the performance impact. It likely is also a pretty simple quick fix.

Issue fork drupal-2453945

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

wim leers’s picture

Are you sure that this is a regression? The block cache was removed in #2158003: Remove Block Cache API in favor of blocks returning #cache with cache tags, and interestingly, no mention of "get multiple" — neither in the comments nor the patch.

wim leers’s picture

That being said, +100 to this. But since this is something that can be done at any point during the release cycle, I think we should prefer to move this to 8.1, to keep focus on the perf issues that cannot be done after 8.0, because they require API changes.

fabianx’s picture

From https://api.drupal.org/api/drupal/modules%21block%21block.module/functio...

 // Proceed to loop over all blocks in order to compute their respective cache
 // identifiers; this allows us to do one single cache_get_multiple() call
 // instead of doing one cache_get() call per block.
[...]
  if ($cacheable) {
  [...]
    if ($cids) {
      // We cannot pass $cids in directly because cache_get_multiple() will
      // modify it, and we need to use it later on in this function.
      $cid_values = array_values($cids);
      $cached_blocks = cache_get_multiple($cid_values, 'cache_block');
    }
  }

( I only know this as I re-implemented a lot of the logic in render_cache module and I was especially trying to not make performance worse than core's block cache).

Actually that hunk was already gone at that time so that patch is not to blame ...

This was even gone already when #918808: Standardize block cache as a drupal_render() #cache got in 4 years ago.

And the reason is that Drupal 8 never had this change to cache_get_multiple().

It is nowhere in the repo, this is a port fail, but so probably indeed not a regression:

#1956914: Use a single cache_get_multiple() call per region instead of a cache_get() per block in _block_render_blocks() introduced this for Drupal 7.

wim leers’s picture

Ah, that'd explain it indeed (that it was already gone before).

Nevertheless, as I said, very much agree that we should do this! But… we can actually go even further.
So far, we're saying that we should look at the children of the current level, see which ones have CIDs, and do a ::getMultiple() on that. That's true. But we can go further than that: instead of looking only at the children, for every child that does not have a CID, we can actually descend further down until we hit a level in the render tree again that has CIDs.
In other words: for each child, the first encountered level that has a CID.

Not sure if the extra traversing necessary would offset the advantage of doing an even bigger ::getMultiple() though.

fabianx’s picture

Title: Performance Regression: #cache + #pre_render is slower than previous block cache » #cache + #pre_render is slower than block cache in Drupal 7 now
Related issues: +#1956914: Use a single cache_get_multiple() call per region instead of a cache_get() per block in _block_render_blocks()
berdir’s picture

Noticed a lot of cache get calls from the Renderer as well, I guess this would then also apply to things like lists of nodes in a view in the same way?

Another way to address this might be to try and render cache complete regions if all blocks in a region can be cached? I started implementation something like that for #2344073: Support block caching, which I will have to update for the max-age and cache context changes now, where I automatically cache the whole page display if every block inside it can be cached. Separate issue probably, though

fabianx’s picture

Yes, caching larger regions (also smartcache) definitely helps, but this should still be fixed.

Yes, lists of nodes in a view would probably be the same (if they set #cache render arrays).

catch’s picture

Title: #cache + #pre_render is slower than block cache in Drupal 7 now » Use multiple get for #pre_render / #cache where possible

Re-titling. This is definitely worth trying.

wim leers’s picture

wim leers’s picture

Since this wouldn't require any API changes, keeping as major.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

smccabe’s picture

Trying to pick this up but notes are a little outdated and my D8 caching knowledge isn't amazing, is the correct idea to add in a getMultiple to renderCache and add to either the Renderer:doRender or Renderer:render. A little nudge in the right direction would be much appreciated.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mxh’s picture

Issue tags: -Quick fix

A major issue with "Quick fix" tag. Something is wrong here, guess this isn't something to quickly fix.
This one is not just a performance one, it would drastically reduce IO between PHP and Cache backend.

ndobromirov’s picture

Issue tags: +scalability

Based on previous comment.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Render cache multiple get is now in core after #3493911: Add a CachedPlaceholderStrategy to optimize render cache hits and reduce layout shift from big pipe, we should see if there's an example in core where this would help, and then try to implement it in the renderer.

berdir’s picture

The combination of those 4 issues means we now have efficient get multiple cache loading for placeholdered routes and we have quite a few of those now with menus, block_content, local tasks and so on being placeholdered:

#3493911: Add a CachedPlaceholderStrategy to optimize render cache hits and reduce layout shift from big pipe
#3512762: Optimize placeholder retrieval from cache
#3512766: Optimize redirect chain retrieval in VariationCache
#3437499: Use placeholdering for more blocks

So I think the benefits for blocks aren't that huge, you'd need multiple non-placeholdered blocks in the same region for this to be efficient. We might want to instead look into either placeholder even more blocks or then not cache them on their own at all ##3516051: Add CacheOptionalInterface to more blocks ).

Looking at umami, the only blocks that are not placeholdered are: umami_search (simple form, pretty fast to build), umami_branding (also pretty simple), umami_messages (just puts in a placeholder, definitely could be non-cacheable?) and umami_help (unsure, but depends on route, so high amount of variations). So, not a lot of benefit here.

But I think one scenario where this is still interesting is what I mentioned #6. If you have a view with 50 nodes that gets invalidated, then you can fetch them with a single cache get instead of 50 (with cache redirects, possibly even 2 instead of 100). To measure the benefit, we'd need to create a handful of articles for the frontpage view.

We'd need to run the children through getMultiple() and somehow flag those that have been fetched, so we don't attempt to load them again.

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.