Problem/Motivation

BlockRepository::getVisibleBlocksPerRegion() does an uncached config entity query on every page.

Proposed resolution

Cache the query per-theme.

Remaining tasks

Fix the patch - just did this quickly to profile, needs cache tags etc.

Also determine whether the blocks are loaded elsewhere in the request - we may want to cache just the query and do a getMultiple on the config entities so they don't get loaded twice.

CommentFileSizeAuthor
#107 with-patch.png146.69 KBandyg5000
#107 without-patch.png139.36 KBandyg5000
#83 block-repository-caching-2479459-83.patch10.07 KBsnehi
#83 interdiff.txt3.19 KBsnehi
#77 block-repository-caching-2479459-77-interdiff.txt4.91 KBBerdir
#77 block-repository-caching-2479459-77.patch10.09 KBBerdir
#74 block-repository-caching-2479459-74-interdiff.txt1.66 KBBerdir
#74 block-repository-caching-2479459-74.patch7.01 KBBerdir
#70 block-repository-caching-2479459-70.patch6.43 KBBerdir
#53 2479459.53.patch24.43 KBalexpott
#53 51-53-interdiff.txt1.37 KBalexpott
#51 2479459.51.patch24.37 KBalexpott
#51 44-51-interdiff.txt27.1 KBalexpott
#44 2479459_0.patch12.03 KBcatch
#40 2479459.patch12.02 KBcatch
#40 interdiff.txt1.12 KBcatch
#38 Screen Shot 2015-06-08 at 4.21.30 PM.png170.31 KBcatch
#37 Screen Shot 2015-06-01 at 6.16.27 PM.png113.93 KBcatch
#37 Screen Shot 2015-06-01 at 6.14.40 PM.png130.48 KBcatch
#36 2479459-36.patch11.89 KBstar-szr
#31 2479459-31-interdiff.txt517 bytesBerdir
#31 2479459-31.patch12.04 KBBerdir
#23 histogram_interleaved.png6.71 KBWim Leers
#23 histogram_facet.png5.6 KBWim Leers
#23 ab runs.zip7.59 KBWim Leers
#15 interdiff.txt3.79 KBtim.plunkett
#15 2479459-15.patch11.95 KBtim.plunkett
#13 interdiff.txt6.77 KBtim.plunkett
#13 2479459-13.patch8.16 KBtim.plunkett
#11 block-repository-cache-2479459-11.patch3.07 KBBerdir
#1 2479459.patch2.89 KBcatch
after.png91.08 KBcatch
before.png87.8 KBcatch
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

catch’s picture

FileSize
2.89 KB
anavarre’s picture

Issue tags: +D8 cacheability, +Performance
dawehner’s picture

I'm curious, how much of the actual request do we talk about? I see 25ms gain, which is a LOT, but especially a lot from the measured 100ms we had ...

catch’s picture

No the loadByProperties() call is showing up as ~3% of the request for me.

Also the 25ms gain I think is a result of different profiling runs (unless somehow the access check also gets cheaper...) - this is more like 8-12ms (still not bad, and could be a larger saving with more blocks configured).

catch’s picture

dawehner’s picture

Yeah I guess we would need a block_list tag and invalidate that on save time.

alexpott’s picture

Adding some related issues. I think we should explore whether or not we can make config entity retrieval faster.

Berdir’s picture

We have a config:block_list cache tag, that's the list cache tag of block config entities.

Yes, I've seen this too in profiling, obviously, this will get slower the more blocks you have in the system, as we need to load them all. I still think it was a bad idea to remove the forced theme ID prefix like fields have for example from blocks. We have optimizations in place for config entity queries on (partial) ID matches, that would be much faster.

Berdir’s picture

Status: Needs work » Needs review
FileSize
3.07 KB

This adds the list cache tag, at least one test was fixed by this, let's see about others.

tim.plunkett’s picture

FileSize
8.16 KB
6.77 KB

