From 7cca797b1ee8a7fe8eb8b474221c682bf35d689e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 17 Jan 2023 15:54:42 -0600 Subject: [PATCH 1/6] initial work on more scalable redis cache tags --- src/Illuminate/Cache/RedisStore.php | 2 +- src/Illuminate/Cache/RedisTagSet.php | 119 ++++++++++++++++++ src/Illuminate/Cache/RedisTaggedCache.php | 145 ++++------------------ tests/Cache/CacheTaggedCacheTest.php | 80 ------------ 4 files changed, 141 insertions(+), 205 deletions(-) create mode 100644 src/Illuminate/Cache/RedisTagSet.php diff --git a/src/Illuminate/Cache/RedisStore.php b/src/Illuminate/Cache/RedisStore.php index 4896c9183d03..146d23389cc2 100755 --- a/src/Illuminate/Cache/RedisStore.php +++ b/src/Illuminate/Cache/RedisStore.php @@ -244,7 +244,7 @@ public function flush() public function tags($names) { return new RedisTaggedCache( - $this, new TagSet($this, is_array($names) ? $names : func_get_args()) + $this, new RedisTagSet($this, is_array($names) ? $names : func_get_args()) ); } diff --git a/src/Illuminate/Cache/RedisTagSet.php b/src/Illuminate/Cache/RedisTagSet.php new file mode 100644 index 000000000000..ff47c5fb9b51 --- /dev/null +++ b/src/Illuminate/Cache/RedisTagSet.php @@ -0,0 +1,119 @@ + 0 ? now()->addSeconds($ttl)->getTimestamp() : -1; + + foreach ($this->tagIds() as $tagKey) { + $this->store->connection()->zadd($this->store->getPrefix().$tagKey, $ttl, $key); + } + } + + /** + * Get all of the cache entry keys for the tag set. + * + * @return \Illuminate\Support\LazyCollection + */ + public function entries() + { + return LazyCollection::make(function () { + foreach ($this->tagIds() as $tagKey) { + $cursor = $defaultCursorValue = '0'; + + do { + [$cursor, $entries] = $this->store->connection()->zscan( + $this->store->getPrefix().$tagKey, + $cursor, + ['match' => '*', 'count' => 1000] + ); + + if (! is_array($entries)) { + break; + } + + $entries = array_unique(array_keys($entries)); + + if (count($entries) === 0) { + continue; + } + + foreach ($entries as $entry) { + yield $entry; + } + } while (((string) $cursor) !== $defaultCursorValue); + } + }); + } + + /** + * Remove the stale entries from the tag set. + * + * @return void + */ + public function flushStaleEntries() + { + $this->store->connection()->pipeline(function ($pipe) { + foreach ($this->tagIds() as $tagKey) { + $pipe->zremrangebyscore($this->store->getPrefix().$tagKey, 0, now()->getTimestamp()); + } + }); + } + + /** + * Flush the tag from the cache. + * + * @param string $name + */ + public function flushTag($name) + { + return $this->resetTag($name); + } + + /** + * Reset the tag and return the new tag identifier. + * + * @param string $name + * @return string + */ + public function resetTag($name) + { + $this->store->forget($this->tagKey($name)); + + return $this->tagId($name); + } + + /** + * Get the unique tag identifier for a given tag. + * + * @param string $name + * @return string + */ + public function tagId($name) + { + return "tag:{$name}:entries"; + } + + /** + * Get the tag identifier key for a given tag. + * + * @param string $name + * @return string + */ + public function tagKey($name) + { + return "tag:{$name}:entries"; + } +} diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 7863dbc0a60a..0b0aa8762ad4 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -4,20 +4,6 @@ class RedisTaggedCache extends TaggedCache { - /** - * Forever reference key. - * - * @var string - */ - const REFERENCE_KEY_FOREVER = 'forever_ref'; - - /** - * Standard reference key. - * - * @var string - */ - const REFERENCE_KEY_STANDARD = 'standard_ref'; - /** * Store an item in the cache. * @@ -28,11 +14,14 @@ class RedisTaggedCache extends TaggedCache */ public function put($key, $value, $ttl = null) { - if ($ttl === null) { + if (is_null($ttl)) { return $this->forever($key, $value); } - $this->pushStandardKeys($this->tags->getNamespace(), $key); + $this->tags->addEntry( + $this->itemKey($key), + $this->getSeconds($ttl) + ); return parent::put($key, $value, $ttl); } @@ -46,7 +35,7 @@ public function put($key, $value, $ttl = null) */ public function increment($key, $value = 1) { - $this->pushStandardKeys($this->tags->getNamespace(), $key); + $this->tags->addEntry($this->itemKey($key)); return parent::increment($key, $value); } @@ -60,7 +49,7 @@ public function increment($key, $value = 1) */ public function decrement($key, $value = 1) { - $this->pushStandardKeys($this->tags->getNamespace(), $key); + $this->tags->addEntry($this->itemKey($key)); return parent::decrement($key, $value); } @@ -74,7 +63,7 @@ public function decrement($key, $value = 1) */ public function forever($key, $value) { - $this->pushForeverKeys($this->tags->getNamespace(), $key); + $this->tags->addEntry($this->itemKey($key)); return parent::forever($key, $value); } @@ -86,129 +75,37 @@ public function forever($key, $value) */ public function flush() { - $this->deleteForeverKeys(); - $this->deleteStandardKeys(); - + $this->flushValues(); $this->tags->flush(); return true; } /** - * Store standard key references into store. + * Flush the individual cache entries for the tags. * - * @param string $namespace - * @param string $key * @return void */ - protected function pushStandardKeys($namespace, $key) + protected function flushValues() { - $this->pushKeys($namespace, $key, self::REFERENCE_KEY_STANDARD); - } + $entries = $this->tags->entries() + ->map(fn (string $key) => $this->store->getPrefix().$key) + ->chunk(1000); - /** - * Store forever key references into store. - * - * @param string $namespace - * @param string $key - * @return void - */ - protected function pushForeverKeys($namespace, $key) - { - $this->pushKeys($namespace, $key, self::REFERENCE_KEY_FOREVER); - } - - /** - * Store a reference to the cache key against the reference key. - * - * @param string $namespace - * @param string $key - * @param string $reference - * @return void - */ - protected function pushKeys($namespace, $key, $reference) - { - $fullKey = $this->store->getPrefix().sha1($namespace).':'.$key; - - foreach (explode('|', $namespace) as $segment) { - $this->store->connection()->sadd($this->referenceKey($segment, $reference), $fullKey); + foreach ($entries as $cacheKeys) { + $this->store->connection()->del(...$cacheKeys); } } /** - * Delete all of the items that were stored forever. + * Remove all stale reference entries from the tag set. * - * @return void - */ - protected function deleteForeverKeys() - { - $this->deleteKeysByReference(self::REFERENCE_KEY_FOREVER); - } - - /** - * Delete all standard items. - * - * @return void - */ - protected function deleteStandardKeys() - { - $this->deleteKeysByReference(self::REFERENCE_KEY_STANDARD); - } - - /** - * Find and delete all of the items that were stored against a reference. - * - * @param string $reference - * @return void - */ - protected function deleteKeysByReference($reference) - { - foreach (explode('|', $this->tags->getNamespace()) as $segment) { - $this->deleteValues($segment = $this->referenceKey($segment, $reference)); - - $this->store->connection()->del($segment); - } - } - - /** - * Delete item keys that have been stored against a reference. - * - * @param string $referenceKey - * @return void + * @return bool */ - protected function deleteValues($referenceKey) + public function flushStale() { - $cursor = $defaultCursorValue = '0'; - - do { - [$cursor, $valuesChunk] = $this->store->connection()->sscan( - $referenceKey, $cursor, ['match' => '*', 'count' => 1000] - ); - - // PhpRedis client returns false if set does not exist or empty. Array destruction - // on false stores null in each variable. If valuesChunk is null, it means that - // there were not results from the previously executed "sscan" Redis command. - if (is_null($valuesChunk)) { - break; - } - - $valuesChunk = array_unique($valuesChunk); - - if (count($valuesChunk) > 0) { - $this->store->connection()->del(...$valuesChunk); - } - } while (((string) $cursor) !== $defaultCursorValue); - } + $this->tags->flushStaleEntries(); - /** - * Get the reference key for the segment. - * - * @param string $segment - * @param string $suffix - * @return string - */ - protected function referenceKey($segment, $suffix) - { - return $this->store->getPrefix().$segment.':'.$suffix; + return true; } } diff --git a/tests/Cache/CacheTaggedCacheTest.php b/tests/Cache/CacheTaggedCacheTest.php index b2493694d136..e6a7dc6ce98e 100644 --- a/tests/Cache/CacheTaggedCacheTest.php +++ b/tests/Cache/CacheTaggedCacheTest.php @@ -207,86 +207,6 @@ public function testTagsCacheForever() $this->assertSame('bar', $store->tags($tags)->get('foo')); } - public function testRedisCacheTagsPushForeverKeysCorrectly() - { - $store = m::mock(Store::class); - $tagSet = m::mock(TagSet::class, [$store, ['foo', 'bar']]); - $tagSet->shouldReceive('getNamespace')->andReturn('foo|bar'); - $tagSet->shouldReceive('getNames')->andReturn(['foo', 'bar']); - $redis = new RedisTaggedCache($store, $tagSet); - $store->shouldReceive('getPrefix')->andReturn('prefix:'); - $store->shouldReceive('connection')->andReturn($conn = m::mock(stdClass::class)); - $conn->shouldReceive('sadd')->once()->with('prefix:foo:forever_ref', 'prefix:'.sha1('foo|bar').':key1'); - $conn->shouldReceive('sadd')->once()->with('prefix:bar:forever_ref', 'prefix:'.sha1('foo|bar').':key1'); - - $store->shouldReceive('forever')->with(sha1('foo|bar').':key1', 'key1:value'); - - $redis->forever('key1', 'key1:value'); - } - - public function testRedisCacheTagsPushStandardKeysCorrectly() - { - $store = m::mock(Store::class); - $tagSet = m::mock(TagSet::class, [$store, ['foo', 'bar']]); - $tagSet->shouldReceive('getNamespace')->andReturn('foo|bar'); - $tagSet->shouldReceive('getNames')->andReturn(['foo', 'bar']); - $redis = new RedisTaggedCache($store, $tagSet); - $store->shouldReceive('getPrefix')->andReturn('prefix:'); - $store->shouldReceive('connection')->andReturn($conn = m::mock(stdClass::class)); - $conn->shouldReceive('sadd')->once()->with('prefix:foo:standard_ref', 'prefix:'.sha1('foo|bar').':key1'); - $conn->shouldReceive('sadd')->once()->with('prefix:bar:standard_ref', 'prefix:'.sha1('foo|bar').':key1'); - $store->shouldReceive('push')->with(sha1('foo|bar').':key1', 'key1:value'); - $store->shouldReceive('put')->andReturn(true); - - $redis->put('key1', 'key1:value', 60); - } - - public function testRedisCacheTagsPushForeverKeysCorrectlyWithNullTTL() - { - $store = m::mock(Store::class); - $tagSet = m::mock(TagSet::class, [$store, ['foo', 'bar']]); - $tagSet->shouldReceive('getNamespace')->andReturn('foo|bar'); - $tagSet->shouldReceive('getNames')->andReturn(['foo', 'bar']); - $redis = new RedisTaggedCache($store, $tagSet); - $store->shouldReceive('getPrefix')->andReturn('prefix:'); - $store->shouldReceive('connection')->andReturn($conn = m::mock(stdClass::class)); - $conn->shouldReceive('sadd')->once()->with('prefix:foo:forever_ref', 'prefix:'.sha1('foo|bar').':key1'); - $conn->shouldReceive('sadd')->once()->with('prefix:bar:forever_ref', 'prefix:'.sha1('foo|bar').':key1'); - $store->shouldReceive('forever')->with(sha1('foo|bar').':key1', 'key1:value'); - - $redis->put('key1', 'key1:value'); - } - - public function testRedisCacheTagsCanBeFlushed() - { - $store = m::mock(Store::class); - $tagSet = m::mock(TagSet::class, [$store, ['foo', 'bar']]); - $tagSet->shouldReceive('getNamespace')->andReturn('foo|bar'); - $redis = new RedisTaggedCache($store, $tagSet); - $store->shouldReceive('getPrefix')->andReturn('prefix:'); - $store->shouldReceive('connection')->andReturn($conn = m::mock(stdClass::class)); - - // Forever tag keys - $conn->shouldReceive('sscan')->once()->with('prefix:foo:forever_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key1', 'key2']]); - $conn->shouldReceive('sscan')->once()->with('prefix:bar:forever_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key3']]); - $conn->shouldReceive('del')->once()->with('key1', 'key2'); - $conn->shouldReceive('del')->once()->with('key3'); - $conn->shouldReceive('del')->once()->with('prefix:foo:forever_ref'); - $conn->shouldReceive('del')->once()->with('prefix:bar:forever_ref'); - - // Standard tag keys - $conn->shouldReceive('sscan')->once()->with('prefix:foo:standard_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key4', 'key5']]); - $conn->shouldReceive('sscan')->once()->with('prefix:bar:standard_ref', '0', ['match' => '*', 'count' => 1000])->andReturn(['0', ['key6']]); - $conn->shouldReceive('del')->once()->with('key4', 'key5'); - $conn->shouldReceive('del')->once()->with('key6'); - $conn->shouldReceive('del')->once()->with('prefix:foo:standard_ref'); - $conn->shouldReceive('del')->once()->with('prefix:bar:standard_ref'); - - $tagSet->shouldReceive('flush')->once(); - - $redis->flush(); - } - private function getTestCacheStoreWithTagValues(): ArrayStore { $store = new ArrayStore; From 6590b4c4eda14dbf8068f828fe65570cf91d66ef Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 17 Jan 2023 21:55:43 +0000 Subject: [PATCH 2/6] Apply fixes from StyleCI --- tests/Cache/CacheTaggedCacheTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Cache/CacheTaggedCacheTest.php b/tests/Cache/CacheTaggedCacheTest.php index e6a7dc6ce98e..1834bce56d4f 100644 --- a/tests/Cache/CacheTaggedCacheTest.php +++ b/tests/Cache/CacheTaggedCacheTest.php @@ -5,12 +5,8 @@ use DateInterval; use DateTime; use Illuminate\Cache\ArrayStore; -use Illuminate\Cache\RedisTaggedCache; -use Illuminate\Cache\TagSet; -use Illuminate\Contracts\Cache\Store; use Mockery as m; use PHPUnit\Framework\TestCase; -use stdClass; class CacheTaggedCacheTest extends TestCase { From 4b93e692c8fe9212f5d7b973b4808c8622d9f246 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 17 Jan 2023 16:49:53 -0600 Subject: [PATCH 3/6] add integration test --- tests/Integration/Cache/RedisStoreTest.php | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index ad89de93afd2..8b58cdd358a2 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -45,4 +45,86 @@ public function testItCanStoreNan() $this->assertTrue($result); $this->assertNan(Cache::store('redis')->get('foo')); } + + public function testTagsCanBeAccessed() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['people', 'author'])->put('name', 'Sally', 5); + Cache::store('redis')->tags(['people', 'author'])->put('age', 30, 5); + + $this->assertEquals('Sally', Cache::store('redis')->tags(['people', 'author'])->get('name')); + $this->assertEquals(30, Cache::store('redis')->tags(['people', 'author'])->get('age')); + + Cache::store('redis')->tags(['people', 'author'])->flush(); + + $keyCount = Cache::store('redis')->connection()->keys('*'); + $this->assertEquals(0, count($keyCount)); + } + + public function testTagEntriesCanBeStoredForever() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['people', 'author'])->forever('name', 'Sally'); + Cache::store('redis')->tags(['people', 'author'])->forever('age', 30); + + $this->assertEquals('Sally', Cache::store('redis')->tags(['people', 'author'])->get('name')); + $this->assertEquals(30, Cache::store('redis')->tags(['people', 'author'])->get('age')); + + Cache::store('redis')->tags(['people', 'author'])->flush(); + + $keyCount = Cache::store('redis')->connection()->keys('*'); + $this->assertEquals(0, count($keyCount)); + } + + public function testTagEntriesCanBeIncremented() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['votes'])->put('person-1', 0, 5); + Cache::store('redis')->tags(['votes'])->increment('person-1'); + Cache::store('redis')->tags(['votes'])->increment('person-1'); + + $this->assertEquals(2, Cache::store('redis')->tags(['votes'])->get('person-1')); + + Cache::store('redis')->tags(['votes'])->decrement('person-1'); + Cache::store('redis')->tags(['votes'])->decrement('person-1'); + + $this->assertEquals(0, Cache::store('redis')->tags(['votes'])->get('person-1')); + } + + public function testTagsCanBeFlushedBySingleKey() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['people', 'author'])->put('person-1', 'Sally', 5); + Cache::store('redis')->tags(['people', 'artist'])->put('person-2', 'John', 5); + + Cache::store('redis')->tags(['artist'])->flush(); + + $this->assertEquals('Sally', Cache::store('redis')->tags(['people', 'author'])->get('person-1')); + $this->assertNull(Cache::store('redis')->tags(['people', 'artist'])->get('person-2')); + + $keyCount = Cache::store('redis')->connection()->keys('*'); + $this->assertEquals(3, count($keyCount)); // Sets for people, authors, and actual entry for Sally + } + + public function testStaleEntriesCanBeFlushed() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['people', 'author'])->put('person-1', 'Sally', 1); + Cache::store('redis')->tags(['people', 'artist'])->put('person-2', 'John', 1); + + sleep(2); + + // Add a non-stale entry to people... + Cache::store('redis')->tags(['people', 'author'])->put('person-3', 'Jennifer', 1); + + Cache::store('redis')->tags(['people'])->flushStale(); + + $keyCount = Cache::store('redis')->connection()->keys('*'); + $this->assertEquals(4, count($keyCount)); // Sets for people, authors, and artists + individual entry for Jennifer + } } From 1a1521f4656083065f0ac5b7e3d7cdd6095d02ed Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 17 Jan 2023 17:44:47 -0600 Subject: [PATCH 4/6] update tests --- .github/workflows/tests.yml | 2 +- docker-compose.yml | 2 +- src/Illuminate/Cache/RedisTagSet.php | 9 +++++++-- src/Illuminate/Cache/RedisTaggedCache.php | 22 ++++++++++++++++++++-- tests/Integration/Cache/RedisStoreTest.php | 21 ++++++++++++++++++++- 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 63d32d9dfd14..d59a4ebe1533 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,7 +27,7 @@ jobs: - 33306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 redis: - image: redis:5.0 + image: redis:7.0 ports: - 6379:6379 options: --entrypoint redis-server diff --git a/docker-compose.yml b/docker-compose.yml index ef0a87950983..b3dee5dc9df3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: - "3306:3306" restart: always redis: - image: redis:5.0-alpine + image: redis:7.0-alpine ports: - "6379:6379" restart: always diff --git a/src/Illuminate/Cache/RedisTagSet.php b/src/Illuminate/Cache/RedisTagSet.php index ff47c5fb9b51..175694daed04 100644 --- a/src/Illuminate/Cache/RedisTagSet.php +++ b/src/Illuminate/Cache/RedisTagSet.php @@ -11,14 +11,19 @@ class RedisTagSet extends TagSet * * @param string $key * @param int $ttl + * @param string $updateWhen * @return void */ - public function addEntry(string $key, int $ttl = 0) + public function addEntry(string $key, int $ttl = 0, $updateWhen = null) { $ttl = $ttl > 0 ? now()->addSeconds($ttl)->getTimestamp() : -1; foreach ($this->tagIds() as $tagKey) { - $this->store->connection()->zadd($this->store->getPrefix().$tagKey, $ttl, $key); + if ($updateWhen) { + $this->store->connection()->zadd($this->store->getPrefix().$tagKey, $updateWhen, $ttl, $key); + } else { + $this->store->connection()->zadd($this->store->getPrefix().$tagKey, $ttl, $key); + } } } diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 0b0aa8762ad4..5f3d3faeeb50 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -4,6 +4,24 @@ class RedisTaggedCache extends TaggedCache { + /** + * Store an item in the cache if the key does not exist. + * + * @param string $key + * @param mixed $value + * @param \DateTimeInterface|\DateInterval|int|null $ttl + * @return bool + */ + public function add($key, $value, $ttl = null) + { + $this->tags->addEntry( + $this->itemKey($key), + ! is_null($ttl) ? $this->getSeconds($ttl) : 0 + ); + + return parent::add($key, $value, $ttl); + } + /** * Store an item in the cache. * @@ -35,7 +53,7 @@ public function put($key, $value, $ttl = null) */ public function increment($key, $value = 1) { - $this->tags->addEntry($this->itemKey($key)); + $this->tags->addEntry($this->itemKey($key), updateWhen: 'GT'); return parent::increment($key, $value); } @@ -49,7 +67,7 @@ public function increment($key, $value = 1) */ public function decrement($key, $value = 1) { - $this->tags->addEntry($this->itemKey($key)); + $this->tags->addEntry($this->itemKey($key), updateWhen: 'GT'); return parent::decrement($key, $value); } diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index 8b58cdd358a2..dad3c31ef4e0 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -94,6 +94,25 @@ public function testTagEntriesCanBeIncremented() $this->assertEquals(0, Cache::store('redis')->tags(['votes'])->get('person-1')); } + public function testIncrementedTagEntriesProperlyTurnStale() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['votes'])->add('person-1', 0, $seconds = 1); + Cache::store('redis')->tags(['votes'])->increment('person-1'); + Cache::store('redis')->tags(['votes'])->increment('person-1'); + + sleep(2); + + Cache::store('redis')->tags(['votes'])->flushStale(); + + $keyCount = Cache::store('redis')->connection()->keys('*'); + // var_dump($keyCount); + // var_dump(Cache::store('redis')->tags(['votes'])->getTags()->entries()->all()); + // var_dump(Cache::store('redis')->tags(['votes'])->getTags()->entries()->all()); + $this->assertEquals(0, count($keyCount)); + } + public function testTagsCanBeFlushedBySingleKey() { Cache::store('redis')->clear(); @@ -120,7 +139,7 @@ public function testStaleEntriesCanBeFlushed() sleep(2); // Add a non-stale entry to people... - Cache::store('redis')->tags(['people', 'author'])->put('person-3', 'Jennifer', 1); + Cache::store('redis')->tags(['people', 'author'])->put('person-3', 'Jennifer', 5); Cache::store('redis')->tags(['people'])->flushStale(); From d0cc95162f6feefae30dc23f98b604c5566b48cf Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 17 Jan 2023 17:57:45 -0600 Subject: [PATCH 5/6] remove commented code --- tests/Integration/Cache/RedisStoreTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index dad3c31ef4e0..dac63c17c7eb 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -107,9 +107,6 @@ public function testIncrementedTagEntriesProperlyTurnStale() Cache::store('redis')->tags(['votes'])->flushStale(); $keyCount = Cache::store('redis')->connection()->keys('*'); - // var_dump($keyCount); - // var_dump(Cache::store('redis')->tags(['votes'])->getTags()->entries()->all()); - // var_dump(Cache::store('redis')->tags(['votes'])->getTags()->entries()->all()); $this->assertEquals(0, count($keyCount)); } From af5de80941568980b32bde77dc31b19eb079e7cf Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 17 Jan 2023 18:33:32 -0600 Subject: [PATCH 6/6] use nx flag --- src/Illuminate/Cache/RedisTaggedCache.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 5f3d3faeeb50..b8120be95c03 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -53,7 +53,7 @@ public function put($key, $value, $ttl = null) */ public function increment($key, $value = 1) { - $this->tags->addEntry($this->itemKey($key), updateWhen: 'GT'); + $this->tags->addEntry($this->itemKey($key), updateWhen: 'NX'); return parent::increment($key, $value); } @@ -67,7 +67,7 @@ public function increment($key, $value = 1) */ public function decrement($key, $value = 1) { - $this->tags->addEntry($this->itemKey($key), updateWhen: 'GT'); + $this->tags->addEntry($this->itemKey($key), updateWhen: 'NX'); return parent::decrement($key, $value); }