Кеширование в Laravel — полный конспект
Glossary overview

Кеширование в Laravel — полный конспект

Table of Contents

Кеширование — это сохранение результата тяжёлой операции, чтобы не выполнять её повторно. Вместо того чтобы каждый раз лезть в базу, считать агрегации или вызывать внешний API — берём готовый результат из быстрого хранилища.

Практика.

Без кеша:
    Запрос → Controller → Service → БД (100ms) → Ответ
    Запрос → Controller → Service → БД (100ms) → Ответ
    Запрос → Controller → Service → БД (100ms) → Ответ

С кешем:
    Запрос → Controller → Service → БД (100ms) → Сохранить в кеш → Ответ
    Запрос → Controller → Кеш (1ms) → Ответ
    Запрос → Controller → Кеш (1ms) → Ответ

1. Драйверы кеша

Драйвер — это где физически хранится кеш. Настраивается в config/cache.php и переменной CACHE_STORE в .env.

# .env
CACHE_STORE=redis
Драйвер       Скорость    Где хранит            Когда использовать
──────────────────────────────────────────────────────────────────────────
file           Медленно    storage/framework/    Локальная разработка,
                           cache/data/           маленькие проекты
redis          Быстро      Оперативная память    Продакшен, основной выбор
memcached      Быстро      Оперативная память    Альтернатива Redis
database       Средне      Таблица в БД          Когда нет Redis/Memcached
array          Мгновенно   Массив в PHP          Тесты (сбрасывается после запроса)
null           —           Никуда                Отключить кеш для отладки

Redis — стандарт для продакшена. Хранит данные в оперативной памяти, работает на порядок быстрее файлов и БД. Для Laravel ставится пакет predis/predis или PHP-расширение phpredis.

composer require predis/predis

2. Базовые операции

use Illuminate\Support\Facades\Cache;

// Записать в кеш на 60 минут
Cache::put('stats:total_orders', $count, now()->addMinutes(60));

// Записать навсегда (пока не удалишь вручную)
Cache::forever('settings:site_name', 'My Shop');

// Прочитать из кеша
$count = Cache::get('stats:total_orders');          // значение или null
$count = Cache::get('stats:total_orders', 0);       // значение или default

// Проверить наличие
Cache::has('stats:total_orders');    // true / false

// Удалить
Cache::forget('stats:total_orders');

// Удалить всё
Cache::flush();

remember() — главный метод

Самый частый паттерн: «если есть в кеше — верни, если нет — выполни Closure, сохрани результат и верни». Одна строка вместо трёх.

// Без remember() — вручную
$stats = Cache::get('dashboard:stats');

if ($stats === null) {
    $stats = $this->calculateStats();  // тяжёлый запрос
    Cache::put('dashboard:stats', $stats, now()->addHour());
}

return $stats;
// С remember() — одна строка
$stats = Cache::remember('dashboard:stats', now()->addHour(), function () {
    return $this->calculateStats();
});

// rememberForever() — без срока
$categories = Cache::rememberForever('all:categories', function () {
    return Category::all();
});

Хелпер cache()

Глобальный хелпер — альтернатива фасаду Cache:

// Прочитать
$value = cache('key');
$value = cache('key', 'default');

// Записать
cache(['key' => 'value'], now()->addMinutes(30));

// remember через хелпер — нет, только через фасад
$stats = Cache::remember('key', $ttl, fn () => ...);

3. TTL — время жизни кеша

TTL (Time To Live) — сколько кеш живёт до автоматического удаления. Задаётся через Carbon или в секундах.

// Через Carbon (читаемо)
Cache::put('key', $value, now()->addMinutes(30));
Cache::put('key', $value, now()->addHours(2));
Cache::put('key', $value, now()->addDay());
Cache::put('key', $value, now()->addWeek());

// В секундах
Cache::put('key', $value, 1800);   // 30 минут

// Навсегда
Cache::forever('key', $value);
Что кешировать              Рекомендуемый TTL
──────────────────────────────────────────────────────────
Курсы валют, цены акций     1–5 минут
Результаты поиска           5–15 минут
Статистика дашборда          15–60 минут
Список категорий            1–24 часа
Настройки сайта             forever (сбрасывать вручную)
Данные из внешнего API      Зависит от rate limits API

4. Ключи кеша — как называть правильно

Ключ — это строка, по которой кеш сохраняется и читается. Плохие ключи — частая причина багов: данные перезаписываются, кеш не инвалидируется, разные сущности получают одинаковый ключ.

// ПЛОХО — слишком общие
Cache::remember('products', ...);        // каких продуктов? всех? категории?
Cache::remember('stats', ...);           // чьи? за какой период?
Cache::remember('user', ...);            // какой пользователь?

// ХОРОШО — иерархические, с контекстом
Cache::remember('products:category:5', ...);
Cache::remember('stats:dashboard:2026-04', ...);
Cache::remember("user:{$userId}:profile", ...);
Cache::remember("orders:user:{$userId}:recent", ...);
Cache::remember("api:weather:kyiv:" . now()->format('Y-m-d'), ...);

Правило: ключ должен содержать всё, что влияет на результат. Если данные зависят от пользователя — ID в ключе. Если от даты — дата в ключе. Если от фильтров — фильтры в ключе.

// Кеш, который зависит от фильтров
$cacheKey = 'products:' . md5(json_encode($request->only(['category', 'sort', 'page'])));

$products = Cache::remember($cacheKey, now()->addMinutes(15), function () use ($request) {
    return Product::filter($request)->paginate(20);
});

5. Инвалидация кеша

Инвалидация — это удаление устаревшего кеша. Самая сложная часть кеширования: данные изменились, а в кеше лежит старая версия.

Ручная инвалидация

// Удалить конкретный ключ
Cache::forget('products:category:5');

// После обновления товара — сбросить связанный кеш
class ProductService
{
    public function update(Product $product, array $data): Product
    {
        $product->update($data);

        // Сбрасываем все кеши, которые зависят от этого товара
        Cache::forget("product:{$product->id}");
        Cache::forget("products:category:{$product->category_id}");
        Cache::forget('products:featured');

        return $product;
    }
}

Инвалидация через теги (только Redis/Memcached)

Теги позволяют группировать ключи и сбрасывать их все разом. Не нужно помнить каждый ключ по отдельности.

// Записываем с тегами
Cache::tags(['products'])->put('products:all', $products, now()->addHour());
Cache::tags(['products', 'category:5'])->put('products:category:5', $catProducts, now()->addHour());
Cache::tags(['products'])->put('products:featured', $featured, now()->addHour());

// Сбросить всё с тегом 'products' — одной командой
Cache::tags(['products'])->flush();

// Сбросить только категорию 5
Cache::tags(['category:5'])->flush();

Ограничение: теги работают только с Redis и Memcached. File и Database драйверы теги не поддерживают.

Инвалидация через события модели

// В Observer — сбрасывать кеш при изменении модели
class ProductObserver
{
    public function saved(Product $product): void
    {
        Cache::forget("product:{$product->id}");
        Cache::forget("products:category:{$product->category_id}");
    }

    public function deleted(Product $product): void
    {
        Cache::forget("product:{$product->id}");
        Cache::forget("products:category:{$product->category_id}");
    }
}

6. Кеширование в реальных сценариях

Кеширование в сервисе

class ProductService
{
    public function getFeatured(): Collection
    {
        return Cache::remember('products:featured', now()->addHour(), function () {
            return Product::where('is_featured', true)
                ->with('category')
                ->orderBy('sort_order')
                ->get();
        });
    }

    public function getByCategory(int $categoryId): Collection
    {
        return Cache::remember("products:category:{$categoryId}", now()->addMinutes(30), function () use ($categoryId) {
            return Product::where('category_id', $categoryId)
                ->where('is_active', true)
                ->get();
        });
    }
}

Кеширование ответа внешнего API

