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

Драйверы кэша, Cache API, оптимизация запросов, Octane, Redis.

Junior Какие драйверы кэша есть в Laravel и как работать с Cache?

Драйверы (CACHE_STORE в .env): file, database, redis, memcached, dynamodb, array (в памяти запроса, для тестов).

Cache::put('key', $value, 600);                        // TTL в секундах
Cache::put('key', $value, now()->addHour());
Cache::get('key', 'default');
Cache::remember('stats', 3600, fn () => $this->calcStats());   // взять или вычислить
Cache::rememberForever('key', fn () => ...);
Cache::forever('key', $value);
Cache::forget('key');
Cache::flush();                                         // всё хранилище (осторожно!)
Cache::has('key'); Cache::pull('key');                  // get + forget
Cache::increment('counter'); Cache::add('key', $v, 60); // только если нет

Теги (redis/memcached): Cache::tags(['users', $id])->put(...), сброс Cache::tags('users')->flush().

Atomic locks (redis):

Cache::lock('import', 10)->get(function () { /* только один процесс */ });

Правило: remember + осмысленные ключи + инвалидация при изменении данных (через события моделей/observer).

Middle Какие команды оптимизации запускают при деплое?
php artisan config:cache     # все конфиги → один файл, .env больше не читается
php artisan route:cache      # маршруты сериализованы (нельзя замыкания!)
php artisan view:cache       # прекомпиляция Blade
php artisan event:cache      # карта событий/слушателей
php artisan optimize         # всё сразу
php artisan optimize:clear   # сброс всех кэшей

Дополнительно:

  • composer install --no-dev --optimize-autoloader — classmap-автолоад;
  • OPcache в php.ini (проверить opcache.enable=1, validate_timestamps=0 на проде);
  • миграции: php artisan migrate --force;
  • перезапуск воркеров: queue:restart (долгоживущие процессы держат старый код).

Частый вопрос-ловушка: после config:cache хелпер env() возвращает null вне конфигов — поэтому env() вызывают только в config/*.php.

Middle Как устроено кэширование HTTP-ответов и сессий? Что такое Redis в стеке Laravel?

Redis — центральный компонент: кэш, сессии, очереди, блокировки, rate limiter, broadcasting:

'connections' => ['cache' => [...], 'default' => [...]]   // config/database.php

Рекомендуемый клиент — phpredis (C-расширение, быстрее predis). Разделяйте логические БД или префиксы для cache/queue/session.

Сессии (SESSION_DRIVER): file (по умолчанию), cookie (шифрованные, всё на клиенте), database, redis. API — session(['key' => $v]), $request->session()->get(...). Flash-данные — на один следующий запрос.

HTTP-кэширование:

return response($html)->header('Cache-Control', 'public, max-age=300');
// ETag/Last-Modified + 304 Not Modified

Middleware cache.headers. Для публичных страниц — полностраничный кэш (reverse proxy: nginx, Varnish, CDN) эффективнее любого PHP-кэша.

Балансировка: сессии в Redis/БД обязательны, если приложение за несколькими серверами (sticky sessions — костыль).

Senior Что такое Laravel Octane и как он меняет модель выполнения?

Octane запускает Laravel под высокопроизводительными серверами приложений (Swoole, RoadRunner, FrankenPHP): приложение загружается в память один раз и обслуживает тысячи запросов без повторного bootstrap'а. Рост RPS — в разы.

php artisan octane:start --server=roadrunner --workers=4

Главная ловушка — утечка состояния между запросами: приложение долгоживущее, поэтому:

  • нельзя хранить данные запроса в статических свойствах и свойствах синглтонов — они «переедут» в следующий запрос;
  • осторожно с app()->instance() и глобальным состоянием;
  • контейнер «прогревается» заново на каждый запрос, но синглтоны из bootstrap остаются;
  • утечки памяти теперь накапливаются — есть octane:reload и --max-requests.

Взамен: конкурентные задачи (Octane::concurrently), таблицы в shared memory (Swoole Table), тики.

Octane не нужен мелким сайтам; критичен для highload API.

Senior Как искать и устранять медленные запросы и «узкие места» приложения?

Диагностика:

  • Laravel Telescope (dev) / Debugbar — запросы, время, N+1;
  • DB::listen(fn ($q) => Log::info($q->sql, [$q->time])) или DB::enableQueryLog();
  • slow query log СУБД + EXPLAIN для подозрительных запросов;
  • APM на проде: New Relic, Sentry Performance, Horizon для очередей.

Типовые проблемы и решения:

Проблема Решение
N+1 with(), preventLazyLoading()
SELECT * select(['id','name']), pluck
Отсутствие индексов индексы под where/join/order by; составные — порядок важен
Тяжёлый COUNT при пагинации simplePaginate / cursorPaginate
Загрузка тысяч записей chunk, cursor, lazy
Дорогие вычисления в запросе кэш remember, денормализация, материализованные данные
Внешние HTTP в запросе очереди, Http::pool()

Системный уровень: Redis-кэш, OPcache, Octane, CDN для статики, HTTP-кэширование (Cache-Control, ETag).