Fixing the unit test and adding coverage for the change.

tim.plunkett’s picture

Status: Needs work » Needs review

Working on the \Drupal\block\Tests\BlockSystemBrandingTest fails.

tim.plunkett’s picture

FileSize
11.95 KB
3.79 KB

Oooh, bad test, modifying config directly like that...
Nice to know that using the correct API fixes the problem :)

Berdir’s picture

+++ b/core/modules/block/block.services.yml
@@ -24,4 +24,4 @@ services:
   block.repository:
     class: Drupal\block\BlockRepository
-    arguments: ['@entity.manager', '@theme.manager', '@context.handler']
+    arguments: ['@entity.manager', '@theme.manager', '@context.handler', '@cache.data']
diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php

Entities are usually in the entity bin, is there a reason that data was used here?

dawehner’s picture

IMHO this code is looking great, especially because the approach is simple, understandable etc. especially compared to the other suggestions out there.

catch’s picture

I put it in @cache.data because:

1. It's caching based on the query result - not entity IDs or anything.
2. A cache item with lots of entities in it is a bit different from a 1-1 entity cache

That felt like data more than entity to me, but it's definitely borderline.

Berdir’s picture

Status: Needs review » Reviewed & tested by the community

Knowing you thought about it and decided for cache.data works for me.

I just added the cache tag, it has been reviewed by others already. so I think it's OK if I RTBC this.

Agreed with @dawehner: A nice performance improvement that is very simple in itself, thanks to cache tags :)

Wim Leers’s picture

Patch looks great; doing a quick round of benchmarking.

Wim Leers’s picture

Issue summary: View changes
FileSize
7.59 KB
5.6 KB
6.71 KB

My benchmarking (as UID 2, frontpage) shows that this doesn't actually gain us much. The IS has nice before vs. after XHProf screenshots, but they only look at BlockRepository::getVisibleBlocksPerRegion(), not the overall request. My benchmark results suggest that the majority of the cost was actually in initializing some things, which are now just being initialized later/elsewhere.


Before
Requests per second:    12.39 [#/sec] (mean)
Time per request:       80.735 [ms] (mean)
Time per request:       80.735 [ms] (mean, across all concurrent requests)
Transfer rate:          147.23 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:    75   81   1.5     81      89
Waiting:       67   71   1.4     71      78
Total:         75   81   1.5     81      89
After
Requests per second:    12.38 [#/sec] (mean)
Time per request:       80.797 [ms] (mean)
Time per request:       80.797 [ms] (mean, across all concurrent requests)
Transfer rate:          147.12 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:    74   81   1.6     81      95
Waiting:       66   72   1.5     71      86
Total:         74   81   1.6     81      96
Conclusion
In any case, I cannot see the big gain described in the IS.
Wim Leers’s picture

Status: Reviewed & tested by the community » Needs review

Back to NR per #23.

Wim Leers’s picture

Looking at it with XHProf:

  1. 40848 -> 40040 function calls (minus 808)
  2. \Drupal\block\BlockRepository::getVisibleBlocksPerRegion() remains around 8-9 ms, because unlike catch, the loading of the entities only took 1.5-2.5 ms on my machine, the cache get takes 0.75 ms.

That, combined with some code being loaded later in the request, is probably why I'm not seeing a performance improvement.

This may still be worth it for the reduction in number of function calls, but in practice, the gain is very small in any case.

Wim Leers’s picture

Note: I tested with the Standard install profile, catch tested with Minimal.

catch’s picture

So doing an xhprof diff of the whole request, I see 7ms less from that function but only 3.5ms less from the request overall in wall time, and -397 function calls. I'm testing minimal profile logged out on the front page and I think Wim is testing standard which partly explains the function call difference.

One thing that might offset the improvement from the caching here is that we're moving from APCu/chainedfast for cache.config to database for cache.data - putting this in a chained backend would probably make more sense.

Berdir’s picture

Try it with 20, 30, 40 blocks and enable a few different themes.

I didn't expect a huge gain in profiling, and certainly not with a default installation.

Instead of execution time, try to compare the amount of database queries. I'd expect we save at least the query on the config table there, and we have a single cache get instead of a cache get multiple. But as @catch said, the downside is that those cache gets are currently in APC, while this would be the database.

Wim Leers’s picture

One thing that might offset the improvement from the caching here is that we're moving from APCu/chainedfast for cache.config to database for cache.data - putting this in a chained backend would probably make more sense.

+1

catch’s picture

So do we think cache.config would be acceptable here? It is more or less the reverse of the argument I had for cache.data.

Berdir’s picture

FileSize
12.04 KB
517 bytes

Works for me.

In fact, I think we should consider that config entity caching should use the cache_config table as well instead of cache_entity, for the exact same reason. We do use that for roles for example...

Even if this doesn't have a big impact on performance, I think we should still go ahead with this. We are definitely saving queries now, and that is worth it IMHO. The impact of this will likely be more visible on sites under load. queries are always very fast when testing with ab, because then the query caches are warm, that's not always the case.

Berdir’s picture

Oh, I'm confused, roles/config entities only use static cache. Forget what I said :)

Berdir’s picture

Issue tags: +needs profiling

Pretty sure this needs to be profiled again after #2430219: Implement a key value store to optimise config entity lookups. Should be quite a bit faster now in HEAD, especially if you have many blocks.

Berdir queued 31: 2479459-31.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 31: 2479459-31.patch, failed testing.

star-szr’s picture

Status: Needs work » Needs review
FileSize
11.89 KB

(In theory) Easy rebase, git auto merged it :)

catch’s picture

Profiled again. Still looks like an improvement here - on my machine with xhprof it comes out as ~16ms (just diffing two requests before/after).

Also while the KeyValue lookup is faster than what we used to do, it's still a database query compared to what is an APCu get with the patch.

catch’s picture

And profling again, I'm only seeing this as a 3ms improvement (see attached diff). The overall request comes out as 11ms faster, but the specific code we're looking at I see 3.6ms.

However, that's a database query on every request on every page, and 3ms overall time is still a good improvement on a warm cache.

dawehner’s picture

Issue tags: -needs profiling
+++ b/core/modules/block/src/BlockRepository.php
@@ -74,7 +85,18 @@ public function getVisibleBlocksPerRegion(array $contexts) {
+    if ($cached = $this->cacheBackend->get($cid)) {
+      $blocks = $cached->data;
+    }
+    else {
+      $blocks = $this->blockStorage->loadByProperties(['theme' => $theme]);

Should we document that this gets rid of a keyvalue store and all the overhead of config queries, and so provide a even better way to serve Drupal without a database on a warm cache?

catch’s picture

FileSize
1.12 KB
12.02 KB

Rebased and added those docs.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Thank you

alexpott’s picture

+++ b/core/modules/block/block.services.yml
@@ -24,4 +24,4 @@ services:
+    arguments: ['@entity.manager', '@theme.manager', '@context.handler', '@cache.config']

I'm really uncertain about putting something that's not config in the config cache. Especially just because it is quicker.

Berdir’s picture

It kind of is config :)

We can also use a different cache bin that uses apcu by default. Both discovery and bootstrap would be fine with me.

catch’s picture

FileSize
12.03 KB

Right it's a list of config entities that are the result of a config entity query. Whether that makes it suitable for @cache.config is definitely up for discussion though.

Not sure about discovery since that is more info hook/plugins, but bootstrap is definitely fine.

Here's a patch with just that change, I'm fine with either config or bootstrap.

Wim Leers’s picture

How about adding a @cache.config_query service, to make that distinction clear? Wouldn't that address Alex' concerns, and point D8 contrib in the right direction as well?

alexpott’s picture

#45 makes me think - should we make caching expensive queries part of ConfigEntityStorage and introduce a special version of loadByProperties to do... something like CachedLoadByProperties()

Wim Leers’s picture

#46: But if we make it too easy, won't that fill up APCu too quickly? We want it to be a very very very conscious choice to put things in APCu, I think?

alexpott’s picture

Re #47 thats why I was suggesting a new method.

Wim Leers’s picture

Right, but I'm wondering if that's not still "too easy". But I guess docs of such a new method could give sufficient warning :) So +1 to cachedLoadByProperties().

catch’s picture

So the only concern about that I have compared to the current patch, is this is currently the only config entity query for basic requests (and hopefully it'll stay the only one). Doing the cache where it is now also avoids initializing the config entity query service (which otherwise we could mark lazy).

I do think it's worth exploring though, but possibly in a separate issue which we could then convert the caching added here to?

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
27.1 KB
24.37 KB
+++ b/core/modules/block/src/BlockRepository.php
@@ -55,7 +66,20 @@ public function getVisibleBlocksPerRegion(array $contexts) {
+      $this->cacheBackend->set($cid, $blocks, Cache::PERMANENT, ['config:block_list']);

Not too mad about hard coding 'config:block_list'] here.

Here's a patch that adds cachedLoadByProperties to ConfigEntityStorage and we can still mark the query service as lazy. I'm happy if we choose to split this into a separate issue - if everyone else thinks that is the way to go.

Wim Leers’s picture

I think the patch looks great.


  1. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php
    @@ -88,4 +88,33 @@ public function loadOverrideFree($id);
    +   * Load entities by their property values from cache, if available.
    

    s/Load/Loads/

  2. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php
    @@ -88,4 +88,33 @@ public function loadOverrideFree($id);
    +   * If there is no cache entry available the method falls back to
    

    s/entry/item/

  3. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php
    @@ -88,4 +88,33 @@ public function loadOverrideFree($id);
    +   * Over-use of this method is discouraged since, by default, it stores entries
    

    s/entries in APC/data in APCu, which has a very limited amount of available storage space/

alexpott’s picture

FileSize
1.37 KB
24.43 KB

Thanks for the review @Wim Leers.

Wim Leers’s picture

I wanted to RTBC, but then found my own profiling in #29 that shows the gain is not noticeable. But looks like the much more recent #37 and #38 show a different story.

Do we want more profiling before committing this?

catch’s picture

@Wim #38 I think is pretty accurate (on my machine at least), 16ms was with opcache disabled. Even if there's not a measurable difference profiling, we're swapping a database query for APCu which is good for scalability.

Worth a sanity check profiling.

catch’s picture

Status: Needs review » Reviewed & tested by the community

Just double checked and still seeing approx 3ms improvement. #53 is a really nice approach we can use elsewhere. So RTBC for me.

Wim Leers’s picture

Great :)

effulgentsia’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record, +Needs beta evaluation

This patch looks great. Very nice implementation!

  1. +++ b/core/core.services.yml
    @@ -152,6 +152,12 @@ services:
    +  cache.config_entity_storage:
    

    We don't use the "_storage" suffix for other cache bins, why do so here? Will this bin be intended for caching individual config entities too (if we ever choose to cache those)? If so, perhaps just "config_entity"? If not, perhaps "config_entity_query" or similar?

  2. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
    @@ -111,12 +120,13 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
    -  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
    +  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, CacheBackendInterface $cache_backend) {
    

    Since this will break every contrib module that implements a config entity type with its own storage class that has its own constructor, this could use a change record. Also, a beta evaluation listing that as a (acceptable) disruption. And, an @param doc should be added here and in all the subclasses.

  3. +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
    @@ -856,6 +866,34 @@ public function testDeleteNothing() {
    +    // This to cachedLoadByProperties() will fall through to loadByProperties().
    ...
    +    // This to cachedLoadByProperties() will fall through to loadByProperties().
    

    s/This/This call/?

effulgentsia’s picture

Right, but I'm wondering if that's not still "too easy".

Just brainstorming, feel free to reject, but if we want to make it less easy/accidental, and also avoid the BC break of #58.2, could we put cachedLoadByProperties() (along with a setCacheBackend()) in a trait, and let individual storages opt-in by using the trait (and calling the setter from within their createInstance() method)?

effulgentsia’s picture

along with a setCacheBackend()

Clearly I wasn't thinking straight. There's no reason for the trait to require a setter. It can still be constructor injected. So really, the question is whether it makes sense for cachedLoadByProperties() to be its own interface/trait rather than requiring every subclass to have it and the corresponding $cache_backend dependency. Problem with that kind of opt-in though is that maybe it's the users of a config entity type that should decide if/when to benefit from cachedLoadByProperties() rather than it being the entity type's decision to make. So alternatively, would it make sense to have a separate service (e.g., ConfigEntityQueryCache) rather than baked into the storage classes?

catch’s picture

So I'd be OK with either leaving the method as is, or a new service adding the method.

We want this easy enough to use when it makes sense, but require at least some thought as to when it's used to avoid cache bin explosion. For me either a method or a service does that, it's just what the emphasis is.

effulgentsia’s picture

I'm also OK with either. So this issue is Needs Work on either the CR, beta eval, and @param docs mentioned in #58.2, or on changing the approach to use a service that would make the CR and @param docs unnecessary. Leaving the decision to whoever wants to roll the next patch. If the next re-roll can also improve the name of the cache bin per #58.1 (I think config_entity makes most sense, since I think it'll make sense to use that for individual config entity caching as well, but am open to arguments against that), that would also be great, but not a blocker.

Fabianx’s picture

Berdir’s picture

Doing some profiling on a site.

Caching the loadByProperties() is nice and what not, but in my case, that's just 2ms. However, in the same method, I'm seeing 9ms spent in $block->access() (If you don't see those numbers, try adding a few custom blocks to your site, those are extremely expensive).

The thing is, we actually have the cacheability for those access checks now. What if we just cache the whole method, using that cacheability metadata? In total, that's 11.5ms or 8% on a node page in my case.

Berdir’s picture

Ah, I guess we'd need cache redirects for the contexts to work, which we currently only have for render caching.

effulgentsia’s picture

Does SmartCache fix #64 / #65, or is there still a common scenario even with SmartCache where 5-10% of time is spent on $block->access()?

Berdir’s picture

Yes, smartcache will cache that as well.

The question I guess is how many variations there are. If you have blocks that vary by path then block access needs to be checked by path, just like smartcache and then we don't gain much. Except smartcache might also end up caching per user on many pages.

I've discussed a completely different solution with Wim in IRC. The basic idea is to move the access check to #pre_render. Then we can cache access as part of the normal block render array. There are a few problems with that, though:

a) It would mean to remove access checking from BlockRepository, which is an API change and a tricky one because it could expose security issues in contrib/custom code which used it and isn't updated. We could solve that with a new optional argument to disable access checking? But that would also affect how the whole cacheability stuff works.
b) We need to solve the problem of regions with blocks but no blocks is displayed on a specific page, so they end up empty, that would then break if (region) checks in templates, add empty wrappers and so on.
c) And possibly the most problematic one (only just thought of this again). It would mean to vary the block result by the access cacheablity. If you have a custom block and only show that on a single page, then block access varies by url. And we'd have t' re-check access on every URL and also cache the built block for every URL even though the block output itself never changes.

So... I don't think that's a good solution, actually :) (Writing down things helps)

Wim Leers’s picture

#67

a) Could be solved by keeping the existing API, and just adding a new API.
b) That's #953034: [meta] Themes improperly check renderable arrays when determining visibility. Region checks in templates are broken by design; they're fundamentally incompatible with lazy rendering.
c) This is true. But many blocks aren't shown on a single page, of course. So I'm not sure how big of a problem that'd actually be.

Berdir’s picture

Not sure what you mean with your response to c). It is actually very common to have blocks that are only shown on certain pages through the path visibility condition. I can totally imagine sites with dozens of those blocks that are shown on special pages (e.g. a contact form or a certain node) and we have to be able to handle that in an efficient way. And node type visibility condition is per route as well.

Berdir’s picture

Here's a pretty ugly but working implementation for this. This could also easily live in a wrapper of the block repository if we decide to not have it in core for now.

Wim Leers’s picture

I think it's only ugly because it reimplements cache redirection (also incompletely, but I'm sure you know that, you said it's a PoC). Once we'd have #2551419: Abstract RenderCache into a separate service that is capable of cache redirects in a non-render array-specific way, this would be able to just reuse that.

But the principle looks sound to me. This is exactly what I've been saying we should/hoping we would have for a while :)

Berdir’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 70: block-repository-caching-2479459-70.patch, failed testing.

