📝

cache:prune-stale-tagsは何をしているのか

2023/11/24に公開

アップグレードガイド 10.x Laravel に記載のRedisキャッシュタグについて、Artisanコマンドでcache:prune-stale-tagsを定期実行してください、と記載があります。
何だこれ、ということで調べてみました。

Laravelキャッシュファサードのタグ機能


undocument cache tags due to complexity of implementation · laravel/docs@63dde39

Taylor御大が自らドキュメントを削除しています...。おいおい。

タグ機能は、複数のキーに目印(タグ)をつける、というもので「複数のキーをまとめて削除する」みたいなときに便利な機能です。
Laravel10へのアップグレード時に、大きな改修が入って性能が向上したらしいです。
その改修の影響で、タイトルのcache:prune-stale-tagsの定期実行が必要になった模様。

複数のタグを付け足ときの奇妙な挙動


Flushing entries with 2 tags in RedisTaggedCache doesn't clean up properly · laravel/framework · Discussion #46106

↑のIssueで記載がありますが、複数タグをセットしている場合に、片側でflush()を呼んだ後で、flushを読んでいない側の参照が残ってしまうようです。
tinkerを使って調べてみました。

> Cache::tags(['a', 'b'])->put(1, 10);
= true

> Cache::tags(['a', 'b'])->put(2, 11);
= true

> Cache::tags(['a', 'b'])->put(3, 12);
= true

> $redis = Cache::getRedis();
= Illuminate\Redis\RedisManager {#1686}

> $redis->keys('*')
= [
    "laravel_cache:85142c9a6bd23d13c2ff4925786ac610a07f9992:1",
    "laravel_cache:tag:a:entries",
    "laravel_cache:tag:b:entries",
    "laravel_cache:85142c9a6bd23d13c2ff4925786ac610a07f9992:2",
    "laravel_cache:85142c9a6bd23d13c2ff4925786ac610a07f9992:3",
  ]

> $redis->zrange('laravel_cache:tag:a:entries', 0, 10)
= [
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:1",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:2",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:3",
  ]

> $redis->zrange('laravel_cache:tag:b:entries', 0, 10)
= [
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:1",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:2",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:3",
  ]

> Cache::tags(['a'])->flush();
= true

> $redis->keys('*')
= [
    "laravel_cache:tag:b:entries",
  ]

> $redis->zrange('laravel_cache:tag:b:entries', 0, 10)
= [
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:1",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:2",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:3",
  ]

ここで、cache:prune-stale-tagsの呼び出し元を手繰っていくと、\Illuminate\Cache\RedisTagSet::flushStaleEntries にたどり着きます。

     /**
     * 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, Carbon::now()->getTimestamp());
            }
        });
    }

zremrangebyscoreは、ソート済みセットから特定の範囲のスコアを持つエントリを削除する機能です。
ゼロから実行時点のUnixtimeまで、ということですね。
で、スコアを見てみます。

> $redis->zrange('laravel_cache:tag:b:entries', 0, 10, 'withscores')
= [
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:1" => "1700631234",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:2" => "1700631265",
    "85142c9a6bd23d13c2ff4925786ac610a07f9992:3" => "1700631292",
  ]

> $redis->keys('*')
= [
    "laravel_cache:tag:a:entries",
    "laravel_cache:tag:b:entries",
    "laravel_cache:85142c9a6bd23d13c2ff4925786ac610a07f9992:3",
  ]

これで、ようやくcache:prune-stale-tagsの挙動がわかりました。
TTLを超過していて、かつ削除されていないエントリを削除する、というごみ掃除の機能ということです。
...もしかして、複数タグを使っていなければ実行しなくて良いのでは?

Torana tech blog

Discussion