- Опубликовано: 15 июл 2026
- 41
Кэширование на пальцах: Redis, FastCGI cache и когда хватит просто заголовков
Часть 11 из 14 · Серия «Сервер в бою»
Прошлую статью мы закончили вопросом: зачем спрашивать базу о списке товаров, не менявшемся с прошлого вторника, — триста раз в минуту? Ответ «кэшировать» знают все. А вот дальше начинается мода: спроси в любом чате «как ускорить сайт» — и первым же ответом прилетит «поставь Redis». Redis — прекрасный инструмент, но как первый шаг это совет уровня «начни ремонт квартиры с покупки перфоратора»: инструмент серьёзный, а обои-то, может, и переклеивать не надо.
Правда о кэшировании в том, что это не инструмент, а иерархия — этажи от браузера посетителя до buffer pool базы, и на каждом этаже запрос можно перехватить и не пустить глубже. Главный принцип всей статьи умещается в одну строку: чем ближе к пользователю перехвачен запрос, тем дешевле он обошёлся серверу. Запрос, погашенный в браузере, стоит вам ноль. Дошедший до Redis — уже разбудил PHP. Поэтому строить кэширование правильно сверху вниз — и Redis в этой стройке последний этаж, а не первый. Пройдём по этажам.
Карта этажей
Путь запроса на типичном VPS — сверху вниз, и на каждом этаже стоит вопрос «а можно не идти дальше?»:
| Этаж | Кто кэширует | Что перехватывает | Цена промаха |
|---|---|---|---|
| 1. Браузер | Заголовки Cache-Control | Повторные визиты: статика вообще не запрашивается | Запрос летит на сервер |
| 2. Nginx | FastCGI cache | Целые готовые страницы: PHP даже не просыпается | Просыпается PHP |
| 3. PHP | Opcache | Компиляцию кода (уже включили в статье 9) | Перекомпиляция |
| 4. Приложение | APCu / Redis / файлы | Фрагменты, результаты запросов, сессии | Запрос в базу |
| 5. База | InnoDB buffer pool (статья 10) | Чтения с диска | Поход на диск |
Заметьте: этажи 3 и 5 у прилежного читателя серии уже обустроены. Сегодняшняя работа — этажи 1, 2 и 4. И идти будем в этом порядке, потому что каждый верхний этаж снижает нагрузку на все нижние.
Этаж 1: заголовки — кэш, который у вас уже есть
Самый дешёвый кэш в мире не требует ни демонов, ни памяти, ни кода — только сказать браузеру: «эту картинку можно не спрашивать год». В nginx это буквально несколько строк в конфиг хоста:
location ~* \.(css|js|png|jpg|jpeg|webp|svg|woff2|ico)$ {
expires 365d;
add_header Cache-Control "public, immutable";
access_log off; # вот где эта строчка уместна, в отличие от статьи 9
}
Что происходит: при первом визите браузер скачивает статику и запоминает; при всех последующих — даже не отправляет запрос. Повторный визит на сайт с сотней ресурсов превращается из ста запросов в несколько. immutable усиливает эффект: «файл не изменится никогда, не переспрашивай даже по F5».
Резонный вопрос: а как же обновления? Если CSS закэширован на год, посетители год будут видеть старый дизайн? Отсюда единственное обязательное условие этого этажа — версионирование имён: файл меняется — меняется имя (app.css?v=57, а лучше app.8f3a1c.css — это умеет любой сборщик, и Laravel Mix/Vite делает из коробки). Новое имя браузер не видел — скачает; старое пусть хоть вечно живёт в кэше. Такая схема разом снимает конфликт «кэшировать надолго vs обновлять мгновенно» — и это единственный этаж, где инвалидация решается бесплатно.
Для HTML-страниц заголовки — инструмент поаккуратнее (страница-то меняется), поэтому динамику мы перехватим на следующем этаже, где контроль в наших руках, а не в браузере посетителя.
Этаж 2: FastCGI cache — главный герой этой статьи
Вот этаж с самым драматичным соотношением «эффект/усилия» — и при этом наименее известный широкой публике, потому что мода смотрит на этаж ниже. Идея: nginx один раз получает от PHP готовую HTML-страницу, складывает к себе — и следующие запросы к этому URL отдаёт сам, из своего кэша, вообще не будя PHP-FPM. Ни PHP, ни базы, ни ORM — статика де-факто. Дешёвый VPS, отдававший 30 динамических страниц в секунду, начинает отдавать сотни и тысячи — потому что для nginx отдать файл из кэша и правда почти бесплатно.
Настройка. В http {} (например, отдельным файлом в conf.d) объявляем хранилище:
fastcgi_cache_path /var/cache/nginx/site1 levels=1:2
keys_zone=site1:10m max_size=512m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
А в хосте, в location ~ \.php$, включаем и — обязательно — учим обходить кэш там, где страницы личные:
set $skip_cache 0;
# залогиненные пользователи и корзины — мимо кэша:
if ($http_cookie ~* "wordpress_logged_in|session|cart") { set $skip_cache 1; }
if ($request_method = POST) { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin|/cart|/checkout|/account") { set $skip_cache 1; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/site1.sock;
fastcgi_cache site1;
fastcgi_cache_valid 200 10m; # успешные страницы живут 10 минут
fastcgi_cache_bypass $skip_cache; # для этих — сходить в PHP
fastcgi_no_cache $skip_cache; # и ответ не сохранять
add_header X-Cache-Status $upstream_cache_status; # отладочный маячок
}
Про блок skip_cache — с нажимом, потому что это главная грабля всего кэширования страниц: закэшировать личную страницу — значит показать её следующему посетителю. Классика жанра: «в корзине чужие товары», «зашёл на сайт — а я залогинен как Ирина». Правило железное: кэшируется только то, что одинаково для всех; всё, где есть кука сессии, POST или личные разделы, — bypass. Проверяется маячком:
curl -sI https://ваш-сайт/ | grep -i x-cache
# X-Cache-Status: MISS — первый запрос, сходили в PHP, сохранили
# X-Cache-Status: HIT — отдали из кэша, PHP спит
# X-Cache-Status: BYPASS — личное, честно сходили в PHP
Осталась инвалидация — та самая, про которую шутят, что в информатике есть только две сложные вещи: инвалидация кэша и придумывание имён. Для маленького сайта есть честный и недооценённый ответ: короткий TTL вместо умной инвалидации. Поставьте fastcgi_cache_valid 200 1m — и максимальная «несвежесть» любой страницы — минута, что для блога, каталога и новостника незаметно, а нагрузку срезает так же радикально: при 100 запросах в минуту на страницу PHP просыпается один раз вместо ста. Предельная форма этой идеи — микрокэширование с TTL в 1 секунду: страница почти реал-тайм, но наплыв в тысячу RPS превращается для PHP в 1 RPS. А полную очистку кэша достаточно уметь делать грубо — rm -rf /var/cache/nginx/site1/* после деплоя (автоматизируем в тринадцатой статье).
Этаж 4: кэш приложения — вот теперь поговорим про Redis
Спускаемся на этаж, куда мода предлагала заселиться сразу. Он нужен, когда страницу нельзя закэшировать целиком: интернет-магазин с корзиной, личные кабинеты, любой контент, собирающийся под пользователя. Целое кэшировать нельзя — но части можно: меню и список категорий одинаковы для всех, тяжёлая выборка «популярное за неделю» может жить час, сессии и счётчики должны лежать в быстром хранилище. Это кэш уровня кода: приложение само решает, что положить и на сколько.
И вот тут — развилка, которую пропускает совет «поставь Redis». Хранилищ для этого этажа несколько, и на одиноком VPS иерархия такая:
APCu — кэш в общей памяти самих PHP-процессов. Быстрее Redis (нет даже похода по сокету), ставится как обычное PHP-расширение, конфигурировать нечего. Для «закэшировать меню и тяжёлые выборки на одном сервере» — идеален и достаточен. Ограничения: живёт внутри одного сервера и очищается при перезапуске FPM.
Redis — отдельный сервис со своей памятью. Его честные, настоящие преимущества: кэш переживает перезапуск PHP; виден всем процессам (и cron-скриптам, и очередям); умеет структуры данных, очереди, атомарные счётчики, pub/sub; и главное на вырост — доступен по сети, то есть переживёт переезд на несколько серверов. Если у вас Laravel с очередями (QUEUE_CONNECTION=redis), сессии, rate-limiting — Redis оправдан на все сто.
Итого критерий без моды: нужен только кэш значений на одной машине — хватит APCu; нужны очереди, сессии, разделяемость между процессами или задел на второй сервер — Redis. Ставится он, к слову, за минуту (apt install redis-server), но два параметра проверьте обязательно, оба — про уроки прошлых статей:
# /etc/redis/redis.conf
bind 127.0.0.1 -::1 # НАРУЖУ НЕ СВЕТИТЬ. Открытый Redis без пароля —
# одна из самых взламываемых вещей в интернете
maxmemory 256mb # потолок; впишите его в неравенство памяти из статей 8–10
maxmemory-policy allkeys-lru # при переполнении вытеснять старое, а не падать с ошибкой
Без maxmemory Redis растёт неограниченно — и вы уже знаете, кто приходит к самым толстым процессам в три ночи. С maxmemory, но без политики allkeys-lru, — при заполнении начнёт отвечать ошибками на запись. Две строчки — и оба сценария закрыты.
Так почему всё-таки не начинать с Redis
Соберём аргумент целиком, он того стоит. Redis ускоряет только то, что код явно в него положил: это работа программиста, точечная и продолжающаяся. FastCGI cache перехватывает запрос раньше — PHP вообще не просыпается, — настраивается один раз в конфиге и ускоряет весь анонимный трафик скопом, ничего не зная о вашем коде. Заголовки перехватывают ещё раньше — запрос не покидает браузер. Для типичного сайта (блог, каталог, витрина, лендинг, новостник — всё, где основная аудитория анонимна) верхние этажи снимают 80–95% нагрузки за вечер настройки, и очень часто выясняется, что нижние этажи уже... не нужны. Redis, поставленный первым, эти проценты не тронет: анонимный запрос всё так же будит PHP, PHP всё так же собирает страницу — просто пара обращений внутри стала быстрее.
Правило на вынос: спускайтесь по этажам сверху вниз и останавливайтесь, когда стало быстро. Заголовки — всем и всегда. FastCGI cache — всем, у кого есть анонимный трафик. APCu — когда в коде появились повторяющиеся тяжёлые выборки. Redis — когда появились очереди, сессии, многопроцессные сценарии или второй сервер на горизонте. У каждого этажа есть билет, и билет на нижние этажи — это конкретная потребность, а не мода.
Грабли этажей
| Грабля | Симптом | Лечение |
|---|---|---|
| Кэш личных страниц | «У меня в корзине чужие товары» | skip_cache по куке/URI/POST — всегда |
| Заголовки на год без версий в именах | Посетители неделями видят старый CSS | Хэш/версия в имени файла |
| «Не могу понять, работает ли кэш» | Тюнинг вслепую | X-Cache-Status и curl -I |
| Redis наружу без пароля | Чужой майнер на сервере | bind 127.0.0.1 (или интерфейс WireGuard из статьи 7) |
| Redis без maxmemory | OOM-ночь из статьи 1 | maxmemory + allkeys-lru, вписать в бюджет RAM |
| Умная инвалидация на маленьком сайте | Недели на систему сброса кэша | Короткий TTL решает то же за одну строчку |
| Кэш «не обновился» после деплоя | «Я же исправил!» (знакомо по opcache) | Сброс кэшей — шаг деплоя, статья 13 |
| Замер скорости по первому запросу | «Кэш не помогает!» | Первый запрос — всегда MISS; мерить второй |
Что в итоге
Кэширование на пальцах — это пять этажей и одно правило: перехватывай запрос как можно выше. Пройдясь сверху вниз, типичный VPS-сайт получает: повторные визиты почти без запросов (заголовки), анонимный трафик без участия PHP (FastCGI cache с честным TTL вместо хитрой инвалидации), код без перекомпиляции (opcache), тяжёлые выборки из памяти (APCu или Redis — по билету, а не по моде) и базу, читающую из buffer pool (прошлая статья). Каждый этаж — вечер работы максимум, а вместе они дают тот самый эффект, ради которого люди покупают тарифы в четыре раза дороже.
По дороге у нас накопилась пачка обещаний вида «по расписанию»: чистить кэши, продлевать сертификаты, делать prune докеру, проверять бэкапы. Всё это держится на cron — механизме настолько же незаменимом, насколько и коварном: он молча работает и молча падает, о чём владельцы узнают через полгода. В следующей статье наведём порядок в автоматизации: cron против systemd-таймеров, как ловить упавшие задачи и почему MAILTO — не решение в 2026-м.
- 1 Сервер упал в три ночи: разбор полётов без паники
- 2 Логи, которые говорят: учимся читать journalctl, nginx и syslog
- 3 Fail2ban и брутфорс: как перестать кормить ботов
- 4 Мониторинг для одного сервера: Uptime Kuma, Netdata и здравый смысл
- 5 Диск закончился внезапно: куда деваются гигабайты на VPS
- 6 Обновления без страха: apt upgrade, который ничего не сломает
- 7 Свой VPN на VPS за полчаса: WireGuard без магии
- 8 Несколько сайтов на одном VPS: делим ресурсы без драки
- 9 Тормозит сайт — виноват не хостинг: тюним nginx и PHP-FPM
- 10 MySQL на маленьком VPS: почему база съедает всю память
- 11 Кэширование на пальцах: Redis, FastCGI cache и когда хватит просто заголовков вы здесь
- 12 Cron, systemd-таймеры и скрипты: автоматизируем рутину сервера скоро
- 13 Деплой без FTP в 2026-м: git pull, хуки и простой CI/CD на своём VPS скоро
- 14 Переезд на другой VPS без даунтайма: чек-лист миграции скоро
Была статья полезной: