Problem/Motivation

When a site has lots of blocks that have context definitions, loading the blocks from cache (and not necessarily doing anything with them) is expensive. It's a cost that is paid any time the block list is accessed from cache. This is because in __wakeup when blocks are being deserialized, a compatibility layer is instantiated.

Proposed resolution

Instead load the compatibility layer on demand when you try to actually do something with a given block plugin.

Before:

After:

Comparison:

Remaining tasks

Commit!

User interface changes

None.

API changes

None.

Data model changes

None.

Release notes snippet

Comments

Sam152 created an issue. See original summary.

sam152’s picture

I'm not quite sure what the fix for this is. Perhaps the deriver could only create blocks for entity types that had layout builder enabled, or perhaps there is a way to __wakeup a context definition without calling initializeEntityContextDefinition?

sam152’s picture

I have spun off #3043330: Reduce the number of field blocks created for entities (possibly to zero) to look into reducing the size of the block plugin list.

tim.plunkett’s picture

Issue tags: +Blocks-Layouts
sam152’s picture

tedbow’s picture

Status: Active » Needs review
StatusFileSize
new4.11 KB

Not sure if this will work but we didn't call \Drupal\Core\Plugin\Context\ContextDefinition::initializeEntityContextDefinition() until we actually need to access entityContextDefinition internally.

This is will break \Drupal\Tests\Core\Plugin\Context\EntityContextDefinitionDeprecationTest::testSerialization() because it assumes entityContextDefinition will be set as soon as the object is constructed.

Interested to see what else it breaks.

Status: Needs review » Needs work

The last submitted patch, 6: 3043087-6.patch, failed testing. View results

tedbow’s picture

@Sam152 could you run your profiling with this patch and see if it helps at all?

sam152’s picture

Issue summary: View changes
StatusFileSize
new93.18 KB
new65.51 KB
new31.94 KB

That's a dramatic speed-up, blackfire comparison here. Looks like a 20% speed-up of the whole page when clicking the "Add block" button.

Pulling out the relevant parts from the profiler, before:

After:

Comparison:

sam152’s picture

I think a suitable fix for the test case might simply be to add a call to getConstraints. The test is using reflection, so I don't think it's testing a public API. Asserting it's there after triggering an API call seems appropriate:

--- a/core/tests/Drupal/Tests/Core/Plugin/Context/EntityContextDefinitionDeprecationTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/Context/EntityContextDefinitionDeprecationTest.php
@@ -80,6 +80,7 @@ protected function setUp() {
    */
   public function testSerialization() {
     $definition = unserialize(serialize($this->definition));
+    $definition->getConstraints();
     $bc_layer = $this->compatibilityLayer->getValue($definition);
     $this->assertInstanceOf(EntityContextDefinition::class, $bc_layer);
tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new4.84 KB
new1.21 KB

Yep here is #10 added

tim.plunkett’s picture

Title: Retrieving block plugins with context definitions from cache is expensive, which is noticeable when used with layout builder's FieldBlockDeriver » Retrieving block plugins with context definitions from cache is expensive, which is noticeable when used with Layout Builder's FieldBlockDeriver
Status: Needs review » Reviewed & tested by the community

That is significant, well spotted @Sam152 and elegant fix @tedbow!

sam152’s picture

Good stuff, thanks for the review and fix!

tedbow’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new4.88 KB
new621 bytes
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php
@@ -444,14 +438,15 @@ public function __wakeup() {
-      ->setConstraints($this->getConstraints())
...
+        ->setDataType($this->getDataType())
+        ->setLabel($this->getLabel())
+        ->setRequired($this->isRequired())
+        ->setMultiple($this->isMultiple())
+        ->setDescription($this->getDescription())
+        ->setDefaultValue($this->getDefaultValue());

I removed the call to setConstraints() because it would cause an infinite loop call to initializeEntityContextDefinition()

I thought it didn't matter but realized the object could have non-empty $constraints.

I think the solution is
->setConstraints($this->constraints) to avoid the infinite loop.

sam152’s picture

Does the fix in #14 need a test? I had a play and this seems enough to ensure the constraints are added during initialization:

diff --git a/core/tests/Drupal/Tests/Core/Plugin/Context/EntityContextDefinitionDeprecationTest.php b/core/tests/Drupal/Tests/Core/Plugin/Context/EntityContextDefinitionDeprecationTest.php
index 8053a3d669..fc861e8c62 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/Context/EntityContextDefinitionDeprecationTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/Context/EntityContextDefinitionDeprecationTest.php
@@ -79,8 +79,9 @@ protected function setUp() {
    * @expectedDeprecation Constructing a ContextDefinition object for an entity type is deprecated in Drupal 8.6.0. Use Drupal\Core\Plugin\Context\EntityContextDefinition instead. See https://www.drupal.org/node/2976400 for more information.
    */
   public function testSerialization() {
+    $this->definition->addConstraint('foo');
     $definition = unserialize(serialize($this->definition));
-    $definition->getConstraints();
+    $this->assertEquals(['foo' => NULL], $definition->getConstraints());
     $bc_layer = $this->compatibilityLayer->getValue($definition);
     $this->assertInstanceOf(EntityContextDefinition::class, $bc_layer);
   }

Beyond that, looks ready to me.

tedbow’s picture

StatusFileSize
new1.09 KB
new5.2 KB

@Sam152 good idea about the test

sam152’s picture

Status: Needs review » Reviewed & tested by the community

Good stuff, back to RTBC.

tim.plunkett’s picture

Title: Retrieving block plugins with context definitions from cache is expensive, which is noticeable when used with Layout Builder's FieldBlockDeriver » Retrieving plugins with entity context definitions from cache is expensive, which is noticeable when used with Layout Builder's FieldBlockDeriver
Issue tags: +Needs issue summary update

Deserves a fleshed out issue summary

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 16: 3043087-16.patch, failed testing. View results

sam152’s picture

Issue summary: View changes
Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs issue summary update

Fail was a fluke. Updating issue summary.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 16: 3043087-16.patch, failed testing. View results

tedbow’s picture

Status: Needs work » Reviewed & tested by the community

#21 was JS testing fail error in Drupal\Tests\media_library\FunctionalJavascript\MediaLibraryTest

Retesting

  • larowlan committed 506d92f on 8.8.x
    Issue #3043087 by tedbow, Sam152: Retrieving plugins with entity context...

  • larowlan committed c6171b9 on 8.7.x
    Issue #3043087 by tedbow, Sam152: Retrieving plugins with entity context...
larowlan’s picture

Version: 8.8.x-dev » 8.7.x-dev
Status: Reviewed & tested by the community » Fixed

Committed 506d92f and pushed to 8.8.x. Thanks!

c/p as c6171b948c to 8.7.x

Status: Fixed » Closed (fixed)

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