Кэш запросов к БД: Cache::remember()
Зачем кэшировать запросы
Проблема:
- БД — главный bottleneck.
- Одинаковые SELECT повторяются.
- Агрегаты пересчитываются.
- Тяжёлые JOIN на каждый запрос.
Что даёт кэш:
- Меньше запросов в 5–50×.
- Быстрее ответы.
- Меньше CPU и I/O БД.
Правило: любой тяжёлый SELECT — кандидат.
remember()
Главный метод:
$value = Cache::remember('key', $ttl, function () {
return DB::table('users')->count();
});
Что делает:
- GET из кэша.
- Есть → возвращает.
- Нет → выполняет closure.
- PUT результат в кэш.
- Возвращает значение.
Правило: remember() — 90% сценариев кэша данных.
rememberForever()
Без TTL:
$value = Cache::rememberForever('key', fn () => Config::all());
Когда: данные меняются только при деплое.
⚠️ Без инвалидации — вечно устаревшие.
Правило: rememberForever — только для статики.
flexible()
Что это: новый метод Laravel.
$value = Cache::flexible('key', [60, 300], function () {
return Post::popular()->get();
});
Что означает:
- 60 сек — свежий.
- 300 сек — можно отдать устаревший (background refresh).
Что даёт:
- Мгновенный ответ.
- Обновление в фоне.
Правило: flexible — для часто читаемых данных.
Ключи кэша
Плохой ключ:
Cache::remember('users', 3600, fn () => User::all());
Почему плохо:
- Нет параметров.
- Коллизии между разными выборками.
Хороший ключ:
Cache::remember("users:active:page:{$page}", 3600, function () use ($page) {
return User::where('active', true)->paginate(20, ['*'], 'page', $page);
});
Правило: в ключе — все параметры выборки.
Соглашение по ключам
Формат:
{сущность}:{версия}:{параметры}
Примеры:
users:active:page:1posts:slug:hello-worldstats:daily:2026-10-16menu:header:v3
Правило: единый формат по проекту.
Параметры в ключе
Через строку:
$key = 'products:' . md5(json_encode($filters));
Через хэш:
$key = 'catalog:' . md5(serialize([$categoryId, $sort, $page]));
Что важно: избегать длинных ключей (ограничение Redis ~512 байт).
Правило: хэшировать длинные наборы параметров.
Пример: топ-N
public function popular(int $limit = 10): Collection
{
return Cache::remember(
"posts:popular:{$limit}",
now()->addMinutes(15),
fn () => Post::withCount('views')
->orderByDesc('views_count')
->limit($limit)
->get(),
);
}
Что даёт: топ-10 не считая каждый раз.
Правило: топ-N — кэшировать всегда.
Пример: справочник
public function all(): Collection
{
return Cache::remember(
'categories:all',
now()->addDay(),
fn () => Category::orderBy('name')->get(),
);
}
Что даёт: справочник — раз в сутки.
Правило: справочники — долгий TTL.
Пример: агрегат
public function dailyStats(Carbon $date): array
{
return Cache::remember(
"stats:daily:{$date->toDateString()}",
now()->addHours(2),
function () use ($date) {
return [
'orders' => Order::whereDate('created_at', $date)->count(),
'revenue' => Order::whereDate('created_at', $date)->sum('total'),
'users' => User::whereDate('created_at', $date)->count(),
];
},
);
}
Что даёт: один тяжёлый расчёт вместо трёх.
Правило: агрегаты — группировать в один cache-hit.
Пример: внешний API
public function rate(string $currency): float
{
return Cache::remember(
"currency:{$currency}",
now()->addHour(),
fn () => Http::get("https://api.fx.com/{$currency}")->json('rate'),
);
}
Что даёт: внешний API не вызывается чаще раза в час.
Правило: любой внешний API — через кэш.
add() и put()
put() — записать:
Cache::put('key', $value, 3600);
Cache::put('key', $value, now()->addHour());
add() — только если нет:
Cache::add('lock:job', true, 60);
Правило: add — для идемпотентности.
get() с дефолтом
$value = Cache::get('key', fn () => 'default');
$value = Cache::get('key', 'default');
Что даёт: безопасный fallback.
has() и missing()
if (Cache::has('key')) { /* ... */ }
if (Cache::missing('key')) { /* ... */ }
Правило: missing — читается лучше.
forget() и flush()
Удалить ключ:
Cache::forget('posts:popular:10');
Удалить всё:
Cache::flush();
⚠️ flush на общем Redis — убивает всё, включая сессии.
Правило: flush — не в проде.
Инкремент и декремент
Cache::increment('views:' . $post->id);
Cache::decrement('stock:' . $product->id);
Cache::increment('views:' . $post->id, 5);
Что даёт: атомарные счётчики.
Правило: счётчики — в Redis, не в БД.
Pull
$value = Cache::pull('token:' . $user->id);
Что делает: get + forget одним вызовом.
Когда: одноразовые токены.
TTL
Варианты:
Cache::put('key', $v, 3600); // секунды
Cache::put('key', $v, now()->addHour()); // Carbon
Cache::put('key', $v, null); // forever
Правило: TTL — всегда явно, не полагаться на дефолт.
Дефолтный TTL
config/cache.php:
'ttl' => env('CACHE_TTL', 3600),
Что важно: дефолт — явный для проекта.
Нагрев кэша
Проблема: первый запрос медленный.
Что делать: прогреть заранее.
Artisan-команда:
class WarmCache extends Command
{
protected $signature = 'cache:warm';
public function handle(): void
{
Cache::remember('posts:popular:10', now()->addHour(),
fn () => Post::popular()->limit(10)->get());
Cache::remember('categories:all', now()->addDay(),
fn () => Category::all());
}
}
Когда: после деплоя, в scheduler.
Правило: нагрев — после optimize:clear.
Декремент нагрузки
Что ускоряется:
- Топ-N — 10–100×.
- Справочники — 50×.
- Агрегаты — 20×.
- Внешние API — 100×.
Правило: кэш запросов — самый быстрый win.
Что НЕ кэшировать
- Личный кабинет.
- Токены в общем ключе.
- Данные с гарантированной свежестью.
- Персональные списки.
Правило: если сомневаешься — не кэшируй.
Метрики
Что смотреть:
- Hit rate по ключу.
- Размер значения.
- TTL попаданий.
- Время до первого miss.
Через Pulse: cache hit rate в UI.
Правило: каждый важный ключ — в мониторинге.
Практика
1. Найдите 5 тяжёлых SELECT.
2. Оберните каждый в remember().
3. Выберите осмысленные ключи.
4. Задайте TTL.
5. Замерьте до/после.
6. Настройте инвалидацию.
7. Сделайте cache:warm.
8. Прогоните в scheduler.
9. Проверьте Pulse hit rate.
10. Убедитесь: нет персональных данных в общих ключах.
Частые ошибки
1. Один ключ для разных фильтров:
Коллизия
Решение: параметры в ключе.
2. Долгий TTL на живое:
Устаревшее
Решение: TTL по частоте изменения.
3. Кэш без инвалидации:
Старьё
Решение: слушатели модели.
4. rememberForever на динамике:
Вечно устаревшее
Решение: TTL.
5. Personal data под общим ключом:
Утечка
Решение: user_id в ключе.
6. Не греть кэш:
Первый запрос медленный
Решение: cache:warm.
7. Cache::flush() в проде:
Снос сессий
Решение: forget по ключам.
8. Кэш объектов без Serializable:
Ошибка
Решение: массивы или implements.
Итог
remember()— основа.rememberForever— только статика.flexible— фон-refresh.- Ключ — все параметры.
- TTL — явно.
- Справочники, топ-N, агрегаты, API — кэшировать.
- Инвалидация — через слушатели.
- Прогрев —
cache:warm. - Метрики — Pulse.
- Не кэшировать персональное.
Дальше — тегирование кэша: Cache::tags().