From d1cd7481a68a09534fdb1b87597163e3f972156f Mon Sep 17 00:00:00 2001
From: Kristiaan Van den Eynde <kristiaan@factorial.io>
Date: Thu, 13 Mar 2025 14:00:30 +0100
Subject: [PATCH 1/4] Delay cache get for placeholdered elements

---
 core/lib/Drupal/Core/Render/Renderer.php      |  9 ++-
 .../FunctionalJavascript/PerformanceTest.php  |  8 +--
 ...nTelemetryAuthenticatedPerformanceTest.php |  5 +-
 .../OpenTelemetryNodePagePerformanceTest.php  | 70 ++++++++++---------
 .../StandardPerformanceTest.php               | 45 ++++++------
 5 files changed, 77 insertions(+), 60 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 1383c4261b43..c295fa5cb1b9 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -276,7 +276,14 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
 
     // Try to fetch the prerendered element from cache, replace any placeholders
     // and return the final markup.
-    if (isset($elements['#cache']['keys'])) {
+    //
+    // If the element was set to create a placeholder, we do not try to load it
+    // from the cache, as \Drupal\Core\Render\Placeholder\CachedStrategy has an
+    // optimization to load them all in one go, rather than individually here.
+    // If the current request method is not cacheable, no placeholders are
+    // generated, in that case, it's still better to fetch render cached
+    // elements individually instead of fully building them again.
+    if (isset($elements['#cache']['keys']) && (empty($elements['#create_placeholder']) || !$this->requestStack->getCurrentRequest()->isMethodCacheable())) {
       $cached_element = $this->renderCache->get($elements);
       if ($cached_element !== FALSE) {
         $elements = $cached_element;
diff --git a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
index d50e419e90ae..aeb053719090 100644
--- a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
+++ b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php
@@ -73,14 +73,14 @@ public function testLogin(): void {
 
     $expected = [
       'QueryCount' => 4,
-      'CacheGetCount' => 60,
+      'CacheGetCount' => 50,
       'CacheGetCountByBin' => [
         'config' => 11,
-        'data' => 5,
+        'data' => 4,
         'discovery' => 10,
         'bootstrap' => 6,
         'dynamic_page_cache' => 2,
-        'render' => 25,
+        'render' => 16,
         'menu' => 1,
       ],
       'CacheSetCount' => 2,
@@ -89,7 +89,7 @@ public function testLogin(): void {
       ],
       'CacheDeleteCount' => 0,
       'CacheTagInvalidationCount' => 0,
-      'CacheTagLookupQueryCount' => 20,
+      'CacheTagLookupQueryCount' => 14,
       'ScriptCount' => 3,
       'ScriptBytes' => 215500,
       'StylesheetCount' => 2,
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php
index 5f7287335adb..8ae0be5e3a8e 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryAuthenticatedPerformanceTest.php
@@ -55,14 +55,15 @@ public function testFrontPageAuthenticatedWarmCache(): void {
         'config' => 22,
         'bootstrap' => 5,
         'discovery' => 5,
-        'data' => 6,
+        'data' => 5,
         'dynamic_page_cache' => 2,
+        'menu' => 1,
         'render' => 2,
       ],
       'CacheSetCount' => 0,
       'CacheDeleteCount' => 0,
       'CacheTagInvalidationCount' => 0,
-      'CacheTagLookupQueryCount' => 3,
+      'CacheTagLookupQueryCount' => 5,
       'ScriptCount' => 2,
       'ScriptBytes' => 123850,
       'StylesheetCount' => 2,
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
index ab531cff959d..c18710cd8151 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
@@ -287,7 +287,7 @@ protected function testNodePageWarmCache(): void {
 
     $expected = [
       'QueryCount' => 171,
-      'CacheGetCount' => 255,
+      'CacheGetCount' => 225,
       'CacheGetCountByBin' => [
         'page' => 1,
         'config' => 66,
@@ -296,14 +296,14 @@ protected function testNodePageWarmCache(): void {
         'data' => 13,
         'entity' => 18,
         'dynamic_page_cache' => 2,
-        'render' => 73,
+        'render' => 43,
         'default' => 3,
         'menu' => 2,
       ],
       'CacheSetCount' => 41,
       'CacheDeleteCount' => 0,
       'CacheTagInvalidationCount' => 0,
-      'CacheTagLookupQueryCount' => 27,
+      'CacheTagLookupQueryCount' => 22,
       'CacheTagGroupedLookups' => [
         [
           'entity_types',
@@ -354,13 +354,44 @@ protected function testNodePageWarmCache(): void {
           'block_view',
           'config:block.block.umami_search',
         ],
-        ['config:block.block.umami_account_menu', 'config:system.menu.account'],
-        ['config:block.block.umami_branding', 'config:system.site'],
-        ['config:block.block.umami_main_menu', 'config:system.menu.main'],
+        [
+          'config:block.block.umami_branding',
+          'config:system.site',
+        ],
         ['config:block.block.umami_messages'],
         ['config:block.block.umami_help'],
         [
+          'block_content:1',
+          'block_content:2',
+          'config:block.block.umami_account_menu',
+          'config:block.block.umami_banner_home',
+          'config:block.block.umami_banner_recipes',
+          'config:block.block.umami_breadcrumbs',
+          'config:block.block.umami_content',
+          'config:block.block.umami_disclaimer',
+          'config:block.block.umami_footer',
+          'config:block.block.umami_footer_promo',
+          'config:block.block.umami_languageswitcher',
+          'config:block.block.umami_local_tasks',
+          'config:block.block.umami_main_menu',
+          'config:block.block.umami_page_title',
+          'config:block.block.umami_views_block__articles_aside_block_1',
+          'config:block.block.umami_views_block__promoted_items_block_1',
           'config:block.block.umami_views_block__recipe_collections_block',
+          'config:block_list',
+          'config:configurable_language_list',
+          'http_response',
+        ],
+        [
+          'config:system.menu.account',
+          'config:system.menu.footer',
+          'block_content_view',
+          'config:core.entity_view_display.block_content.footer_promo_block.default',
+          'config:core.entity_view_display.media.image.medium_8_7',
+          'config:image.style.medium_8_7',
+          'file:37',
+          'media:19',
+          'config:system.menu.main',
           'taxonomy_term:1',
           'taxonomy_term:10',
           'taxonomy_term:11',
@@ -378,33 +409,6 @@ protected function testNodePageWarmCache(): void {
           'taxonomy_term:9',
           'taxonomy_term_list',
         ],
-        [
-          'block_content:2',
-          'block_content_view',
-          'config:block.block.umami_footer_promo',
-          'config:core.entity_view_display.block_content.footer_promo_block.default',
-          'config:core.entity_view_display.media.image.medium_8_7',
-          'config:image.style.medium_8_7',
-          'file:37',
-          'media:19',
-        ],
-        ['config:block.block.umami_footer', 'config:system.menu.footer'],
-        ['config:block.block.umami_disclaimer'],
-        [
-          'block_content:1',
-          'config:block.block.umami_banner_home',
-          'config:block.block.umami_banner_recipes',
-          'config:block.block.umami_breadcrumbs',
-          'config:block.block.umami_content',
-          'config:block.block.umami_languageswitcher',
-          'config:block.block.umami_local_tasks',
-          'config:block.block.umami_page_title',
-          'config:block.block.umami_views_block__articles_aside_block_1',
-          'config:block.block.umami_views_block__promoted_items_block_1',
-          'config:block_list',
-          'config:configurable_language_list',
-          'http_response',
-        ],
         ['config:views.view.recipes'],
         ['config:workflows.workflow.editorial'],
         ['config:user.role.anonymous'],
diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php
index aa8d6dca9bb0..1adbeb26c376 100644
--- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php
+++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php
@@ -127,7 +127,7 @@ protected function testAnonymous(): void {
     $this->assertSame($expected_queries, $recorded_queries);
     $expected = [
       'QueryCount' => 36,
-      'CacheGetCount' => 132,
+      'CacheGetCount' => 121,
       'CacheGetCountByBin' => [
         'page' => 1,
         'config' => 21,
@@ -135,7 +135,7 @@ protected function testAnonymous(): void {
         'discovery' => 38,
         'bootstrap' => 8,
         'dynamic_page_cache' => 2,
-        'render' => 45,
+        'render' => 34,
         'default' => 5,
         'entity' => 2,
         'menu' => 2,
@@ -171,16 +171,16 @@ protected function testAnonymous(): void {
           'config:block.block.stark_search_form_narrow',
           'config:search.settings',
         ],
-        ['config:block.block.stark_main_menu', 'config:system.menu.main'],
         ['config:block.block.stark_search_form_wide'],
-        ['config:block.block.stark_account_menu', 'config:system.menu.account'],
         ['config:block.block.stark_messages'],
         ['config:block.block.stark_help'],
         ['config:block.block.stark_powered'],
         ['config:block.block.stark_syndicate'],
         [
+          'config:block.block.stark_account_menu',
           'config:block.block.stark_breadcrumbs',
           'config:block.block.stark_content',
+          'config:block.block.stark_main_menu',
           'config:block.block.stark_page_title',
           'config:block.block.stark_primary_admin_actions',
           'config:block.block.stark_primary_local_tasks',
@@ -188,6 +188,8 @@ protected function testAnonymous(): void {
           'config:block_list',
           'http_response',
         ],
+        ['config:system.menu.main'],
+        ['config:system.menu.account'],
         ['config:user.role.anonymous'],
       ],
       'StylesheetCount' => 1,
@@ -225,7 +227,7 @@ protected function testAnonymous(): void {
     $this->assertSame($expected_queries, $recorded_queries);
     $expected = [
       'QueryCount' => 10,
-      'CacheGetCount' => 102,
+      'CacheGetCount' => 91,
       'CacheSetCount' => 16,
       'CacheDeleteCount' => 0,
       'CacheTagInvalidationCount' => 0,
@@ -254,17 +256,16 @@ protected function testAnonymous(): void {
           'config:block.block.stark_search_form_narrow',
           'config:search.settings',
         ],
-        ['config:block.block.stark_main_menu', 'config:system.menu.main'],
         ['config:block.block.stark_search_form_wide'],
-        ['config:block.block.stark_account_menu', 'config:system.menu.account'],
         ['config:block.block.stark_messages'],
         ['config:block.block.stark_help'],
         ['config:block.block.stark_powered'],
         ['config:block.block.stark_syndicate'],
-
         [
+          'config:block.block.stark_account_menu',
           'config:block.block.stark_breadcrumbs',
           'config:block.block.stark_content',
+          'config:block.block.stark_main_menu',
           'config:block.block.stark_page_title',
           'config:block.block.stark_primary_admin_actions',
           'config:block.block.stark_primary_local_tasks',
@@ -272,6 +273,8 @@ protected function testAnonymous(): void {
           'config:block_list',
           'http_response',
         ],
+        ['config:system.menu.main'],
+        ['config:system.menu.account'],
         ['config:user.role.anonymous'],
       ],
       'StylesheetCount' => 1,
@@ -306,7 +309,7 @@ protected function testAnonymous(): void {
     $this->assertSame($expected_queries, $recorded_queries);
     $expected = [
       'QueryCount' => 14,
-      'CacheGetCount' => 87,
+      'CacheGetCount' => 76,
       'CacheSetCount' => 17,
       'CacheDeleteCount' => 0,
       'CacheTagInvalidationCount' => 0,
@@ -361,11 +364,11 @@ protected function testLogin(): void {
     $this->assertSame($expected_queries, $recorded_queries);
     $expected = [
       'QueryCount' => 17,
-      'CacheGetCount' => 83,
+      'CacheGetCount' => 69,
       'CacheSetCount' => 1,
       'CacheDeleteCount' => 1,
       'CacheTagInvalidationCount' => 0,
-      'CacheTagLookupQueryCount' => 17,
+      'CacheTagLookupQueryCount' => 14,
       'CacheTagGroupedLookups' => [
         // Form submission and login.
         [
@@ -403,20 +406,22 @@ protected function testLogin(): void {
           'config:search.settings',
         ],
         ['config:system.menu.account', 'config:system.menu.main'],
-        ['config:block.block.stark_main_menu'],
         ['config:block.block.stark_search_form_wide'],
-        ['config:block.block.stark_account_menu'],
-        ['config:block.block.stark_breadcrumbs'],
-        ['config:block.block.stark_primary_admin_actions'],
         ['config:block.block.stark_messages'],
+        ['config:block.block.stark_help'],
+        ['config:block.block.stark_powered'],
+        ['config:block.block.stark_syndicate'],
+        ['config:block.block.stark_main_menu'],
+        [
+          'config:block.block.stark_account_menu',
+          'config:block.block.stark_breadcrumbs',
+          'config:block.block.stark_primary_admin_actions',
+        ],
         [
           'config:block.block.stark_primary_local_tasks',
           'config:user.role.authenticated',
+          'config:block.block.stark_secondary_local_tasks'
         ],
-        ['config:block.block.stark_secondary_local_tasks'],
-        ['config:block.block.stark_help'],
-        ['config:block.block.stark_powered'],
-        ['config:block.block.stark_syndicate'],
       ],
     ];
     $this->assertMetrics($expected, $performance_data);
@@ -473,7 +478,7 @@ protected function testLoginBlock(): void {
       'CacheSetCount' => 1,
       'CacheDeleteCount' => 1,
       'CacheTagInvalidationCount' => 0,
-      'CacheTagLookupQueryCount' => 19,
+      'CacheTagLookupQueryCount' => 20,
     ];
     $this->assertMetrics($expected, $performance_data);
   }
-- 
GitLab


From 4c8546ff86ba2df3101a79e6f1d4d44c85ad2fee Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Sat, 29 Mar 2025 12:18:35 +0100
Subject: [PATCH 2/4] phpcs

---
 .../tests/src/FunctionalJavascript/StandardPerformanceTest.php  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php
index 1adbeb26c376..920c2f524d99 100644
--- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php
+++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php
@@ -420,7 +420,7 @@ protected function testLogin(): void {
         [
           'config:block.block.stark_primary_local_tasks',
           'config:user.role.authenticated',
-          'config:block.block.stark_secondary_local_tasks'
+          'config:block.block.stark_secondary_local_tasks',
         ],
       ],
     ];
-- 
GitLab


From e06778ca00900488b922418d2131de30b2e19e51 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 31 Mar 2025 14:10:54 +0100
Subject: [PATCH 3/4] Only skip fetching from cache when is_root_call is true.

---
 core/lib/Drupal/Core/Render/Renderer.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index c295fa5cb1b9..c1f3a3f9450d 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -280,10 +280,12 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // If the element was set to create a placeholder, we do not try to load it
     // from the cache, as \Drupal\Core\Render\Placeholder\CachedStrategy has an
     // optimization to load them all in one go, rather than individually here.
+    // This is only skipped when $is_root_call is false since placeholder
+    // strategies do not run at every level of a render array.
     // If the current request method is not cacheable, no placeholders are
     // generated, in that case, it's still better to fetch render cached
     // elements individually instead of fully building them again.
-    if (isset($elements['#cache']['keys']) && (empty($elements['#create_placeholder']) || !$this->requestStack->getCurrentRequest()->isMethodCacheable())) {
+    if (isset($elements['#cache']['keys']) && (empty($elements['#create_placeholder']) || !$this->requestStack->getCurrentRequest()->isMethodCacheable() || $is_root_call)) {
       $cached_element = $this->renderCache->get($elements);
       if ($cached_element !== FALSE) {
         $elements = $cached_element;
-- 
GitLab


From 9b89758be23b75eda894dbc15e124476208f7b10 Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Mon, 31 Mar 2025 15:05:42 +0100
Subject: [PATCH 4/4] Revert "Only skip fetching from cache when is_root_call
 is true."

This reverts commit e06778ca00900488b922418d2131de30b2e19e51.
---
 core/lib/Drupal/Core/Render/Renderer.php | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index c1f3a3f9450d..c295fa5cb1b9 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -280,12 +280,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // If the element was set to create a placeholder, we do not try to load it
     // from the cache, as \Drupal\Core\Render\Placeholder\CachedStrategy has an
     // optimization to load them all in one go, rather than individually here.
-    // This is only skipped when $is_root_call is false since placeholder
-    // strategies do not run at every level of a render array.
     // If the current request method is not cacheable, no placeholders are
     // generated, in that case, it's still better to fetch render cached
     // elements individually instead of fully building them again.
-    if (isset($elements['#cache']['keys']) && (empty($elements['#create_placeholder']) || !$this->requestStack->getCurrentRequest()->isMethodCacheable() || $is_root_call)) {
+    if (isset($elements['#cache']['keys']) && (empty($elements['#create_placeholder']) || !$this->requestStack->getCurrentRequest()->isMethodCacheable())) {
       $cached_element = $this->renderCache->get($elements);
       if ($cached_element !== FALSE) {
         $elements = $cached_element;
-- 
GitLab