Как ускорить Laravel приложение: кэширование через Redis или Memcached

Как ускорить Laravel приложение: кэширование через Redis или Memcached

Недавно я столкнулся с проблемой, знакомой многим разработчикам: приложение на Laravel, которое отлично работало на этапе разработки, начало заметно тормозить под реальной нагрузкой. Time to First Byte вырос до 800-900 мс, база данных захлёбывалась от повторяющихся запросов, а клиент начал задавать неприятные вопросы про «жуткие тормоза». В этой статье расскажу, как я решил эту проблему с помощью кэширования через Redis, и поделюсь конкретными кейсами с кодом.

Почему я выбрал Redis, а не Memcached

Прежде чем идти дальше, объясню свой выбор. Я тестировал оба решения на одном проекте и вот к каким выводам пришёл:

Redis выиграл для меня по нескольким причинам:

  • Поддержка сложных структур данных (списки, множества, хэши) — очень важно для тегированного кэша.
  • Persistence на диск — при перезапуске сервера кэш не теряется полностью.
  • Встроенные механизмы pub/sub, которые я использовал для инвалидации кэша между несколькими серверами.
  • Нативная интеграция с Laravel через пакет predis/predis или phpredis.

Memcached проще и для некоторых задач чуть быстрее, например для простых key-value операций, но для проекта с большим количеством связанных данных Redis показал себя практичнее.

Шаг 1: Настройка окружения

Первым делом устанавливаем PHP-расширение phpredis — оно быстрее чем predis за счёт того что написано на C:

sudo apt-get install php-redis

Или через composer, если все же предпочитаете predis:

composer require predis/predis

В .env файле настраиваем подключение:

CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_CLIENT=phpredis

В config/database.php проверяем секцию redis:

'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'),
    'default' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_DB', 0),
    ],
],

Шаг 2: Первая победа — кэширование тяжёлых запросов

В моём проекте была страница каталога товаров с фильтрами, которая генерировала запрос на 200+ строк SQL с несколькими джойнами. Каждый запрос занимал 150-200 мс. Вот как я это исправил:

public function getProductsByCategory($categoryId, $filters = [])
{
    $cacheKey = 'products.category.' . $categoryId . '.' . md5(serialize($filters));
    
    return Cache::remember($cacheKey, now()->addMinutes(30), function () use ($categoryId, $filters) {
        return Product::