Skip to content

Кэширование в 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-логику.

См. также

Сайт обновлен и проверен: