Кэширование в nginx
Пример задачи, где требовалось ускорить отдачу публичных редко изменяющихся данных и снять лишнюю нагрузку с приложения за счет кэширования на уровне nginx.
Задача
Обеспечить максимально быструю и стабильную отдачу публичных данных для гостевого GET-запроса, по возможности без лишнего прохождения через PHP-FPM и базу данных.
Контекст
Речь о публичном endpoint-е, ответ которого:
- одинаков для всех пользователей;
- не зависит от пользовательской сессии;
- меняется редко;
- может запрашиваться многократно.
В данном случае это был API endpoint для получения списка таймзон.
Данные по смыслу почти являются "статическими", но технически сформированы приложением.
Если на каждый запрос ходить в приложение и далее в БД, система тратит ресурсы на работу, где ответ можно безопасно переиспользовать.
Рассмотренные варианты
1. Оставить всё без web-server cache
Плюсы:
- минимальная сложность;
- не требуется дополнительная конфигурация
nginx.
Минусы:
- каждый повторный запрос проходит через
PHP-FPM; - растет лишняя нагрузка на backend;
- ускорение ограничено только оптимизацией приложения.
2. Кэшировать ответ только на уровне приложения
Например, через Redis или другой backend cache внутри приложения.
Плюсы:
- гибкая логика инвалидации;
- удобно управлять кэшем из backend-кода.
Минусы:
- запрос всё равно доходит до приложения;
PHP-FPMостается в критическом пути;- для совсем простого публичного ответа это не самый дешевый путь.
3. Вынести кэширование на уровень nginx
Плюсы:
- повторные запросы можно отдавать прямо на edge-слое;
- снижается нагрузка на
PHP-FPMи приложение; - уменьшается latency для гостевых запросов;
- решение хорошо подходит для публичных
GET-ответов с редкими изменениями.
Минусы:
- нужно аккуратно ограничить область применения;
- важно не включить такой кэш для пользовательских или чувствительных данных;
- инвалидация и правила bypass должны быть продуманы заранее.
Выбранное решение
Для конкретного публичного endpoint-а был настроен точечный fastcgi_cache в nginx, без попытки включать глобальный cache для всего приложения.
Кэширование ограничили безопасным сценарием:
- только один публичный endpoint;
- только
GET-запросы; - только успешные
200-ответы; - отдельный cache key по схеме, методу, host и URI;
- диагностический заголовок
X-Cache-Status.
Дополнительно были включены:
fastcgi_cache_lockдля защиты от одновременного прогрева одного и того же ключа;fastcgi_cache_background_updateдля фонового обновления;fastcgi_cache_use_staleдля отдачи устаревшего ответа при обновлении или временной ошибке upstream.
Прогрев и инвалидация
Прогрев кэша происходит естественным образом:
- первый гостевой
GET-запрос попадает в приложение; nginxсохраняет успешный200-ответ вfastcgi_cache;- последующие запросы обслуживаются уже из кэша.
Чтобы несколько одновременных запросов не прогревали один и тот же ключ параллельно, используется fastcgi_cache_lock.
Инвалидация строится не через явный purge, а через TTL:
- успешный ответ считается актуальным
24часа; - после истечения TTL кэш обновляется следующим запросом;
- на время обновления или кратковременной ошибки upstream может временно отдаваться stale-ответ.
Для такого типа публичных редко меняющихся данных этого достаточно: схема остается простой, предсказуемой и не требует отдельного механизма ручной очистки.
Почему был выбран именно этот вариант
Это решение дает лучший баланс между скоростью и контролируемостью:
- не требует усложнять прикладной код ради очень локальной задачи;
- убирает приложение из пути повторных гостевых запросов;
- не создает рисков для приватных данных, так как кэш включается только точечно;
- позволяет прозрачно диагностировать поведение через
X-Cache-Status.
Результат
В результате публичные редко меняющиеся данные отдаются заметно эффективнее:
- повторные гостевые запросы обслуживаются быстрее;
- уменьшается лишняя нагрузка на
PHP-FPM; - поведение кэша остается контролируемым и безопасным;
- решение не требует вводить глобальный full-page cache или усложнять backend-логику.