Skip to content

Commit 16e9f0e

Browse files
committed
add prune stale tags command for redis
1 parent 27c346e commit 16e9f0e

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Illuminate\Cache\Console;
4+
5+
use Illuminate\Cache\CacheManager;
6+
use Illuminate\Cache\RedisStore;
7+
use Illuminate\Console\Command;
8+
use Symfony\Component\Console\Attribute\AsCommand;
9+
use Symfony\Component\Console\Input\InputArgument;
10+
11+
#[AsCommand(name: 'cache:prune-stale-tags')]
12+
class PruneStaleTagsCommand extends Command
13+
{
14+
/**
15+
* The console command name.
16+
*
17+
* @var string
18+
*/
19+
protected $name = 'cache:prune-stale-tags';
20+
21+
/**
22+
* The console command description.
23+
*
24+
* @var string
25+
*/
26+
protected $description = 'Prune stale cache tags from the cache (Redis only)';
27+
28+
/**
29+
* Execute the console command.
30+
*
31+
* @param \Illuminate\Cache\CacheManager $cache
32+
* @return void
33+
*/
34+
public function handle(CacheManager $cache)
35+
{
36+
$cache = $cache->store($this->argument('store'));
37+
38+
if (! $cache->getStore() instanceof RedisStore) {
39+
$this->components->error('Pruning cache tags is only necessary when using Redis.');
40+
41+
return 1;
42+
}
43+
44+
$cache->flushStaleTags();
45+
46+
$this->components->info('Stale cache tags pruned successfully.');
47+
}
48+
49+
/**
50+
* Get the console command arguments.
51+
*
52+
* @return array
53+
*/
54+
protected function getArguments()
55+
{
56+
return [
57+
['store', InputArgument::OPTIONAL, 'The name of the store you would like to prune tags from'],
58+
];
59+
}
60+
}

src/Illuminate/Cache/RedisStore.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
use Illuminate\Contracts\Cache\LockProvider;
66
use Illuminate\Contracts\Redis\Factory as Redis;
77
use Illuminate\Redis\Connections\PhpRedisConnection;
8+
use Illuminate\Redis\Connections\PredisConnection;
9+
use Illuminate\Support\LazyCollection;
10+
use Illuminate\Support\Str;
811

912
class RedisStore extends TaggableStore implements LockProvider
1013
{
@@ -235,6 +238,18 @@ public function flush()
235238
return true;
236239
}
237240

241+
/**
242+
* Remove all expired tag set entries.
243+
*
244+
* @return bool
245+
*/
246+
public function flushStaleTags()
247+
{
248+
foreach ($this->currentTags()->chunk(1000) as $tags) {
249+
$this->tags($tags->all())->flushStale();
250+
}
251+
}
252+
238253
/**
239254
* Begin executing a new tags operation.
240255
*
@@ -248,6 +263,51 @@ public function tags($names)
248263
);
249264
}
250265

266+
/**
267+
* Get a collection of all of the cache tags currently being used.
268+
*
269+
* @param int $chunkSize
270+
* @return \Illuminate\Support\LazyCollection
271+
*/
272+
protected function currentTags($chunkSize = 1000)
273+
{
274+
$connection = $this->connection();
275+
276+
// Connections can have a global prefix...
277+
$connectionPrefix = match (true) {
278+
$connection instanceof PhpRedisConnection => $connection->_prefix(''),
279+
$connection instanceof PredisConnection => $connection->getOptions()->prefix ?: '',
280+
default => '',
281+
};
282+
283+
$prefix = $connectionPrefix.$this->getPrefix();
284+
285+
return LazyCollection::make(function () use ($connection, $chunkSize, $prefix) {
286+
$cursor = $defaultCursorValue = '0';
287+
288+
do {
289+
[$cursor, $tagsChunk] = $connection->scan(
290+
$cursor,
291+
['match' => $prefix.'tag:*:entries', 'count' => $chunkSize]
292+
);
293+
294+
if (! is_array($tagsChunk)) {
295+
break;
296+
}
297+
298+
$tagsChunk = array_unique($tagsChunk);
299+
300+
if (empty($tagsChunk)) {
301+
continue;
302+
}
303+
304+
foreach ($tagsChunk as $tag) {
305+
yield $tag;
306+
}
307+
} while (((string) $cursor) !== $defaultCursorValue);
308+
})->map(fn (string $tagKey) => Str::match('/^'.preg_quote($prefix).'tag:(.*):entries$/', $tagKey));
309+
}
310+
251311
/**
252312
* Get the Redis connection instance.
253313
*

src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Auth\Console\ClearResetsCommand;
66
use Illuminate\Cache\Console\CacheTableCommand;
7+
use Illuminate\Cache\Console\PruneStaleTagsCommand;
78
use Illuminate\Cache\Console\ClearCommand as CacheClearCommand;
89
use Illuminate\Cache\Console\ForgetCommand as CacheForgetCommand;
910
use Illuminate\Console\Scheduling\ScheduleClearCacheCommand;
@@ -124,6 +125,7 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid
124125
'Optimize' => OptimizeCommand::class,
125126
'OptimizeClear' => OptimizeClearCommand::class,
126127
'PackageDiscover' => PackageDiscoverCommand::class,
128+
'PruneStaleTagsCommand' => PruneStaleTagsCommand::class,
127129
'QueueClear' => QueueClearCommand::class,
128130
'QueueFailed' => ListFailedQueueCommand::class,
129131
'QueueFlush' => FlushFailedQueueCommand::class,

0 commit comments

Comments
 (0)