diff --git a/README.md b/README.md index af783a817dd6a6844d2ce4c8d9d2e809351b2b55..e6756c8da7956bba12598901fc7045d3245f9dd9 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 0a3f2cde8ed35d9ed62bc9d2349262fe9dd37080..d1489acb58237619c9ab141e3e7ce1082df3aa31 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 8c585569e42fa57776f4e7263ea90d9dfece9c19..696f4cb6e88f0c3e3c2ef69c61dd63dc05225df2 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 ba5fc1d35d3fff7ebada254aa3cedde175178632..054fba7c64a17db9f1018b26f1b260cfdf7adef8 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.'); + } + }