class CurrencyService
{
    public function getRate(string $from, string $to): float
    {
        $key = "currency:{$from}:{$to}";

        return Cache::remember($key, now()->addMinutes(5), function () use ($from, $to) {
            $response = Http::get("https://api.exchangerate.com/latest", [
                'base' => $from,
                'symbols' => $to,
            ]);

            return $response->json("rates.{$to}");
        });
    }
}

Кеширование конфигурации и настроек

class SettingsService
{
    public function get(string $key, mixed $default = null): mixed
    {
        $settings = Cache::rememberForever('app:settings', function () {
            return Setting::pluck('value', 'key')->toArray();
        });

        return $settings[$key] ?? $default;
    }

    public function set(string $key, mixed $value): void
    {
        Setting::updateOrCreate(['key' => $key], ['value' => $value]);

        // Сбрасываем кеш — при следующем вызове get() он пересоберётся
        Cache::forget('app:settings');
    }
}

Кеширование для пользователя

class DashboardService
{
    public function getStats(int $userId): array
    {
        return Cache::remember("dashboard:user:{$userId}", now()->addMinutes(15), function () use ($userId) {
            return [
                'total_orders'  => Order::where('user_id', $userId)->count(),
                'total_spent'   => Order::where('user_id', $userId)->sum('total_price'),
                'pending_count' => Order::where('user_id', $userId)->where('status', 'pending')->count(),
            ];
        });
    }
}

7. Cache Lock — атомарные блокировки

Lock предотвращает одновременное выполнение одной и той же операции несколькими процессами. Например, два запроса одновременно пытаются создать заказ — без блокировки может произойти двойное списание.

use Illuminate\Support\Facades\Cache;

// Получить блокировку на 10 секунд
$lock = Cache::lock('processing:order:42', 10);

if ($lock->get()) {
    try {
        // Только один процесс выполнит этот код одновременно
        $this->processOrder($order);
    } finally {
        $lock->release();
    }
}

// Или с помощью Closure — блокировка снимается автоматически
Cache::lock('processing:order:42', 10)->block(5, function () {
    // block(5) — ждать до 5 секунд, пока блокировка освободится
    $this->processOrder($order);
});

8. HTTP-кеширование (Response Cache)

Кеширование целых HTTP-ответов — самый агрессивный уровень. Запрос даже не доходит до контроллера.

Middleware для кеширования страниц

// Простой middleware, который кеширует весь ответ
namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Cache;

class CacheResponse
{
    public function handle($request, Closure $next, int $minutes = 60)
    {
        // Кешируем только GET-запросы
        if ($request->method() !== 'GET') {
            return $next($request);
        }

        $key = 'response:' . md5($request->fullUrl());

        return Cache::remember($key, now()->addMinutes($minutes), function () use ($next, $request) {
            return $next($request);
        });
    }
}
// Применение к роутам
Route::get('/catalog', [CatalogController::class, 'index'])
    ->middleware('cache.response:30');  // кеш на 30 минут

Cache-Control заголовки

Браузерное кеширование — ответ кешируется на стороне клиента, запрос вообще не уходит на сервер.

// В контроллере
return response()->json($data)
    ->header('Cache-Control', 'public, max-age=3600');  // браузер кеширует на 1 час

// Через middleware SetCacheHeaders
Route::get('/api/products', [ProductController::class, 'index'])
    ->middleware('cache.headers:public;max_age=3600');

9. Artisan-команды для кеша

# Очистить весь кеш приложения
php artisan cache:clear

# Кеш конфигурации (config/*)
php artisan config:cache       # создать
php artisan config:clear       # очистить

# Кеш маршрутов (routes/*)
php artisan route:cache        # создать
php artisan route:clear        # очистить

# Кеш шаблонов (views)
php artisan view:cache         # создать
php artisan view:clear         # очистить

# Кеш событий и провайдеров
php artisan event:cache
php artisan event:clear