Berdir’s picture

Fix the serialization problem and actually using the cache now. the failing aggregator test passed now.

Status: Needs review » Needs work

The last submitted patch, 74: block-repository-caching-2479459-74.patch, failed testing.

The last submitted patch, 70: block-repository-caching-2479459-70.patch, failed testing.

Berdir’s picture

Fixing those test fails.

Wim Leers’s picture

Regarding #71: Berdir convinced me that we shouldn't do #2551419: Abstract RenderCache into a separate service that is capable of cache redirects in a non-render array-specific way first; that it's better to first have multiple implementations and then do #2551419.

Wim Leers’s picture

Status: Needs review » Needs work
Issue tags: +needs profiling, +Needs documentation

This looks very close! :)

  1. +++ b/core/modules/block/src/BlockRepository.php
    @@ -52,10 +86,22 @@ public function __construct(EntityManagerInterface $entity_manager, ThemeManager
    +        $cacheable_metadata = $cache_redirect->data['cacheable_metadata'];
    

    s/cacheable_metadata/cacheability/

  2. +++ b/core/modules/block/src/BlockRepository.php
    @@ -52,10 +86,22 @@ public function __construct(EntityManagerInterface $entity_manager, ThemeManager
    +    $overall_cacheablity_metadata = new CacheableMetadata();
    

    s/cacheablity/cacheability/

    Perhaps: s/overall/page/ is slightly better?

  3. +++ b/core/modules/block/src/BlockRepository.php
    @@ -80,7 +126,38 @@ public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) {
    +    foreach ($cacheable_metadata as $cacheable_metadata_region) {
    

    s/cacheable_metadata/cacheability/

    s/cacheable_metadata_region/region_cacheability/

The last submitted patch, 74: block-repository-caching-2479459-74.patch, failed testing.

The last submitted patch, 77: block-repository-caching-2479459-77.patch, failed testing.

snehi’s picture

Assigned: Unassigned » snehi
Issue tags: +drupalconasia2016
snehi’s picture

Status: Needs review » Needs work

The last submitted patch, 83: block-repository-caching-2479459-83.patch, failed testing.

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.

Wim Leers’s picture

snehi’s picture

Assigned: snehi » Unassigned
EclipseGc’s picture

It's worth pointing out that a TON of the work that this stuff is doing would be dramatically reduced if we had an index of blocks that appear on pages, in other words, PageManager. The blog's example of 140+ blocks on a single site is TOTALLY reasonable, and I've been making this point for literally years now. That's not to say that there's not an incremental improvement to be had by fixing the existing approach, but this is not the final solution. We need to build pages of blocks and execute and cache those pages. Just my opinion.

Eclipse

krlucas’s picture

Elijah Lynn’s picture

https://github.com/vasi/block_access_records

block_access_records
--------------------

Blocks are super powerful in D8. The page title can be a block, messages are a block, contact forms can be a block… This means your site may have dozens or even hundreds of blocks!

Unfortunately, checking which blocks should be visible on a page gets really slow when you have a lot of blocks. Drupal insists on loading all the blocks, then checking each and every one individually. I've seen it take up to 130 ms!

This module implements a fast way of checking, inspired by the node_access system. It generates a single query that does most of the work, and then only loads the blocks that are likely to be visible. On the site mentioned above, it cut over 90 ms off the time taken.

It currently works with all the visibility conditions in Drupal core: path, language, user role, and node type. It's also extensible, so you can add plugins that handler other types of conditions.

Caveats:
* If you have custom conditions, you'll have to extend this module. This module does at least ensure that you don't add conditions that aren't supported.
* Entity or block access hooks are only invoked on blocks whose conditions seem to pass, since we don't want to load other blocks. There's a new hook_block_visibility_alter() so you can add blocks whose conditions failed.
* This is not well tested, so beware of bugs.

There is a core issue about the slowness of core's block visibility checking: https://www.drupal.org/node/2479459 . I'm not sure the solutions there are ideal, because blocks may vary by path, which changes all the time. So this is my attempt at a better solution.

tim.plunkett’s picture

There is a core issue about the slowness of core's block visibility checking: https://www.drupal.org/node/2479459

That... that's this issue :)

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.

joseph.olstad’s picture

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.

Berdir’s picture

Since I'm not sure that caching this wouldn't just end up with a per-user/per-route cache context on all sites seventually, I'm not sure this will actually ever happen.

So I started a new approach to address the main performance issue that I'm seeing with block access checking: #2878673: Cache loadEntityByUuid() calls for block_content

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.

Wim Leers’s picture

#2878673: Cache loadEntityByUuid() calls for block_content is in now. Does that mean this is no longer needed? I think this at least needs a new round of profiling, because #2878673 will cause the numbers to change.

Berdir’s picture

Yes, I'm not sure if this is still needed. Possibly keep it open but change to a normal issue until we have numbers that proof that it is still useful?

The main problem of this is IMHO that quite likely, the cache would end up being per-user per-url and then at that point fairly useless for a lot of cases, especially now with dynamic page cache.

Wim Leers’s picture

Version: 8.4.x-dev » 8.5.x-dev
Category: Bug report » Task
Priority: Major » Normal
Status: Needs work » Active
Issue tags: -Needs change record, -Needs documentation +Needs issue summary update, +Needs issue rescope

Possibly keep it open but change to a normal issue until we have numbers that proof that it is still useful?

👍 Done!

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

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now 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.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now 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.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.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.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). 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.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now 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: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

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

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

andyg5000’s picture

FileSize
139.36 KB
146.69 KB

Hi everyone, I found this issue after noticing a very long running `getVisibleBlocksPerRegion` call in my Blackfire trace. After profiling with and without this patch, I think there's still a need for this. I've included the 2 screenshots of `Blackfire's BlockPageVariant::build`.

This is with Drupal v8.9.16

joseph.olstad’s picture

Priority: Normal » Major
Status: Active » Needs work

patch #83 needs a complete reroll!

Bumping the priority up - In comment #93 I mentioned that this was fixed in the D7 bean module several years ago.

Berdir’s picture

Priority: Major » Normal
Status: Needs work » Active

And #98/99 agreed to downgrade it to normal because we did other improvements, the config entity query is using an index, the content blocks are using a cache for the uuid lookup.

Unless someone shows numbers that makes this worth it, I don't think this as major. And it's a service, so a contrib module could override it and experiment with caching.

This is very different to the D7 bean module, and as pointed out, the cache would likely be basically end up with the same contexts as dynamic page cache.

mglaman’s picture

Just found this again due to #3249710: hook_theme_suggestions_alter needs to statically cache results of visible blocks. Maybe it's a wrong approach, but their code is calling getVisibleBlocksPerRegion about 41x times and costing up to 2s.

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

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.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.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now 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.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now 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: 10.1.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, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.