Тегирование кэша: Cache::tags()
Зачем теги
Проблема:
- Много связанных ключей.
- Ручной forget каждого — ошибки.
- Инвалидация по сущности — сложно.
Что дают теги:
- Группировку ключей.
- Инвалидацию группы.
- Связь данных с сущностью.
Правило: тег — логическая группа ключей.
Поддержка драйверов
Что поддерживает tags:
- redis ✅
- memcached ✅
- array ✅
- database ❌
- file ❌
- dynamodb ❌
Правило: tags — только redis/memcached/array.
Базовый синтаксис
Запись:
Cache::tags(['posts', 'homepage'])->put('posts:popular', $data, 3600);
Чтение:
$data = Cache::tags(['posts', 'homepage'])->get('posts:popular');
Очистка тега:
Cache::tags(['posts'])->flush();
Что важно: при чтении указывать те же теги.
remember с тегами
Cache::tags(['posts', 'homepage'])->remember('posts:popular', 3600, function () {
return Post::popular()->limit(10)->get();
});
Правило: тег должен описывать ключ.
Инвалидация
Cache::tags(['posts'])->flush();
Что делает: удаляет все ключи с тегом posts.
Правило: flush по тегу — атомарная инвалидация группы.
Множественные теги
Ключ в двух тегах:
Cache::tags(['posts', 'user:5'])->put('user:5:posts', $posts, 3600);
Инвалидация:
// Все посты
Cache::tags(['posts'])->flush();
// Посты конкретного пользователя
Cache::tags(['user:5'])->flush();
Что даёт: ключ удаляется при flush любого из его тегов.
Правило: теги — связь между разными разрезами.
Пример: блог
Кэшируем посты:
Cache::tags(['posts', 'post:' . $post->id])->remember(
"post:{$post->id}",
now()->addHour(),
fn () => $post->toArray(),
);
Инвалидация при сохранении:
static::saved(function (Post $post) {
Cache::tags(['posts', 'post:' . $post->id])->flush();
Cache::tags(['homepage'])->flush();
});
Что даёт:
- Изменили пост → сбрасывается только он + связанные.
- Общий тег
posts— если нужен массовый сброс.
Пример: категории и товары
Товары категории:
Cache::tags(['products', 'category:' . $categoryId])->remember(
"products:category:{$categoryId}",
3600,
fn () => Product::where('category_id', $categoryId)->get(),
);
Инвалидация при изменении категории:
Category::saved(function (Category $category) {
Cache::tags(['category:' . $category->id])->flush();
});
Что даёт: изменил категорию — обновились её товары.
Пример: связь через внешние ключи
Комментарии к посту:
Cache::tags(['comments', 'post:' . $postId])->remember(
"comments:post:{$postId}",
600,
fn () => Comment::where('post_id', $postId)->get(),
);
Изменение поста:
Cache::tags(['post:' . $post->id])->flush();
Что даёт: инвалидируется и пост, и его комментарии.
Правило: связывать сущности через тег {entity}:{id}.
Tags и flush
Cache::flush() — не сбрасывает теги.
Cache::tags([...])->flush() — сбрасывает только группу.
Правило: flush() без тегов — не использовать.
Вложенные теги
⚠️ Вложенных тегов нет.
Что делать: плоский список тегов.
Cache::tags(['posts', 'user:5', 'category:3'])->remember(...);
Правило: теги — плоские, но может быть много.
Ограничения Redis
Что важно:
- Теги реализованы через множества.
- С каждым ключом — связанные sets.
- Много тегов — больше памяти.
Правило: не более 3–5 тегов на ключ.
Мониторинг
Что смотреть:
- Размер множеств тегов.
- Время flush.
- Количество ключей на тег.
Через redis-cli:
redis-cli -n 1 SMEMBERS 'tag:posts:keys'
Правило: большой тег (10000+) — разбить.
Инвалидация через события модели
Post:
protected static function booted(): void
{
static::saved(fn (Post $post) => self::forgetCache($post));
static::deleted(fn (Post $post) => self::forgetCache($post));
}
protected static function forgetCache(Post $post): void
{
Cache::tags(['posts', 'post:' . $post->id])->flush();
Cache::forget('homepage:posts');
}
Правило: события модели — централизованная инвалидация.
Теги vs префиксы
Префикс: posts:popular — просто строка.
Тег: ['posts'] — метка.
Разница:
- Префикс не помогает в flush.
- Тег даёт групповой flush.
Правило: теги — только для групповой инвалидации.
Теги vs версия
Альтернатива: версия в ключе.
$v = Cache::get('posts:version', 1);
$key = "posts:v{$v}:popular";
Что даёт: при инкременте версии — старые ключи игнорируются.
Когда: драйвер не поддерживает теги.
Правило: версия — fallback для file/database.
Что кэшировать с тегами
Хорошо:
- Списки сущностей.
- Детали сущности.
- Связанные данные.
- Страницы с общим layout.
Плохо:
- Глобальные агрегаты.
- Персональные данные.
Правило: теги — по сущностям.
Практика
1. Проверьте драйвер — redis?
2. Оберните топ-3 запроса в tags.
3. Сделайте инвалидацию через модель.
4. Свяжите пост и комментарии тегом.
5. Проверьте flush тега.
6. Замерьте hit rate.
7. Сделайте тег homepage для главной.
8. Сделайте тег post:{id} для деталей.
9. Настройте алерт на размер тега.
10. Проверьте в redis-cli.
Частые ошибки
1. Теги на file/database:
Не работают
Решение: redis или memcached.
2. Забыли тег при чтении:
Не найдёт
Решение: всегда те же теги.
3. Слишком много тегов:
Память растёт
Решение: 3–5 на ключ.
4. Один тег на всё:
Flush убивает всё
Решение: гранулярные теги.
5. Теги без инвалидации:
Устаревшее
Решение: события модели.
6. flush() без тега:
Снесёт всё
Решение: только с тегами.
7. Вложенные теги:
Не поддерживаются
Решение: плоский список.
8. Не мониторить размер:
Redis OOM
Решение: алерт.
Итог
- tags() — группа ключей.
- Только redis / memcached / array.
remember— с тегами.- flush по тегу — атомарно.
{entity}:{id}— стандарт.- 3–5 тегов максимум.
- Инвалидация через события модели.
- Версия в ключе — для file/db.
- Проверять через redis-cli.
- Не использовать теги без инвалидации.
Дальше — инвалидация кэша.