# Всё разом очистить
php artisan optimize:clear
Команда               Что кеширует              Когда использовать
──────────────────────────────────────────────────────────────────────
config:cache          Все config-файлы          При деплое на продакшен
                      в один файл
route:cache           Все роуты в один файл     При деплое на продакшен
view:cache            Компиляция Blade          При деплое на продакшен
cache:clear           Весь runtime-кеш          Когда данные устарели
                      (Cache::put и т.д.)
optimize:clear        Всё вместе                При отладке проблем

Важно: config:cache и route:cache используются только на продакшене. В разработке они могут мешать — изменения в конфигах и роутах не будут подхватываться, пока не сбросишь кеш.

10. Кеширование запросов Eloquent

Eloquent не имеет встроенного кеширования запросов (в отличие от WordPress WP_Query с object cache). Кеширование делается вручную через Cache::remember().

// Кешировать результат Eloquent-запроса
$products = Cache::remember('products:active', now()->addHour(), function () {
    return Product::where('is_active', true)->get();
});

// Кешировать с пагинацией — страница в ключе!
$page = request('page', 1);
$products = Cache::remember("products:page:{$page}", now()->addMinutes(15), function () {
    return Product::paginate(20);
});

Ловушка с пагинацией: если не добавить номер страницы в ключ кеша, все страницы будут возвращать одни и те же данные (первую страницу).

11. Уровни кеширования — от быстрого к медленному

Уровень                  Где живёт          Сбрасывается
──────────────────────────────────────────────────────────────────────
PHP OPcache              RAM сервера        При деплое / restart PHP
Config/Route cache       Файл на диске      php artisan *:clear
Browser cache            Браузер клиента    По Cache-Control заголовкам
HTTP Response cache      Redis / файл       По TTL или вручную
Application cache        Redis / файл       Cache::forget() / TTL
  (Cache::remember)
Query cache              Redis / файл       Cache::forget() / TTL
  (ручное кеширование
   Eloquent-запросов)
Database query cache     Сама СУБД          Автоматически при
  (MySQL Query Cache)                       изменении таблицы

В типичном проекте используются 2–3 уровня: OPcache (автоматически), config/route cache (при деплое), application cache (в коде через Cache::remember).

12. Частые ловушки и ошибки

Ловушка 1: кешируешь null

// Closure вернула null (запись не найдена)
$product = Cache::remember('product:999', now()->addHour(), function () {
    return Product::find(999);  // null
});

// Теперь null закешировался на час!
// Даже если товар появится в БД — будет возвращаться null

// Решение: не кешировать null
$product = Cache::remember('product:999', now()->addHour(), function () {
    $product = Product::find(999);
    if ($product === null) {
        return false;  // или бросить исключение
    }
    return $product;
});

Ловушка 2: одинаковые ключи для разных данных

// ПЛОХО — один ключ для всех пользователей
Cache::remember('user:orders', ...);

// Пользователь A видит заказы пользователя B!

// ХОРОШО — ID пользователя в ключе
Cache::remember("user:{$userId}:orders", ...);

Ловушка 3: забыл сбросить кеш после изменения данных

// Обновили товар, но забыли сбросить кеш
$product->update(['price' => 599]);

// На сайте всё ещё старая цена, потому что кеш отдаёт старые данные
// Решение: всегда сбрасывай кеш рядом с update/delete
$product->update(['price' => 599]);
Cache::forget("product:{$product->id}");
Cache::forget("products:category:{$product->category_id}");

Ловушка 4: кешируешь Eloquent-модель с подгруженными связями

// Кешируешь модель с жадно загруженными связями
$order = Cache::remember("order:{$id}", now()->addHour(), function () use ($id) {
    return Order::with('items', 'user', 'coupon')->find($id);
});

// Проблемы:
// 1. В кеше лежит ОГРОМНЫЙ сериализованный объект
// 2. Если связь items изменилась — кеш не знает

// Лучше: кешировать данные, а не модели
$orderData = Cache::remember("order:{$id}", now()->addHour(), function () use ($id) {
    return Order::with('items')->find($id)?->toArray();
});

