Техника кеширования позволяет создавать более масштабируемые приложения, запоминания результаты некоторых запросов в быстрое in-memory хранилище. Однако, некорректно реализованное кеширование может сильно ухудшить впечатление пользователя о вашем приложении. Эта статья содержит некоторые базовые понятия о кешировании, различные правила и табу, которые я извлек из нескольких прошлых своих проектов. Ваш проект работает быстро и не имеет никаких проблем с производительностью?
Забудьте о кешировании. Серьезно :) Оно сильно усложнит операции чтения из базы без каких-либо бенефитов. Правда, Мохамед Саид в начале этой статьи делает некоторые вычисления и доказывает, что в некоторых случаях оптимизация приложения на миллисекунды способна сэкономить кучу денег на вашем AWS счету. Так что, если прогнозируемая экономия на вашем проекте больше, чем 1.86 долларов, возможно, реализация кеширования — неплохая идея. Когда приложения хочет получить некоторые данные из базы, например сущность Post по его id, оно формирует уникальный ключ кеширования для этого случая ('post_' . $id вполне подходяще) и пытается найти значение по этому ключу в быстром key-value хранилище(memcache, redis, или другое). Если значение там — то приложение использует его. Если нет, забирает его из базы данных и сохраняет в кеш по этому ключу для будущих использований.   Сохранять это значение в кеше навсегда не самая лучшая идея, поскольку эту сущность Post могут обновить, но приложение всегда будет получать старое, закешированное, значение.
Поэтому функции кеширования обычно спрашивают какое время это значение хранить. После истечения этого времени memcache или redis "забудут" про него и приложение возьмет свежее значение из базы. Пример: Здесь я кладу сущность Post в кеш на 15 минут (начиная с версии 5.8 laravel использует секунды в этом параметре, раньше там были минуты). Фасад Cache также имеет удобный метод remember для этого случая. Этот код делает ровно тоже самое, что и предыдущий: В документации Laravel есть глава Cache, которая обьясняет как установить необходимые драйверы для вашего приложения и главный функционал. Все стандартные драйверы Laravel хранят данные как строки. Когда мы просим сохранить в кеше экземпляр модели Eloquent, оно использует функцию serialize, чтобы получить строку из обьекта. Функция unserialize восстанавливает состояние обьекта когда мы получаем его из кеша. Почти любые данные могут быть закешированы. Числа, строки, массивы, обьекты (если они умеют корректно сериализоваться, смотрите описания функций по ссылкам ранее). Сущности Eloquent и коллекции легко могут быть закешированы и являются самыми популярными значениями в кеше приложений Laravel. Однако, использование других типов тоже практикуется довольно широко. Метод Cache::increment популярен для реализации различных счетчиков. Также, атомарные локи весьма полезны когда разработчики сражаются с race conditions. Первые кандидаты на кеширование — это запросы, которые выполняются очень часто, но их план выполнения не самый простой. Лучший пример — top-5 статей на главной странице, или последние новости. Кеширование таких значений способно сильно улучшить производительность главной страницы. Обычно, выборка сущностей по id, используя Model::find($id) работает очень быстро, но если эта таблица сильно загружена многочисленными запросами update, insert и delete, уменьшение количества select запросов даст хорошую передышку базе данных. Сущности с отношениями hasMany, которые будут загружаться каждый раз, тоже хорошие кандидаты на кеширование. Когда я работал на проекте с 10+ миллионов посетителей в день мы кешировали почти любой select запрос. Протухание ключа через заданное время помогает обновить данные в кеше, но это происходит не сразу. Пользователь может поменять данные, но некоторое время он будет продолжать видеть старую версию их в приложении. Обычный диалог на одном из моих прошлых проектов:  Это поведение весьма неудобно для пользователей и очевидное решение удалять из кеша старые данные, когда мы их обновили быстро приходит в голову. Этот процесс называется инвалидацией. Для простых ключей типа "post_%id%", инвалидация не очень сложная. События Eloquent могут помочь, или если ваше приложение генерит специальные события, такие как PostPublished или UserBanned это может быть еще проще. Пример с событиями Eloquent. Сначала надо создать классы событий. Для удобства я буду использовать абстрактный класс для них: Разумеется, по PSR-4, каждый класс должен лежать в своем файле. Настраиваем Post Eloquent класс (используя документацию): Создаем слушатель этих событий: Этот код будет удалять закешированные значения после каждого обновления или удаления сущностей Post. Инвалидация списков сущностей, таких как top-5 статей или последних новостей, будет чуток посложнее. Я видел три стратегии: Просто не трогать эти значения. Обычно, это не приносит никаких проблем. Ничего страшного в том, что новая новость появится в списке последних чуть позже (конечно, если это не большой новостной портал). Но некоторым проектам действительно важно иметь свежие данные в этих списках. Можно при каждом обновлении публикации, пытаться найти её в закешированных списках, и если она там есть, удалить это закешированное значение. Выглядит уродливо, зато работает. Если порядок элементов в списке неважен, то в кеше можно хранить только id записей. После получения id, можно сформировать список ключей вида 'post_'.$id и получить все значения используя метод Cache::many, который достает много значений из кеша за один запрос (это еще называется multi get). Инвалидация кеша не зря названа одной из двух трудностей в програмировании и весьма трудно в некоторых случаях. Кеширование сущностей с отношениями требует повышенного внимания. Этот код выполняет два SELECT запроса. Получение сущности по id и комментариев по post_id. Реализуем кеширование: Первый запрос был закеширован, а второй — нет. Когда драйвер кеша записывает Post в кеш, comments еще не загружены. Если мы хотим кешировать и их тоже, то мы должны загрузить их вручную: Теперь кешируются оба запроса, но мы должны инвалидировать значения 'post_'.$id каждый раз когда добавляется комментарий. Это не очень эффективно, поэтому лучше хранить кеш комментариев отдельно: Иногда сущность и отношение сильно связаны друг с другом и всегда используются вместе (заказ с деталями, публикация с переводом на нужный язык). В этом случае хранить их в одном кеше вполне нормально. Если на проекте реализована инвалидация, ключи кеширования генерируются как минимум в двух местах: для вызова Cache::get / Cache::remember и для вызова Cache::forget. Я уже встречался с ситуациями, когда этот ключ был изменен в одном месте, но не в другом и инвалидация ломалась. Обычный совет для таких случаев — константы, но ключи кеширования формируются динамически, поэтому я использую специальные классы, генерирующие ключи: Время жизни ключей также можно вынести в константы, ради лучшей читаемости. Эти 900 или 15*60 увеличивают когнитивную нагрузку при чтении кода. При реализации операций записи, таких как изменение заголовка или текста публикации, велик соблазн использовать метод getPost, написанный ранее: Пожалуйста, не делайте так. Значение в кеше может быть устаревшим, даже если инвалидация сделана корректно. Небольшой race condition и публикация потеряет изменения, сделанные другим пользователем. Оптимистические блокировки помогут хотя бы не потерять изменения, но количество ошибочных запросов может сильно возрасти. Лучшее решение — использовать абсолютно разную логику выборки сущностей для операций чтения и записи (привет, CQRS). В операциях записи всегда нужно выбирать свежее значение из базы данных. И не забывать о блокировках (оптимистичных или пессимистичных) для важных данных. Я думаю, это достаточно для вводной статьи. Кеширование весьма сложная и пространная тема, с ловушками для разработчиков, но прирост производительности иногда перевешивает все трудности.