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