Ловушка 5: Cache Stampede (давка)

Кеш истёк, и 100 одновременных запросов все разом идут в БД пересчитывать результат. На долю секунды база перегружается.

// Решение: атомарная блокировка + кеш
$stats = Cache::lock('lock:dashboard:stats', 10)->block(5, function () {
    return Cache::remember('dashboard:stats', now()->addHour(), function () {
        return $this->calculateStats();
    });
});

// Первый запрос получит блокировку, пересчитает и закеширует
// Остальные 99 подождут 5 секунд и получат уже готовый кеш

Ловушка 6: config:cache в разработке

# Запустил на локалке
php artisan config:cache

# Теперь env() возвращает null везде кроме config-файлов!
# Потому что после config:cache Laravel не читает .env

# Решение: не использовать config:cache в разработке
php artisan config:clear

Правило: после config:cache функция env() работает только внутри config/*.php. Если ты вызываешь env() напрямую в контроллере или сервисе — получишь null. Всегда используй config('app.name') вместо env('APP_NAME').

13. Аналогия с WordPress

WordPress                          Laravel
─────────────────────────────────────────────────────────────
wp_cache_set('key', $val, '', 3600)   Cache::put('key', $val, 3600)
wp_cache_get('key')                   Cache::get('key')
wp_cache_delete('key')                Cache::forget('key')
Transients API                        Cache::remember()
  set_transient()                       (аналогичная концепция)
  get_transient()
Object Cache (Redis)                  Cache с Redis-драйвером
WP Super Cache / W3TC                 HTTP Response кеш
                                      (middleware)
delete_transient() при save_post      Cache::forget() в Observer

Основное отличие: в WordPress Transients API может падать в базу (таблица wp_options), если нет object cache. В Laravel ты явно выбираешь драйвер, и если стоит Redis — всё всегда в памяти.

Шпаргалка

ЗАПИСЬ / ЧТЕНИЕ
──────────────────────────────────────────────────────────────
Cache::put($key, $val, $ttl)        Записать
Cache::forever($key, $val)          Записать навсегда
Cache::get($key, $default)          Прочитать
Cache::has($key)                    Проверить
Cache::forget($key)                 Удалить
Cache::flush()                      Удалить всё
Cache::remember($key, $ttl, fn)     Прочитать или вычислить и сохранить
Cache::rememberForever($key, fn)    То же, без TTL

ТЕГИ (Redis/Memcached)
──────────────────────────────────────────────────────────────
Cache::tags([...])->put(...)        Записать с тегами
Cache::tags([...])->get(...)        Прочитать по тегу
Cache::tags([...])->flush()         Сбросить все ключи тега

БЛОКИРОВКИ
──────────────────────────────────────────────────────────────
Cache::lock($key, $sec)            Получить блокировку
$lock->get()                       Попробовать захватить
$lock->release()                   Освободить
$lock->block($wait, fn)            Ждать и выполнить

ARTISAN
──────────────────────────────────────────────────────────────
cache:clear                        Runtime-кеш
config:cache / config:clear        Конфигурация
route:cache / route:clear          Маршруты
view:cache / view:clear            Шаблоны
optimize:clear                     Всё разом

LiteSpeed и Redis — два уровня кеширования

LiteSpeed и Redis решают разные задачи и работают на разных уровнях. LiteSpeed — это веб-сервер со встроенным кешем. Redis — это хранилище данных в оперативной памяти. Вместе они закрывают кеширование на всех уровнях.

Веб-серверы: Apache, Nginx, LiteSpeed

Все три делают одно и то же — принимают HTTP-запрос от браузера и отдают ответ. Разница в архитектуре и возможностях.

                Apache            Nginx             LiteSpeed
────────────────────────────────────────────────────────────────────
Год выпуска     1995              2004              2003
Архитектура     Процесс/поток     Асинхронная       Асинхронная
                на каждый         (event-driven)    (event-driven)
                запрос
PHP             mod_php           Через PHP-FPM     Встроенный LSAPI
                (встроен)         (отдельный         (быстрее mod_php
                                  процесс)          и PHP-FPM)
.htaccess       Да                Нет               Да (совместим
                                                    с Apache)
Встроенный      Нет               Нет               Да (LSCache)
кеш             (нужен Varnish)   (FastCGI Cache)
cPanel          Стандартный       Нет (нужен        Замена Apache
                                  Engintron)        в один клик
Производи-      Средняя           Высокая           Высокая
тельность

Apache — старейший, стандарт на shared-хостингах и cPanel. Читает .htaccess в каждой папке, поэтому гибкий, но медленнее — на каждый запрос тратит ресурсы на чтение этих файлов.

Nginx — быстрее Apache за счёт асинхронной архитектуры. Не читает .htaccess — вся конфигурация в одном файле. Часто используется как reverse proxy перед Apache или самостоятельно с PHP-FPM.

LiteSpeed — совмещает плюсы обоих: быстрый как Nginx, но читает .htaccess как Apache. Для cPanel это важно — все редиректы и правила в .htaccess работают без изменений. Плюс встроенный LSCache и быстрый LSAPI для PHP.

Как проверить, какой сервер стоит

curl -I https://mysite.com

# В ответе будет заголовок:
# Server: LiteSpeed
# или
# Server: Apache/2.4.62

Если стоит Apache — можно переключить на LiteSpeed через WHM практически без изменений, потому что LiteSpeed понимает все конфиги Apache.

LSCache — кеш на уровне веб-сервера

LSCache — встроенный в LiteSpeed механизм кеширования. Он сохраняет готовый HTTP-ответ (HTML, JSON) и при повторном запросе отдаёт его из памяти, не запуская PHP вообще.

Без LSCache:
    Запрос → LiteSpeed → PHP → Laravel → БД → Ответ (~150ms)

С LSCache:
    Первый запрос → LiteSpeed → PHP → Laravel → БД → Ответ → LSCache запомнил
    Второй запрос → LiteSpeed → LSCache отдал из памяти → Ответ (~2ms)
                               PHP не запускается!

Что можно кешировать LSCache

Можно кешировать                   Нельзя кешировать
───────────────────────────────────────────────────
Каталог товаров                    Корзина пользователя
Страница товара                    Личный кабинет
Блог, статьи                      Чекаут / оплата
Главная                            Админка
API с публичными данными           API с личными данными

Правило простое: если страница одинакова для всех пользователей — LSCache. Если у каждого пользователя свои данные — LSCache не подходит, нужен Redis.

LSCache в WordPress

Ставишь плагин LiteSpeed Cache — он управляет LSCache из админки. Включил, настроил TTL, выбрал какие страницы кешировать — всё работает.

LSCache в Laravel

Готового плагина нет. Управление через HTTP-заголовки в ответе:

// Кешировать этот ответ на 1 час
return response()->json($data)
    ->header('X-LiteSpeed-Cache-Control', 'public, max-age=3600');

// Не кешировать (персональные данные)
return response()->json($userData)
    ->header('X-LiteSpeed-Cache-Control', 'no-cache');

// Кешировать с тегом — чтобы потом сбросить группу
return response()->json($products)
    ->header('X-LiteSpeed-Cache-Control', 'public, max-age=3600')
    ->header('X-LiteSpeed-Tag', 'products');

Удобнее вынести в middleware, чтобы не писать заголовки в каждом контроллере:

// app/Http/Middleware/LiteSpeedCache.php

namespace App\Http\Middleware;

use Closure;

class LiteSpeedCache
{
    public function handle($request, Closure $next, int $minutes = 60, string $tag = '')
    {
        $response = $next($request);

        // Кешируем только GET-запросы для неавторизованных пользователей
        if ($request->method() === 'GET' && !auth()->check()) {
            $response->header('X-LiteSpeed-Cache-Control', "public, max-age=" . ($minutes * 60));

            if ($tag) {
                $response->header('X-LiteSpeed-Tag', $tag);
            }
        } else {
            $response->header('X-LiteSpeed-Cache-Control', 'no-cache');
        }

        return $response;
    }
}
// Применение к роутам
Route::get('/catalog', [CatalogController::class, 'index'])
    ->middleware('lscache:60,products');

Route::get('/blog/{slug}', [BlogController::class, 'show'])
    ->middleware('lscache:1440,blog');  // 24 часа

// Личный кабинет — без LSCache
Route::get('/dashboard', [DashboardController::class, 'index']);  // no-cache автоматически

Сброс LSCache через Laravel

// При обновлении товара — сбросить LSCache по тегу
class ProductObserver
{
    public function saved(Product $product): void
    {
        // Сбросить LSCache для страниц с тегом 'products'
        header('X-LiteSpeed-Purge: tag=products');
    }
}

// Сбросить весь LSCache
header('X-LiteSpeed-Purge: *');

Redis — кеш на уровне приложения

Redis хранит данные в оперативной памяти. В Laravel он используется не только для кеша — одна установка закрывает сразу несколько задач.

Задача                    Без Redis              С Redis
──────────────────────────────────────────────────────────
Кеш (Cache)               file / database        redis — в 10-100x быстрее
Очереди (Queue)            database / sync        redis — стандарт
Сессии (Session)           file / database        redis — быстрее
Broadcasting               —                      redis (pub/sub)
Rate Limiting              —                      redis (атомарные счётчики)
Cache Lock                 —                      redis

Установка

# На сервере (Ubuntu)
sudo apt install redis-server
sudo systemctl enable redis-server

# PHP-расширение
sudo apt install php-redis

# Или через Composer (без расширения)
composer require predis/predis

Настройка в Laravel

# .env
CACHE_STORE=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null

Использование в коде

use Illuminate\Support\Facades\Cache;

// Кеш запроса к БД
$products = Cache::remember('products:featured', now()->addHour(), function () {
    return Product::where('is_featured', true)->get();
});

// Кеш данных конкретного пользователя
$stats = Cache::remember("user:{$userId}:stats", now()->addMinutes(15), function () use ($userId) {
    return [
        'orders_count' => Order::where('user_id', $userId)->count(),
        'total_spent'  => Order::where('user_id', $userId)->sum('total_price'),
    ];
});

// Кеш ответа внешнего API
$rate = Cache::remember('currency:usd:uah', now()->addMinutes(5), function () {
    return Http::get('https://api.exchangerate.com/latest?base=USD')->json('rates.UAH');
});

Когда нужен Redis

Нужен Redis                        Можно без Redis
───────────────────────────────────────────────────
Продакшен                          Локальная разработка
Средний/большой проект             Маленький блог
Персонализированный контент        Статичный сайт
Очереди (email, обработка)         Sync-очереди (всё сразу)
Несколько серверов                  Один сервер
Rate limiting                      Без ограничений

Как LSCache и Redis работают вместе

Запрос от клиента
    ↓
[LiteSpeed + LSCache]
    ├── Есть в кеше? → Отдал за ~2ms. PHP не тронут.
    └── Нет в кеше ↓
        [PHP / Laravel]
            ├── Cache::remember() → Есть в Redis? → Отдал за ~30ms. БД не тронута.
            └── Нет в Redis ↓
                [БД] → Запрос ~100ms → Сохранил в Redis → Ответ → LSCache запомнил

Следующий точно такой же запрос — LSCache отдаст мгновенно. Если LSCache истёк — Laravel возьмёт из Redis (не пойдёт в БД). Если и Redis истёк — только тогда пойдёт в базу. Три уровня защиты.

Пример: страница каталога

// routes/web.php
Route::get('/catalog', [CatalogController::class, 'index'])
    ->middleware('lscache:60,catalog');  // LSCache на 60 минут
// app/Http/Controllers/CatalogController.php

class CatalogController extends Controller
{
    public function index(ProductService $service)
    {
        // Redis-кеш внутри — страховка, если LSCache промахнулся
        $products = $service->getActive();
        $categories = $service->getCategories();

        return view('catalog.index', compact('products', 'categories'));
    }
}
// app/Services/ProductService.php

class ProductService
{
    public function getActive(): Collection
    {
        return Cache::remember('products:active', now()->addHour(), function () {
            return Product::where('is_active', true)
                ->with('category')
                ->orderBy('sort_order')
                ->get();
        });
    }

    public function getCategories(): Collection
    {
        return Cache::rememberForever('categories:all', function () {
            return Category::orderBy('name')->get();
        });
    }
}
// app/Observers/ProductObserver.php — сброс обоих кешей при изменении

class ProductObserver
{
    public function saved(Product $product): void
    {
        // Сброс Redis
        Cache::forget('products:active');
        Cache::forget("products:category:{$product->category_id}");

        // Сброс LSCache
        header('X-LiteSpeed-Purge: tag=catalog');
    }
}

Пример: личный кабинет (только Redis, без LSCache)

// LSCache не используется — у каждого пользователя свои данные
Route::get('/dashboard', [DashboardController::class, 'index']);

class DashboardController extends Controller
{
    public function index(DashboardService $service)
    {
        // Только Redis — кеш привязан к пользователю
        $stats = $service->getStats(auth()->id());

        return view('dashboard', compact('stats'));
    }
}

class DashboardService
{
    public function getStats(int $userId): array
    {
        return Cache::remember("dashboard:user:{$userId}", now()->addMinutes(15), function () use ($userId) {
            return [
                'orders'    => Order::where('user_id', $userId)->count(),
                'spent'     => Order::where('user_id', $userId)->sum('total_price'),
                'pending'   => Order::where('user_id', $userId)->where('status', 'pending')->count(),
            ];
        });
    }
}

JS-фильтры и кеширование

LSCache кеширует HTML, который сервер отдаёт браузеру. После того как браузер получил HTML — JavaScript работает как обычно, LSCache его никак не трогает.

Фильтр на фронтенде (JS фильтрует загруженные данные)

Всё работает без проблем. Страница закешировалась вместе со всеми товарами и JS-кодом. Пользователь кликает фильтр — JavaScript прячет/показывает элементы в DOM. Запроса на сервер нет.

LSCache отдал HTML (все 100 товаров + JS-код)
    ↓
Браузер: пользователь выбрал "до 500₴"
    ↓
JS: скрыл товары дороже 500 → запроса на сервер нет

Фильтр через AJAX (JS отправляет запрос на сервер)

Пользователь кликает фильтр, JS отправляет запрос на API. Этот AJAX-запрос — отдельный HTTP-запрос со своим URL, и LSCache может его тоже кешировать.

LSCache отдал HTML (страница каталога)
    ↓
Браузер: пользователь выбрал "до 500₴"
    ↓
JS: fetch('/api/products?max_price=500')
    ↓
LiteSpeed: отдельный запрос — проверяет кеш для ЭТОГО URL
    ↓
Есть → отдаёт из кеша
Нет  → PHP → Laravel → Redis/БД → ответ → LSCache запомнил

Каждая комбинация фильтров — отдельный URL, а значит отдельный ключ в LSCache. /api/products?max_price=500 и /api/products?max_price=1000 — два разных кеша.

Итого: что где использовать

Задача                                  Инструмент
──────────────────────────────────────────────────────────────
Публичные страницы (каталог, блог)      LSCache + Redis
Персональные данные (кабинет, корзина)  Только Redis
Очереди (email, обработка)              Redis
Сессии                                  Redis
Ответы внешнего API                     Redis (Cache::remember)
Настройки сайта                         Redis (Cache::rememberForever)
Конфиги/роуты при деплое                php artisan config:cache

WordPress-проекты                       LSCache плагин + Redis Object Cache
Laravel-проекты                         LSCache через заголовки + Redis