10000 Cache tags beyond groups · Issue #18650 · cakephp/cakephp · GitHub
[go: up one dir, main page]

Skip to content

Cache tags beyond groups #18650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dereuromark opened this issue May 8, 2025 · 8 comments
Open

Cache tags beyond groups #18650

dereuromark opened this issue May 8, 2025 · 8 comments
Milestone

Comments

@dereuromark
Copy link
Member
dereuromark commented May 8, 2025

Description

https://book.cakephp.org/5/en/core-libraries/caching.html
We seem to have groups.
Those are however linked to the engine itself, not individual cache entries.
I think given this they are quite limited in its use - and not very practical.

In other systems they use cache tags, and those work on the entries themselves
https://symfony.com/doc/current/components/cache/cache_invalidation.html#using-cache-tags
That makes it possible to store them with specific entries, and then be able to invalidate them throughout that engine.
This is usually what they makes them so useful and practical, you dont need to set up engine configs to get different tags to work, and invalidating them.

We lack this functionality it seems.

Symfony added this with v4 it seems ( symfony/symfony#28096 + symfony/symfony#28097 )

@dereuromark dereuromark added this to the Future milestone May 8, 2025
@markstory
Copy link
Member

How would tags be applied to cache items? We don't really have an ItemInterface like symfony does.

@dereuromark
Copy link
Member Author

Yeah, I think we would need to open this up for such an interface approach maybe?
Is that too out of scope?
Our current solution is IMO too simple for some use cases with more cache usage and invalidation needs.

@LordSimal
Copy link
Contributor
LordSimal commented May 9, 2025

I know from other frameworks, that tagged cached items are not supported by all (but most) engines (primarily Redis, Memcache and APCu) - so this would also be a feature which can only be added to certain engines.

But yes, I'd like that since I also had situations, where I wanted to tag certain - different - cache keys with the same tag to make it easier to delete a certain group of cache entries (e.g. all cache keys of a certain user ID)

@dereuromark
Copy link
Member Author

The way symfony solved this seems to be a TagAwareAdapter
and the two dimensional approach of cache adapters

it needs one or two cache adapters: the first required one is used to store cached items; the second optional one is used to store tags and their invalidation version number (conceptually similar to their latest invalidation date). When only one adapter is used, items and tags are all stored in the same place. By using two adapters, you can e.g. store some big cached items on the filesystem or in the database and keep tags in a Redis database to sync all your fronts and have very fast invalidation checks

This should make it work for each engine in the end, since the tag dimension stores the references on the actual cache items.

@dereuromark
Copy link
Member Author
dereuromark commented May 9, 2025

It would also be nice if we then adopted the idea of built in Stampede prevention.

instead of waiting for the full delay before expiring a value, recompute it ahead of its expiration date. The Probabilistic early expiration algorithm randomly fakes a cache miss for one user while others are still served the cached value. You can control its behavior with the third optional parameter of get(), which is a float value called "beta".

@dereuromark
Copy link
Member Author

If we didnt plan on adding more serious cache functionality, I guess it would be simple enough to just re-use symfony components here.

// src/Service/SymfonyCacheService.php
namespace App\Service;

use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Redis;

class SymfonyCacheService
{
    protected $cache;

    public function __construct()
    {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $this->cache = new TagAwareAdapter(new RedisAdapter($redis));
    }

    public function remember($key, callable $callback, array $tags = [], float $beta = 1.0)
    {
        return $this->cache->get($key, function (CacheItemInterface $item) use ($callback, $tags) {
            $item->tag($tags);
            return $callback();
        }, $beta);
    }

    public function invalidateTags(array $tags)
    {
        $this->cache->invalidateTags($tags);
    }
}
// src/Controller/ArticlesController.php
use App\Service\SymfonyCacheService;

public function view($id)
{
    $cacheService = new SymfonyCacheService();
    $cacheKey = 'article_' . $id;

    $article = $cacheService->remember($cacheKey, function () use ($id) {
        return $this->Articles->get($id, ['contain' => ['Comments']]);
    }, ['article', 'comment']);

    return $this->set(compact('article'));
}
// src/Model/Table/ArticlesTable.php
use App\Service\SymfonyCacheService;

public function afterSave($event, $entity, $options = []): void
{
    if ($entity->isNew() || $entity->isDirty()) {
        $cacheService = new SymfonyCacheService();
        $cacheService->invalidateTags(['article']);
    }
}

@markstory
Copy link
Member

It would also be nice if we then adopted the idea of built in Stampede prevention.

This seems simpler to implement, as it could be a setting on the engine.

Our current solution is IMO too simple for some use cases with more cache usage and invalidation needs.

The tagging use cases can be handled with additional cache engine configurations. Instead of tagging specific keys, those keys can be put into different cache profiles that can be cleared independently of other profiles. I'm interested in knowing more about the other scenarios you have though.

@dereuromark
Copy link
Member Author

This seems simpler to implement, as it could be a setting on the engine.

Then I guess we could just focus on that part for now. That also seems like the more important issue anyways, in terms of uptime reliability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants
0