You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
bug #41804 [Cache] fix eventual consistency when using RedisTagAwareAdapter with a cluster (nicolas-grekas)
This PR was merged into the 4.4 branch.
Discussion
----------
[Cache] fix eventual consistency when using RedisTagAwareAdapter with a cluster
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Tickets | -
| License | MIT
| Doc PR | -
Right now, if the Symfony app stops in the middle of the invalidation logic, we lose the keys to invalidate.
This PR fixes the invalidation logic by making it eventually consistent, and also more scalable thanks to using `SSCAN` instead of `SMEMBERS` when iterating over the items to delete.
The eventual consistency happens when the same tag is invalidated again. We could improve this eg by garbage collecting also when saving and deleting an item but I'll let this as an exercise for a future contributor :)
/cc `@andrerom` in case you'd like to have a look.
Commits
-------
5f2d5e0 [Cache] fix eventual consistency when using RedisTagAwareAdapter with a cluster
Copy file name to clipboardExpand all lines: src/Symfony/Component/Cache/Adapter/RedisTagAwareAdapter.php
+70-78Lines changed: 70 additions & 78 deletions
Original file line number
Diff line number
Diff line change
@@ -23,17 +23,13 @@
23
23
useSymfony\Component\Cache\Traits\RedisTrait;
24
24
25
25
/**
26
-
* Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using RENAME+SMEMBERS.
26
+
* Stores tag id <> cache id relationship as a Redis Set.
27
27
*
28
28
* Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
29
29
* if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
30
30
* relationship survives eviction (cache cleanup when Redis runs out of memory).
31
31
*
32
-
* Requirements:
33
-
* - Client: PHP Redis or Predis
34
-
* Note: Due to lack of RENAME support it is NOT recommended to use Cluster on Predis, instead use phpredis.
35
-
* - Server: Redis 2.8+
36
-
* Configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
32
+
* Redis server 2.8+ with any `volatile-*` eviction policy, OR `noeviction` if you're sure memory will NEVER fill up
37
33
*
38
34
* Design limitations:
39
35
* - Max 4 billion cache keys per cache tag as limited by Redis Set datatype.
@@ -49,11 +45,6 @@ class RedisTagAwareAdapter extends AbstractTagAwareAdapter
49
45
{
50
46
use RedisTrait;
51
47
52
-
/**
53
-
* Limits for how many keys are deleted in batch.
54
-
*/
55
-
privateconstBULK_DELETE_LIMIT = 10000;
56
-
57
48
/**
58
49
* On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
59
50
* preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
@@ -96,7 +87,7 @@ protected function doSave(array $values, int $lifetime, array $addTagData = [],
96
87
{
97
88
$eviction = $this->getRedisEvictionPolicy();
98
89
if ('noeviction' !== $eviction && 0 !== strpos($eviction, 'volatile-')) {
99
-
thrownewLogicException(sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction));
90
+
thrownewLogicException(sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction));
100
91
}
101
92
102
93
// serialize values
@@ -159,15 +150,9 @@ protected function doDeleteYieldTags(array $ids): iterable
Copy file name to clipboardExpand all lines: src/Symfony/Component/Cache/Traits/RedisTrait.php
+10-8Lines changed: 10 additions & 8 deletions
Original file line number
Diff line number
Diff line change
@@ -363,12 +363,6 @@ protected function doHave($id)
363
363
protectedfunctiondoClear($namespace)
364
364
{
365
365
$cleared = true;
366
-
if ($this->redisinstanceof \Predis\ClientInterface) {
367
-
$evalArgs = [0, $namespace];
368
-
} else {
369
-
$evalArgs = [[$namespace], 0];
370
-
}
371
-
372
366
$hosts = $this->getHosts();
373
367
$host = reset($hosts);
374
368
if ($hostinstanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) {
@@ -385,17 +379,20 @@ protected function doClear($namespace)
385
379
$info = $host->info('Server');
386
380
$info = $info['Server'] ?? $info;
387
381
382
+
$pattern = $namespace.'*';
383
+
388
384
if (!version_compare($info['redis_version'], '2.8', '>=')) {
389
385
// As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
390
386
// can hang your server when it is executed against large databases (millions of items).
391
387
// Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above.
392
-
$cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]..'*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $evalArgs[0], $evalArgs[1]) && $cleared;
$cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]) for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $args[0], $args[1]) && $cleared;
0 commit comments