From 82c6950676844d241978a6d974a437f4501582cc Mon Sep 17 00:00:00 2001 From: Sascha Grossenbacher <saschagros@gmail.com> Date: Sun, 19 Jan 2025 22:20:42 +0100 Subject: [PATCH] Introduce a flag to treat invalidateAll() like deleteAll() --- README.md | 14 ++++++++++++++ settings.redis.example.php | 3 +++ src/Cache/CacheBase.php | 25 ++++++++++++++++++------- tests/src/Kernel/RedisCacheTest.php | 28 ++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index af783a8..e6756c8 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,20 @@ needs to be configured for that. ], ]; +Additional cache optimizations +=================== + +These settings allow to further optimize caching but are not be fully compatible +with the expected behavior of cache backends or have other tradeoffs. + +Treat invalidateAll() the same as deleteAll() to avoid two different checks for +each bin. + + $settings['redis_invalidate_all_as_delete'] = TRUE; + +Core may deprecate invalidateAll() in the future, this is essentially the same +as https://www.drupal.org/project/drupal/issues/3498947. + Additional configuration and features. =============== diff --git a/settings.redis.example.php b/settings.redis.example.php index 0a3f2cd..d1489ac 100644 --- a/settings.redis.example.php +++ b/settings.redis.example.php @@ -38,6 +38,9 @@ if (!InstallerKernel::installationAttempted() && extension_loaded('redis')) { // Customize the prefix, a reliable but long fallback is used if not defined. // $settings['cache_prefix'] = 'prefix'; + // Additional optimizations, see README.md + // $settings['redis_invalidate_all_as_delete'] = TRUE; + // Apply changes to the container configuration to better leverage Redis. // This includes using Redis for the lock and flood control systems, as well // as the cache tag checksum. Alternatively, copy the contents of that file diff --git a/src/Cache/CacheBase.php b/src/Cache/CacheBase.php index 8c58556..696f4cb 100644 --- a/src/Cache/CacheBase.php +++ b/src/Cache/CacheBase.php @@ -339,7 +339,7 @@ abstract class CacheBase implements CacheBackendInterface { $cache = (object) $values; - $cache->tags = explode(' ', $cache->tags); + $cache->tags = $cache->tags ? explode(' ', $cache->tags) : []; // Check expire time, allow to have a cache invalidated explicitly, don't // check if already invalid. @@ -351,9 +351,11 @@ abstract class CacheBase implements CacheBackendInterface { $cache->valid = FALSE; } - // Remove the bin cache tag to not expose that, otherwise it is reused - // by the fast backend in the FastChained implementation. - $cache->tags = array_diff($cache->tags, [$this->getTagForBin()]); + if (Settings::get('redis_invalidate_all_as_delete', FALSE) === FALSE) { + // Remove the bin cache tag to not expose that, otherwise it is reused + // by the fast backend in the FastChained implementation. + $cache->tags = array_diff($cache->tags, [$this->getTagForBin()]); + } } // Ensure the entry does not predate the last delete all time. @@ -395,7 +397,9 @@ abstract class CacheBase implements CacheBackendInterface { protected function createEntryHash($cid, $data, $expire, array $tags) { // Always add a cache tag for the current bin, so that we can use that for // invalidateAll(). - $tags[] = $this->getTagForBin(); + if (Settings::get('redis_invalidate_all_as_delete', FALSE) === FALSE) { + $tags[] = $this->getTagForBin(); + } assert(Inspector::assertAllStrings($tags), 'Cache Tags must be strings.'); $hash = [ 'cid' => $cid, @@ -441,8 +445,15 @@ abstract class CacheBase implements CacheBackendInterface { * {@inheritdoc} */ public function invalidateAll() { - // To invalidate the whole bin, we invalidate a special tag for this bin. - $this->checksumProvider->invalidateTags([$this->getTagForBin()]); + if (Settings::get('redis_invalidate_all_as_delete', FALSE) === FALSE) { + // To invalidate the whole bin, we invalidate a special tag for this bin. + $this->checksumProvider->invalidateTags([$this->getTagForBin()]); + } + else { + // If the optimization for invalidate all is enabled, treat it as a + // deleteAll() so we only have to check one thing. + $this->deleteAll(); + } } /** diff --git a/tests/src/Kernel/RedisCacheTest.php b/tests/src/Kernel/RedisCacheTest.php index ba5fc1d..054fba7 100644 --- a/tests/src/Kernel/RedisCacheTest.php +++ b/tests/src/Kernel/RedisCacheTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\redis\Kernel; +use Drupal\Core\Cache\Cache; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\KernelTests\Core\Cache\GenericCacheBackendUnitTestBase; use Symfony\Component\DependencyInjection\Reference; @@ -46,4 +47,31 @@ class RedisCacheTest extends GenericCacheBackendUnitTestBase { return $cache; } + /** + * Tests Drupal\Core\Cache\CacheBackendInterface::invalidateAll(). + * + * @group legacy + */ + public function testInvalidateAllOptimized(): void { + $this->setSetting('redis_invalidate_all_as_delete', TRUE); + $backend_a = $this->getCacheBackend(); + $backend_b = $this->getCacheBackend('bootstrap'); + + // Set both expiring and permanent keys. + $backend_a->set('test1', 1, Cache::PERMANENT); + $backend_a->set('test2', 3, time() + 1000); + $backend_b->set('test3', 4, Cache::PERMANENT); + + $backend_a->invalidateAll(); + + $this->assertFalse($backend_a->get('test1'), 'First key has been invalidated.'); + $this->assertFalse($backend_a->get('test2'), 'Second key has been invalidated.'); + $this->assertNotEmpty($backend_b->get('test3'), 'Item in other bin is preserved.'); + + // Keys can also no longer be retrieved when allowing invalid caches to be + // returned. + $this->assertEmpty($backend_a->get('test1', TRUE), 'First key has been deleted.'); + $this->assertEmpty($backend_a->get('test2', TRUE), 'Second key has been deleted.'); + } + } -- GitLab