From 883f7510253210bcaaaed6d92144dc05f9bfc7f5 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Thu, 13 Mar 2025 14:27:17 +0100 Subject: [PATCH 01/25] Update variation cache so chain lookups are stored in memory until the next set, invalidate or delete. --- core/lib/Drupal/Core/Cache/VariationCache.php | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 3e3080b32301..2a210bc7245d 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -12,6 +12,13 @@ */ class VariationCache implements VariationCacheInterface { + /** + * Stores redirect chain lookups until the next SET. + * + * @var array + */ + protected array $redirectChainCache = []; + /** * Constructs a new VariationCache object. * @@ -224,6 +231,7 @@ public function set(array $keys, $data, CacheableDependencyInterface $cacheabili $this->cacheBackend->set($chain_cid, new CacheRedirect($cacheability)); } + unset($this->redirectChainCache[$this->createCacheIdFast($keys, $initial_cacheability)]); $this->cacheBackend->set($cid, $data, $this->maxAgeToExpire($cacheability->getCacheMaxAge()), $optimized_cacheability->getCacheTags()); } @@ -232,6 +240,13 @@ public function set(array $keys, $data, CacheableDependencyInterface $cacheabili */ public function delete(array $keys, CacheableDependencyInterface $initial_cacheability): void { $chain = $this->getRedirectChain($keys, $initial_cacheability); + + // Don't need to delete what could not be found. + if (end($chain) === FALSE) { + return; + } + + unset($this->redirectChainCache[$this->createCacheIdFast($keys, $initial_cacheability)]); $this->cacheBackend->delete(array_key_last($chain)); } @@ -240,6 +255,13 @@ public function delete(array $keys, CacheableDependencyInterface $initial_cachea */ public function invalidate(array $keys, CacheableDependencyInterface $initial_cacheability): void { $chain = $this->getRedirectChain($keys, $initial_cacheability); + + // Don't need to invalidate what could not be found. + if (end($chain) === FALSE) { + return; + } + + unset($this->redirectChainCache[$this->createCacheIdFast($keys, $initial_cacheability)]); $this->cacheBackend->invalidate(array_key_last($chain)); } @@ -261,7 +283,11 @@ public function invalidate(array $keys, CacheableDependencyInterface $initial_ca * to query the cache for that result. */ protected function getRedirectChain(array $keys, CacheableDependencyInterface $initial_cacheability): array { - $cid = $this->createCacheIdFast($keys, $initial_cacheability); + $cid = $initial_cid = $this->createCacheIdFast($keys, $initial_cacheability); + if (isset($this->redirectChainCache[$cid])) { + return $this->redirectChainCache[$cid]; + } + $chain[$cid] = $result = $this->cacheBackend->get($cid); while ($result && $result->data instanceof CacheRedirect) { @@ -269,7 +295,7 @@ protected function getRedirectChain(array $keys, CacheableDependencyInterface $i $chain[$cid] = $result = $this->cacheBackend->get($cid); } - return $chain; + return $this->redirectChainCache[$initial_cid] = $chain; } /** -- GitLab From 1c9f750dac4e04d0c5266ca4d6f0f1515ceb7269 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Thu, 13 Mar 2025 15:25:19 +0100 Subject: [PATCH 02/25] Build in some safety for when cache context values change during a request. --- core/lib/Drupal/Core/Cache/VariationCache.php | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 2a210bc7245d..a8fda8cd8209 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -13,7 +13,7 @@ class VariationCache implements VariationCacheInterface { /** - * Stores redirect chain lookups until the next SET. + * Stores redirect chain lookups until the next set, invalidate or delete. * * @var array */ @@ -40,7 +40,7 @@ public function __construct( */ public function get(array $keys, CacheableDependencyInterface $initial_cacheability) { $chain = $this->getRedirectChain($keys, $initial_cacheability); - return array_pop($chain); + return end($chain); } /** @@ -284,8 +284,15 @@ public function invalidate(array $keys, CacheableDependencyInterface $initial_ca */ protected function getRedirectChain(array $keys, CacheableDependencyInterface $initial_cacheability): array { $cid = $initial_cid = $this->createCacheIdFast($keys, $initial_cacheability); + + // See if we previously stored the redirect chain in memory. We do need to + // run a validity check because cache context values might have changed + // since the last time we got the chain. In theory that should never happen + // during a single request, but better safe than sorry. if (isset($this->redirectChainCache[$cid])) { - return $this->redirectChainCache[$cid]; + if ($this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { + return $this->redirectChainCache[$cid]; + } } $chain[$cid] = $result = $this->cacheBackend->get($cid); @@ -298,6 +305,29 @@ protected function getRedirectChain(array $keys, CacheableDependencyInterface $i return $this->redirectChainCache[$initial_cid] = $chain; } + /** + * Validates a redirect chain for the current cache context values. + * + * @param string[] $keys + * The cache keys used to build the chain. + * @param array $chain + * The redirect chain to validate. + * + * @return bool + * Whether the redirect chain is valid. + */ + protected function redirectChainIsValid(array $keys, array $chain): bool { + foreach ($chain as $result) { + if ($result->data instanceof CacheRedirect) { + $cid = $this->createCacheIdFast($keys, $result->data); + if (!isset($chain[$cid])) { + return FALSE; + } + } + } + return TRUE; + } + /** * Maps a max-age value to an "expire" value for the Cache API. * -- GitLab From f80c6ea86e04032e9950194d9103cde9e0b8e285 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Thu, 13 Mar 2025 15:34:25 +0100 Subject: [PATCH 03/25] Fix notices --- core/lib/Drupal/Core/Cache/VariationCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index a8fda8cd8209..81e4353f18db 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -318,7 +318,7 @@ protected function getRedirectChain(array $keys, CacheableDependencyInterface $i */ protected function redirectChainIsValid(array $keys, array $chain): bool { foreach ($chain as $result) { - if ($result->data instanceof CacheRedirect) { + if ($result && $result->data instanceof CacheRedirect) { $cid = $this->createCacheIdFast($keys, $result->data); if (!isset($chain[$cid])) { return FALSE; -- GitLab From 773bc39af04799c2232cc63d327930a5f37eaf90 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Thu, 13 Mar 2025 15:52:49 +0100 Subject: [PATCH 04/25] Update performance tests --- .../tests/src/FunctionalJavascript/PerformanceTest.php | 4 ++-- .../FunctionalJavascript/StandardPerformanceTest.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php index 1acfd62fb1da..b7e1a693df7c 100644 --- a/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php +++ b/core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php @@ -73,13 +73,13 @@ public function testLogin(): void { $expected = [ 'QueryCount' => 4, - 'CacheGetCount' => 59, + 'CacheGetCount' => 58, 'CacheGetCountByBin' => [ 'config' => 11, 'data' => 6, 'discovery' => 10, 'bootstrap' => 6, - 'dynamic_page_cache' => 2, + 'dynamic_page_cache' => 1, 'render' => 23, 'menu' => 1, ], diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index 0c3d7300ea33..2cc271326a16 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -127,15 +127,15 @@ protected function testAnonymous(): void { $this->assertSame($expected_queries, $recorded_queries); $expected = [ 'QueryCount' => 36, - 'CacheGetCount' => 122, + 'CacheGetCount' => 109, 'CacheGetCountByBin' => [ 'page' => 1, 'config' => 21, 'data' => 8, 'discovery' => 38, 'bootstrap' => 8, - 'dynamic_page_cache' => 2, - 'render' => 35, + 'dynamic_page_cache' => 1, + 'render' => 23, 'default' => 5, 'entity' => 2, 'menu' => 2, @@ -202,7 +202,7 @@ protected function testAnonymous(): void { $this->assertSame($expected_queries, $recorded_queries); $expected = [ 'QueryCount' => 10, - 'CacheGetCount' => 92, + 'CacheGetCount' => 81, 'CacheSetCount' => 16, 'CacheDeleteCount' => 0, 'CacheTagInvalidationCount' => 0, @@ -259,7 +259,7 @@ protected function testAnonymous(): void { $this->assertSame($expected_queries, $recorded_queries); $expected = [ 'QueryCount' => 14, - 'CacheGetCount' => 80, + 'CacheGetCount' => 69, 'CacheSetCount' => 17, 'CacheDeleteCount' => 0, 'CacheTagInvalidationCount' => 0, -- GitLab From 82a27b945ae87d4173559b9bdb30027ce4b6d571 Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Thu, 13 Mar 2025 20:07:49 +0100 Subject: [PATCH 05/25] Attempt to support getMultiple() --- core/lib/Drupal/Core/Cache/VariationCache.php | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 81e4353f18db..b829c04e2434 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -53,19 +53,32 @@ public function getMultiple(array $items): array { // cache backend. // // Create a map of CIDs with their associated $items index and cache keys. + $results = []; $cid_map = []; foreach ($items as $index => [$keys, $cacheability]) { $cid = $this->createCacheIdFast($keys, $cacheability); + + // See if we previously stored the redirect chain in memory. We do need to + // run a validity check because cache context values might have changed + // since the last time we got the chain. In theory that should never happen + // during a single request, but better safe than sorry. + if (isset($this->redirectChainCache[$cid])) { + if ($this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { + $results[$index] = end($this->redirectChainCache[$cid]); + continue; + } + } + $cid_map[$cid] = [ 'index' => $index, 'keys' => $keys, + 'initial' => $cid, ]; } // Go over all CIDs and update the map according to found redirects. If the // map is empty, it means we've followed all CIDs to their final result or // lack thereof. - $results = []; while (!empty($cid_map)) { $new_cid_map = []; @@ -73,6 +86,8 @@ public function getMultiple(array $items): array { foreach ($this->cacheBackend->getMultiple($fetch_cids) as $cid => $result) { $info = $cid_map[$cid]; + $this->redirectChainCache[$info['initial']][$cid] = $result; + // Add redirects to the next CID map, so the next iteration can look // them all up in one ::getMultiple() call to the cache backend. if ($result->data instanceof CacheRedirect) { @@ -84,6 +99,12 @@ public function getMultiple(array $items): array { $results[$info['index']] = $result; } + // Any cache misses are still in $fetch_cids, ensure they are set. + foreach ($fetch_cids as $fetch_cid) { + $info = $cid_map[$cid]; + $this->redirectChainCache[$info['initial']][$fetch_cid] = FALSE; + } + $cid_map = $new_cid_map; } -- GitLab From 31d44e52e7e4297ee399b4f23745b23b95068ed9 Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Thu, 13 Mar 2025 20:15:32 +0100 Subject: [PATCH 06/25] fix and update some tests --- core/lib/Drupal/Core/Cache/VariationCache.php | 2 +- .../tests/src/FunctionalJavascript/StandardPerformanceTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index b829c04e2434..bf626a98791d 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -101,7 +101,7 @@ public function getMultiple(array $items): array { // Any cache misses are still in $fetch_cids, ensure they are set. foreach ($fetch_cids as $fetch_cid) { - $info = $cid_map[$cid]; + $info = $cid_map[$fetch_cid]; $this->redirectChainCache[$info['initial']][$fetch_cid] = FALSE; } diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index 2cc271326a16..5cf9b0675950 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -314,7 +314,7 @@ protected function testLogin(): void { $this->assertSame($expected_queries, $recorded_queries); $expected = [ 'QueryCount' => 17, - 'CacheGetCount' => 84, + 'CacheGetCount' => 81, 'CacheSetCount' => 1, 'CacheDeleteCount' => 1, 'CacheTagInvalidationCount' => 0, -- GitLab From 976dd227a8d72cde00948befce394a08d96a0e01 Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Thu, 13 Mar 2025 22:53:59 +0100 Subject: [PATCH 07/25] Only cache redirects and misses --- core/lib/Drupal/Core/Cache/VariationCache.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index bf626a98791d..2b411e1e7ce9 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -86,13 +86,13 @@ public function getMultiple(array $items): array { foreach ($this->cacheBackend->getMultiple($fetch_cids) as $cid => $result) { $info = $cid_map[$cid]; - $this->redirectChainCache[$info['initial']][$cid] = $result; // Add redirects to the next CID map, so the next iteration can look // them all up in one ::getMultiple() call to the cache backend. if ($result->data instanceof CacheRedirect) { $redirect_cid = $this->createCacheIdFast($info['keys'], $result->data); $new_cid_map[$redirect_cid] = $info; + $this->redirectChainCache[$info['initial']][$cid] = $result; continue; } @@ -323,7 +323,13 @@ protected function getRedirectChain(array $keys, CacheableDependencyInterface $i $chain[$cid] = $result = $this->cacheBackend->get($cid); } - return $this->redirectChainCache[$initial_cid] = $chain; + $chain[$cid] = $result; + if ($result === FALSE) { + $this->redirectChainCache[$initial_cid] = $chain; + } + + return $chain; + } /** -- GitLab From abfe2e608321e12c0229616c067336cda5736122 Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Fri, 14 Mar 2025 09:42:32 +0100 Subject: [PATCH 08/25] phpcs --- core/lib/Drupal/Core/Cache/VariationCache.php | 1 - 1 file changed, 1 deletion(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 2b411e1e7ce9..b6e2e4006c41 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -86,7 +86,6 @@ public function getMultiple(array $items): array { foreach ($this->cacheBackend->getMultiple($fetch_cids) as $cid => $result) { $info = $cid_map[$cid]; - // Add redirects to the next CID map, so the next iteration can look // them all up in one ::getMultiple() call to the cache backend. if ($result->data instanceof CacheRedirect) { -- GitLab From fd3a5cd8a0fa9bcdaf59154f7e53ae5c543fd758 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Fri, 14 Mar 2025 10:15:20 +0100 Subject: [PATCH 09/25] Update logic to store entire chain except for the final part if it's a cache hit. --- core/lib/Drupal/Core/Cache/VariationCache.php | 24 ++++++++++++++----- .../StandardPerformanceTest.php | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index b6e2e4006c41..57cce3a61503 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -311,24 +311,36 @@ protected function getRedirectChain(array $keys, CacheableDependencyInterface $i // during a single request, but better safe than sorry. if (isset($this->redirectChainCache[$cid])) { if ($this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { - return $this->redirectChainCache[$cid]; + $chain = $this->redirectChainCache[$cid]; } } - $chain[$cid] = $result = $this->cacheBackend->get($cid); + // Initiate the chain if we couldn't retrieve (a partial) one from memory. + // If we did find one, we continue our search from the last redirect in the + // chain in case we had a cache hit before, or we take the FALSE at the end + // of the chain from a previous cache miss, bypassing the while loop below. + if (empty($chain)) { + $chain[$cid] = $result = $this->cacheBackend->get($cid); + } + else { + $result = end($chain); + } while ($result && $result->data instanceof CacheRedirect) { $cid = $this->createCacheIdFast($keys, $result->data); $chain[$cid] = $result = $this->cacheBackend->get($cid); } - $chain[$cid] = $result; - if ($result === FALSE) { - $this->redirectChainCache[$initial_cid] = $chain; + // When storing the redirect chain in memory we must take care to not store + // a cache hit as they can be invalidated, unlike CacheRedirect objects. We + // do store the rest of the chain because redirects can be reused safely. + $chain_to_cache = $chain; + if ($result !== FALSE) { + array_pop($chain_to_cache); } + $this->redirectChainCache[$initial_cid] = $chain_to_cache; return $chain; - } /** diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index 5cf9b0675950..2cc271326a16 100644 --- a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php +++ b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php @@ -314,7 +314,7 @@ protected function testLogin(): void { $this->assertSame($expected_queries, $recorded_queries); $expected = [ 'QueryCount' => 17, - 'CacheGetCount' => 81, + 'CacheGetCount' => 84, 'CacheSetCount' => 1, 'CacheDeleteCount' => 1, 'CacheTagInvalidationCount' => 0, -- GitLab From cb5f495f9b8f45f08a7b8270acb1270bd62c7217 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Fri, 14 Mar 2025 10:15:50 +0100 Subject: [PATCH 10/25] Temporarily revert getMultiple changes so we can see impact of recent change on test fails --- core/lib/Drupal/Core/Cache/VariationCache.php | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 57cce3a61503..f7e9d36b53fe 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -53,32 +53,20 @@ public function getMultiple(array $items): array { // cache backend. // // Create a map of CIDs with their associated $items index and cache keys. - $results = []; $cid_map = []; foreach ($items as $index => [$keys, $cacheability]) { $cid = $this->createCacheIdFast($keys, $cacheability); - // See if we previously stored the redirect chain in memory. We do need to - // run a validity check because cache context values might have changed - // since the last time we got the chain. In theory that should never happen - // during a single request, but better safe than sorry. - if (isset($this->redirectChainCache[$cid])) { - if ($this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { - $results[$index] = end($this->redirectChainCache[$cid]); - continue; - } - } - $cid_map[$cid] = [ 'index' => $index, 'keys' => $keys, - 'initial' => $cid, ]; } // Go over all CIDs and update the map according to found redirects. If the // map is empty, it means we've followed all CIDs to their final result or // lack thereof. + $results = []; while (!empty($cid_map)) { $new_cid_map = []; @@ -91,19 +79,12 @@ public function getMultiple(array $items): array { if ($result->data instanceof CacheRedirect) { $redirect_cid = $this->createCacheIdFast($info['keys'], $result->data); $new_cid_map[$redirect_cid] = $info; - $this->redirectChainCache[$info['initial']][$cid] = $result; continue; } $results[$info['index']] = $result; } - // Any cache misses are still in $fetch_cids, ensure they are set. - foreach ($fetch_cids as $fetch_cid) { - $info = $cid_map[$fetch_cid]; - $this->redirectChainCache[$info['initial']][$fetch_cid] = FALSE; - } - $cid_map = $new_cid_map; } -- GitLab From e58e01139ab5c70d4e6be67e50f477984d47918c Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Fri, 14 Mar 2025 14:55:50 +0100 Subject: [PATCH 11/25] Fix some tests already --- core/lib/Drupal/Core/Cache/VariationCache.php | 15 +++++++++++---- .../jsonapi/tests/src/Functional/NodeTest.php | 8 +++++++- .../Views/StyleSerializerEntityTest.php | 9 +++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index f7e9d36b53fe..981af4b309cb 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -290,10 +290,8 @@ protected function getRedirectChain(array $keys, CacheableDependencyInterface $i // run a validity check because cache context values might have changed // since the last time we got the chain. In theory that should never happen // during a single request, but better safe than sorry. - if (isset($this->redirectChainCache[$cid])) { - if ($this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { - $chain = $this->redirectChainCache[$cid]; - } + if (isset($this->redirectChainCache[$cid]) && $this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { + $chain = $this->redirectChainCache[$cid]; } // Initiate the chain if we couldn't retrieve (a partial) one from memory. @@ -411,4 +409,13 @@ protected function createCacheIdFast(array $keys, CacheableDependencyInterface $ return implode(':', $keys); } + /** + * Reset statically cached variables. + * + * This is only used by tests. + */ + public function reset() { + $this->redirectChainCache = []; + } + } diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTest.php index aa40c2446c5c..33e96003d6ad 100644 --- a/core/modules/jsonapi/tests/src/Functional/NodeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/NodeTest.php @@ -399,7 +399,13 @@ protected function assertCacheableNormalizations(): void { * @internal */ protected function assertNormalizedFieldsAreCached(array $field_names): void { - $cache = \Drupal::service('variation_cache.jsonapi_normalizations')->get(['node--camelids', $this->entity->uuid(), $this->entity->language()->getId()], new CacheableMetadata()); + $variation_cache = \Drupal::service('variation_cache.jsonapi_normalizations'); + + // Because we warm caches in different requests, we do not properly populate + // the internal properties of our variation cache. Reset it. + $variation_cache->reset(); + + $cache = $variation_cache->get(['node--camelids', $this->entity->uuid(), $this->entity->language()->getId()], new CacheableMetadata()); $cached_fields = $cache->data['fields']; $this->assertSameSize($field_names, $cached_fields); array_walk($field_names, function ($field_name) use ($cached_fields) { diff --git a/core/modules/rest/tests/src/Functional/Views/StyleSerializerEntityTest.php b/core/modules/rest/tests/src/Functional/Views/StyleSerializerEntityTest.php index 9175d6f5da49..af91d1634272 100644 --- a/core/modules/rest/tests/src/Functional/Views/StyleSerializerEntityTest.php +++ b/core/modules/rest/tests/src/Functional/Views/StyleSerializerEntityTest.php @@ -220,6 +220,11 @@ protected function addRequestWithFormat($format): void { */ public function testRestRenderCaching(): void { $this->drupalLogin($this->adminUser); + + /** @var \Drupal\Core\Cache\VariationCacheFactoryInterface $vc_factory */ + $variation_cache_factory = \Drupal::service('variation_cache_factory'); + $variation_cache = $variation_cache_factory->get('render'); + /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */ $render_cache = \Drupal::service('render_cache'); @@ -268,6 +273,10 @@ public function testRestRenderCaching(): void { $this->assertSession()->responseHeaderEquals('content-type', 'application/json'); $this->assertCacheContexts($cache_contexts); $this->assertCacheTags($cache_tags); + + // Because we warm caches in different requests, we do not properly populate + // the internal properties of our variation cache. Reset it. + $variation_cache->reset(); $this->assertNotEmpty($render_cache->get($original)); $result_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]); -- GitLab From f311425fa02b1fff1f46e522db86af6392429e76 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Fri, 14 Mar 2025 15:38:48 +0100 Subject: [PATCH 12/25] phpstan --- core/lib/Drupal/Core/Cache/VariationCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 981af4b309cb..2d3b67b58be5 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -414,7 +414,7 @@ protected function createCacheIdFast(array $keys, CacheableDependencyInterface $ * * This is only used by tests. */ - public function reset() { + public function reset(): void { $this->redirectChainCache = []; } -- GitLab From 3fbc1664d7eef2ca6b4e3fc8c927c7a09f2e50d4 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Fri, 14 Mar 2025 16:15:35 +0100 Subject: [PATCH 13/25] Fix final test, other JS test seemed like a random failure as it went green locally. --- .../views/tests/src/Functional/Plugin/CacheWebTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/modules/views/tests/src/Functional/Plugin/CacheWebTest.php b/core/modules/views/tests/src/Functional/Plugin/CacheWebTest.php index 29902c6aee4d..e1b403eb53ff 100644 --- a/core/modules/views/tests/src/Functional/Plugin/CacheWebTest.php +++ b/core/modules/views/tests/src/Functional/Plugin/CacheWebTest.php @@ -62,6 +62,10 @@ public function testCacheOutputOnPage(): void { $view->save(); $this->container->get('router.builder')->rebuildIfNeeded(); + /** @var \Drupal\Core\Cache\VariationCacheFactoryInterface $vc_factory */ + $variation_cache_factory = \Drupal::service('variation_cache_factory'); + $variation_cache = $variation_cache_factory->get('render'); + /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */ $render_cache = \Drupal::service('render_cache'); $cache_element = DisplayPluginBase::buildBasicRenderable('test_display', 'page_1'); @@ -70,6 +74,11 @@ public function testCacheOutputOnPage(): void { $this->drupalGet('test-display'); $this->assertSession()->statusCodeEquals(200); + + // Because we warm caches in different requests, we do not properly populate + // the internal properties of our variation cache. Reset it. + $variation_cache->reset(); + $this->assertNotEmpty($render_cache->get($cache_element)); $cache_tags = [ 'config:user.role.anonymous', -- GitLab From 7ed117a8b8e87754de685d1c44769adb990f3272 Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Sun, 16 Mar 2025 11:30:09 +0100 Subject: [PATCH 14/25] Update umami performance test --- .../OpenTelemetryNodePagePerformanceTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php index 7204388bbb34..4e3bb56b62de 100644 --- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php @@ -288,7 +288,7 @@ protected function testNodePageWarmCache(): void { $expected = [ 'QueryCount' => 172, - 'CacheGetCount' => 247, + 'CacheGetCount' => 231, 'CacheGetCountByBin' => [ 'page' => 1, 'config' => 66, @@ -296,8 +296,8 @@ protected function testNodePageWarmCache(): void { 'data' => 13, 'entity' => 18, 'bootstrap' => 8, - 'dynamic_page_cache' => 2, - 'render' => 67, + 'dynamic_page_cache' => 1, + 'render' => 52, 'default' => 3, 'menu' => 2, ], -- GitLab From 48ab7c633e205168092d451cc8c7060a4eca1d7b Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Wed, 19 Mar 2025 14:44:58 +0100 Subject: [PATCH 15/25] Support getMultiple() again --- core/lib/Drupal/Core/Cache/VariationCache.php | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 2d3b67b58be5..fcda1c05067e 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -15,6 +15,18 @@ class VariationCache implements VariationCacheInterface { /** * Stores redirect chain lookups until the next set, invalidate or delete. * + * Array keys are the cache IDs constructed from the cache keys and initial + * cacheability and values are arrays where each step of a redirect chain is + * recorded. + * + * These arrays are indexed by the cache IDs being followed during the chain + * and the CacheRedirect objects that construe the chain. At the end there + * should always be a value of FALSE for a cache miss, or a CacheRedirect for + * a cache hit because we cannot store the cache hit itself into a property + * that does not support invalidation based on cache metadata. By storing the + * last CacheRedirect that led to the hit, we can at least avoid having to + * retrieve the entire chain again to get to the actual cached data. + * * @var array */ protected array $redirectChainCache = []; @@ -52,21 +64,43 @@ public function getMultiple(array $items): array { // following of redirect chains by calling ::getMultiple() on the underlying // cache backend. // + // However, ::getRedirectChain() has an internal cache that we could both + // benefit from and contribute to whenever we call this function. So any use + // or manipulation of $this->redirectChainCache below is for optimization + // purposes. You can read up on how the internal cache is structured on the + // property documentation of $this->redirectChainCache. + // // Create a map of CIDs with their associated $items index and cache keys. - $cid_map = []; + $cid_map = $results = []; foreach ($items as $index => [$keys, $cacheability]) { - $cid = $this->createCacheIdFast($keys, $cacheability); + $cid = $initial_cid = $this->createCacheIdFast($keys, $cacheability); + + // Immediately set cache misses on the results or fast-forward the CID map + // to look from the last known redirect onwards. + if (isset($this->redirectChainCache[$cid]) && $this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { + $last_item = end($this->redirectChainCache[$cid]); + + // Immediately set cache misses on the results. + if ($last_item === FALSE) { + $results[$index] = $last_item; + continue; + } + // Prime the CID map with the last known redirect for the initial CID. + if ($last_item->data instanceof CacheRedirect) { + $cid = $this->createCacheIdFast($keys, $last_item->data); + } + } $cid_map[$cid] = [ 'index' => $index, 'keys' => $keys, + 'initial' => $initial_cid, ]; } // Go over all CIDs and update the map according to found redirects. If the // map is empty, it means we've followed all CIDs to their final result or // lack thereof. - $results = []; while (!empty($cid_map)) { $new_cid_map = []; @@ -79,12 +113,20 @@ public function getMultiple(array $items): array { if ($result->data instanceof CacheRedirect) { $redirect_cid = $this->createCacheIdFast($info['keys'], $result->data); $new_cid_map[$redirect_cid] = $info; + $this->redirectChainCache[$info['initial']][$cid] = $result; continue; } $results[$info['index']] = $result; } + // Any CID that did not get a cache hit is still in $fetch_cids. Add them + // to the internal redirect chain cache as a miss. + foreach ($fetch_cids as $fetch_cid) { + $info = $cid_map[$fetch_cid]; + $this->redirectChainCache[$info['initial']][$fetch_cid] = FALSE; + } + $cid_map = $new_cid_map; } -- GitLab From 0621807df3f7b68761026d61cd67e58957704f4e Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Wed, 19 Mar 2025 14:45:12 +0100 Subject: [PATCH 16/25] Mark the reset method as internal --- core/lib/Drupal/Core/Cache/VariationCache.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index fcda1c05067e..0e40fbe0fe34 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -455,6 +455,8 @@ protected function createCacheIdFast(array $keys, CacheableDependencyInterface $ * Reset statically cached variables. * * This is only used by tests. + * + * @internal */ public function reset(): void { $this->redirectChainCache = []; -- GitLab From b463c23952c4a107ce53c630c265a73134f86bd1 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Wed, 19 Mar 2025 14:53:55 +0100 Subject: [PATCH 17/25] construe -> construct --- core/lib/Drupal/Core/Cache/VariationCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 0e40fbe0fe34..5a95be6b0dd0 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -20,7 +20,7 @@ class VariationCache implements VariationCacheInterface { * recorded. * * These arrays are indexed by the cache IDs being followed during the chain - * and the CacheRedirect objects that construe the chain. At the end there + * and the CacheRedirect objects that construct the chain. At the end there * should always be a value of FALSE for a cache miss, or a CacheRedirect for * a cache hit because we cannot store the cache hit itself into a property * that does not support invalidation based on cache metadata. By storing the -- GitLab From c15d3c3ca97fed8d78edaea2bd2b6ada149bf6a4 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Wed, 19 Mar 2025 14:58:17 +0100 Subject: [PATCH 18/25] Simplify comment --- core/lib/Drupal/Core/Cache/VariationCache.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 5a95be6b0dd0..b3790d0fff85 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -75,8 +75,7 @@ public function getMultiple(array $items): array { foreach ($items as $index => [$keys, $cacheability]) { $cid = $initial_cid = $this->createCacheIdFast($keys, $cacheability); - // Immediately set cache misses on the results or fast-forward the CID map - // to look from the last known redirect onwards. + // Try to optimize based on the redirect chain cache. if (isset($this->redirectChainCache[$cid]) && $this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { $last_item = end($this->redirectChainCache[$cid]); -- GitLab From 7902e2d4b41e9a059f7c5b9c5eb1c3587cfa17b4 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Wed, 19 Mar 2025 15:17:38 +0100 Subject: [PATCH 19/25] Oh wait, on getMultiple() we're not supposed to return FALSE for misses. --- core/lib/Drupal/Core/Cache/VariationCache.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index b3790d0fff85..aad67acb6bf5 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -71,7 +71,7 @@ public function getMultiple(array $items): array { // property documentation of $this->redirectChainCache. // // Create a map of CIDs with their associated $items index and cache keys. - $cid_map = $results = []; + $cid_map = []; foreach ($items as $index => [$keys, $cacheability]) { $cid = $initial_cid = $this->createCacheIdFast($keys, $cacheability); @@ -79,9 +79,8 @@ public function getMultiple(array $items): array { if (isset($this->redirectChainCache[$cid]) && $this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { $last_item = end($this->redirectChainCache[$cid]); - // Immediately set cache misses on the results. + // Immediately skip processing the CID for cache misses. if ($last_item === FALSE) { - $results[$index] = $last_item; continue; } // Prime the CID map with the last known redirect for the initial CID. @@ -100,6 +99,7 @@ public function getMultiple(array $items): array { // Go over all CIDs and update the map according to found redirects. If the // map is empty, it means we've followed all CIDs to their final result or // lack thereof. + $results = []; while (!empty($cid_map)) { $new_cid_map = []; -- GitLab From 7dcf497ed16bf9c3ba6d344d4e35108b2b474ec5 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Wed, 19 Mar 2025 15:51:06 +0100 Subject: [PATCH 20/25] Cache gets -2 across the board after getMultiple support --- .../OpenTelemetryNodePagePerformanceTest.php | 4 ++-- .../src/FunctionalJavascript/StandardPerformanceTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php index f237e79830a9..03a4b2cd9328 100644 --- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php @@ -288,7 +288,7 @@ protected function testNodePageWarmCache(): void { $expected = [ 'QueryCount' => 172, - 'CacheGetCount' => 231, + 'CacheGetCount' => 229, 'CacheGetCountByBin' => [ 'page' => 1, 'config' => 66, @@ -297,7 +297,7 @@ protected function testNodePageWarmCache(): void { 'entity' => 18, 'bootstrap' => 8, 'dynamic_page_cache' => 1, - 'render' => 52, + 'render' => 50, 'default' => 3, 'menu' => 2, ], diff --git a/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php b/core/profiles/standard/tests/src/FunctionalJavascript/StandardPerformanceTest.php index 68d6523426e6..2cb6c35e80f3 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' => 111, + 'CacheGetCount' => 109, 'CacheGetCountByBin' => [ 'page' => 1, 'config' => 21, @@ -135,7 +135,7 @@ protected function testAnonymous(): void { 'discovery' => 38, 'bootstrap' => 8, 'dynamic_page_cache' => 1, - 'render' => 25, + 'render' => 23, 'default' => 5, 'entity' => 2, 'menu' => 2, @@ -225,7 +225,7 @@ protected function testAnonymous(): void { $this->assertSame($expected_queries, $recorded_queries); $expected = [ 'QueryCount' => 10, - 'CacheGetCount' => 83, + 'CacheGetCount' => 81, 'CacheSetCount' => 16, 'CacheDeleteCount' => 0, 'CacheTagInvalidationCount' => 0, @@ -306,7 +306,7 @@ protected function testAnonymous(): void { $this->assertSame($expected_queries, $recorded_queries); $expected = [ 'QueryCount' => 14, - 'CacheGetCount' => 68, + 'CacheGetCount' => 66, 'CacheSetCount' => 17, 'CacheDeleteCount' => 0, 'CacheTagInvalidationCount' => 0, -- GitLab From 02c843b5693873f913c16b5eb926ae89e519f8c2 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Mon, 31 Mar 2025 18:53:50 +0200 Subject: [PATCH 21/25] Didn't know we had JsonApiPerformanceTest now... --- .../FunctionalJavascript/JsonApiPerformanceTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/modules/jsonapi/tests/src/FunctionalJavascript/JsonApiPerformanceTest.php b/core/modules/jsonapi/tests/src/FunctionalJavascript/JsonApiPerformanceTest.php index 055b009a0c5e..b1b0934b5da3 100644 --- a/core/modules/jsonapi/tests/src/FunctionalJavascript/JsonApiPerformanceTest.php +++ b/core/modules/jsonapi/tests/src/FunctionalJavascript/JsonApiPerformanceTest.php @@ -87,7 +87,7 @@ public function testGetIndividual(): void { $expected = [ 'QueryCount' => 26, - 'CacheGetCount' => 44, + 'CacheGetCount' => 42, 'CacheGetCountByBin' => [ 'config' => 8, 'data' => 8, @@ -95,8 +95,8 @@ public function testGetIndividual(): void { 'discovery' => 13, 'entity' => 2, 'default' => 4, - 'dynamic_page_cache' => 2, - 'jsonapi_normalizations' => 2, + 'dynamic_page_cache' => 1, + 'jsonapi_normalizations' => 1, ], 'CacheSetCount' => 16, 'CacheSetCountByBin' => [ @@ -206,7 +206,7 @@ public function testGetIndividual(): void { $expected = [ 'QueryCount' => 15, - 'CacheGetCount' => 47, + 'CacheGetCount' => 43, 'CacheGetCountByBin' => [ 'config' => 8, 'data' => 8, @@ -214,8 +214,8 @@ public function testGetIndividual(): void { 'entity' => 2, 'default' => 4, 'bootstrap' => 4, - 'dynamic_page_cache' => 4, - 'jsonapi_normalizations' => 4, + 'dynamic_page_cache' => 2, + 'jsonapi_normalizations' => 2, ], 'CacheSetCount' => 3, 'CacheDeleteCount' => 0, -- GitLab From b0cc8ab3324ead7d9f821a072324f53b4005a9e9 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Tue, 1 Apr 2025 11:01:12 +0200 Subject: [PATCH 22/25] Trying something wild, swapping out validation logic with something new --- core/lib/Drupal/Core/Cache/VariationCache.php | 80 ++++++++++++------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index aad67acb6bf5..3068dc56143c 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -73,20 +73,24 @@ public function getMultiple(array $items): array { // Create a map of CIDs with their associated $items index and cache keys. $cid_map = []; foreach ($items as $index => [$keys, $cacheability]) { - $cid = $initial_cid = $this->createCacheIdFast($keys, $cacheability); - - // Try to optimize based on the redirect chain cache. - if (isset($this->redirectChainCache[$cid]) && $this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { - $last_item = end($this->redirectChainCache[$cid]); + // Try to optimize based on the cached redirect chain. + if ($chain = $this->getValidatedCachedRedirectChain($keys, $cacheability)) { + $last_item = end($chain); // Immediately skip processing the CID for cache misses. if ($last_item === FALSE) { continue; } + + // We do not need to calculate the initial CID as its part of the chain. + $initial_cid = array_key_first($chain); + // Prime the CID map with the last known redirect for the initial CID. - if ($last_item->data instanceof CacheRedirect) { - $cid = $this->createCacheIdFast($keys, $last_item->data); - } + assert($last_item->data instanceof CacheRedirect); + $cid = $this->createCacheIdFast($keys, $last_item->data); + } + else { + $cid = $initial_cid = $this->createCacheIdFast($keys, $cacheability); } $cid_map[$cid] = [ @@ -325,24 +329,18 @@ public function invalidate(array $keys, CacheableDependencyInterface $initial_ca * to query the cache for that result. */ protected function getRedirectChain(array $keys, CacheableDependencyInterface $initial_cacheability): array { - $cid = $initial_cid = $this->createCacheIdFast($keys, $initial_cacheability); - - // See if we previously stored the redirect chain in memory. We do need to - // run a validity check because cache context values might have changed - // since the last time we got the chain. In theory that should never happen - // during a single request, but better safe than sorry. - if (isset($this->redirectChainCache[$cid]) && $this->redirectChainIsValid($keys, $this->redirectChainCache[$cid])) { - $chain = $this->redirectChainCache[$cid]; - } + $chain = $this->getValidatedCachedRedirectChain($keys, $initial_cacheability); // Initiate the chain if we couldn't retrieve (a partial) one from memory. - // If we did find one, we continue our search from the last redirect in the - // chain in case we had a cache hit before, or we take the FALSE at the end - // of the chain from a previous cache miss, bypassing the while loop below. if (empty($chain)) { + $cid = $initial_cid = $this->createCacheIdFast($keys, $initial_cacheability); $chain[$cid] = $result = $this->cacheBackend->get($cid); } + // If we did find one, we continue our search from the last valid redirect + // in the chain or bypass the while loop below in case the chain ends in + // FALSE, indicating a previous cache miss. else { + $initial_cid = array_key_first($chain); $result = end($chain); } @@ -364,26 +362,46 @@ protected function getRedirectChain(array $keys, CacheableDependencyInterface $i } /** - * Validates a redirect chain for the current cache context values. + * Retrieved the redirect chain from cache, validating each part. * * @param string[] $keys - * The cache keys used to build the chain. - * @param array $chain - * The redirect chain to validate. + * The cache keys to retrieve the redirect chain for. + * @param \Drupal\Core\Cache\CacheableDependencyInterface $initial_cacheability + * The initial cacheability for the redirect chain. * - * @return bool - * Whether the redirect chain is valid. + * @return array + * The part of the cached redirect chain, if any, that is still valid. */ - protected function redirectChainIsValid(array $keys, array $chain): bool { - foreach ($chain as $result) { + protected function getValidatedCachedRedirectChain(array $keys, CacheableDependencyInterface $initial_cacheability): array { + $cid = $this->createCacheIdFast($keys, $initial_cacheability); + if (!isset($this->redirectChainCache[$cid])) { + return []; + } + + // Only use that part of the redirect chain that is still valid. Even though + // we do not store cache hits in the internal redirect chain cache, we can + // still reuse the whole chain up until what would have been a cache hit. + // + // If part of a redirect chain no longer matches because cache contexts + // changed values, we could perhaps still reuse part of the chain until we + // encounter a redirect for the changed cache context value. + // + // There is one special case: If the very last item of the chain is a cache + // redirect, and we cannot find anything for it, we still add the redirect + // to the validated chain because the only way a cached chain ends in a + // redirect is if it led to a cache hit in ::getRedirectChain(). + $valid_parts = []; + $last_key = array_key_last($this->redirectChainCache[$cid]); + foreach ($this->redirectChainCache[$cid] as $key => $result) { if ($result && $result->data instanceof CacheRedirect) { $cid = $this->createCacheIdFast($keys, $result->data); - if (!isset($chain[$cid])) { - return FALSE; + if (!isset($chain[$cid]) && $last_key !== $key) { + break; } } + $valid_parts[$key] = $result; } - return TRUE; + return $valid_parts; } /** -- GitLab From c09ac607a863e06dda36280e856ebd6b9dea70cf Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Tue, 1 Apr 2025 11:14:11 +0200 Subject: [PATCH 23/25] phpstan --- core/lib/Drupal/Core/Cache/VariationCache.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index 3068dc56143c..fe0408b0f392 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -377,6 +377,7 @@ protected function getValidatedCachedRedirectChain(array $keys, CacheableDepende if (!isset($this->redirectChainCache[$cid])) { return []; } + $chain = $this->redirectChainCache[$cid]; // Only use that part of the redirect chain that is still valid. Even though // we do not store cache hits in the internal redirect chain cache, we can @@ -391,8 +392,8 @@ protected function getValidatedCachedRedirectChain(array $keys, CacheableDepende // to the validated chain because the only way a cached chain ends in a // redirect is if it led to a cache hit in ::getRedirectChain(). $valid_parts = []; - $last_key = array_key_last($this->redirectChainCache[$cid]); - foreach ($this->redirectChainCache[$cid] as $key => $result) { + $last_key = array_key_last($chain); + foreach ($chain as $key => $result) { if ($result && $result->data instanceof CacheRedirect) { $cid = $this->createCacheIdFast($keys, $result->data); if (!isset($chain[$cid]) && $last_key !== $key) { -- GitLab From dc8151a56863ed975ebca45879fafc1020138c37 Mon Sep 17 00:00:00 2001 From: Kristiaan Van den Eynde <kristiaan@factorial.io> Date: Tue, 1 Apr 2025 11:35:24 +0200 Subject: [PATCH 24/25] Even fewer cache gets now --- .../OpenTelemetryNodePagePerformanceTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php index 314838064591..ee3a60bd012c 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' => 205, + 'CacheGetCount' => 202, 'CacheGetCountByBin' => [ 'page' => 1, 'config' => 66, @@ -296,7 +296,7 @@ protected function testNodePageWarmCache(): void { 'data' => 13, 'entity' => 18, 'dynamic_page_cache' => 1, - 'render' => 24, + 'render' => 21, 'default' => 3, 'menu' => 2, ], -- GitLab From 295caf19543276cd7c74e6f09555ae5a8ffb22df Mon Sep 17 00:00:00 2001 From: catch <6915-catch@users.noreply.drupalcode.org> Date: Tue, 1 Apr 2025 11:51:27 +0000 Subject: [PATCH 25/25] Apply 1 suggestion(s) to 1 file(s) --- core/lib/Drupal/Core/Cache/VariationCache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Cache/VariationCache.php b/core/lib/Drupal/Core/Cache/VariationCache.php index fe0408b0f392..1d26eea8c7ee 100644 --- a/core/lib/Drupal/Core/Cache/VariationCache.php +++ b/core/lib/Drupal/Core/Cache/VariationCache.php @@ -67,7 +67,7 @@ public function getMultiple(array $items): array { // However, ::getRedirectChain() has an internal cache that we could both // benefit from and contribute to whenever we call this function. So any use // or manipulation of $this->redirectChainCache below is for optimization - // purposes. You can read up on how the internal cache is structured on the + // purposes. A description of the internal cache structure is on the // property documentation of $this->redirectChainCache. // // Create a map of CIDs with their associated $items index and cache keys. -- GitLab