Прозрачное кэширование в nginx для всех и каждого

Представим, что у нас есть сайт, на который регулярно дают ссылки с хабра.
Нам нужно подготовить его к резким всплескам посещаемости. Как это сделать?
С версии 0.8.46 в nginx появились опции, позволяющие легко и просто настроить прозрачное кэширование для анонимных пользователей.
Для работы этой схемы от сайта требуется очень мало: достаточно лишь не начинать сессию, то есть не отправлять сессионную куку, без явной на то необходимости. Редкий сайт нельзя довести до такого состояния, а значит большинство сайтов можно защитить от резких всплесков посещаемости с помощью nginx с минимальными затратами сил и времени.

Научим сайт начинать сессию только когда она нужна

Для широкораспространенных сайтов на PHP это можно сделать выставив в ноль session.auto_start в php.ini или, лучше, в настройках виртуального хоста Apache. Также нам нужно задать подходящие имя для сессионной куки:

php_admin_value session.name "session"
php_admin_value session.auto_start 0

Если сайт сам стартует сессию в коде, то session_start() следует заменить на:

if (!empty($_COOKIE[session_name()]) || $_SERVER['REQUEST_METHOD'] == 'POST') {
	session_id() || session_start();
}

Так, сессия будет начата только если

  • куки с сессией уже есть или если
  • нам отправляют форму, например, с логином и паролем.

Что нам и требовалось.

Настроим nginx на кэширование запросов без сессионной куки

После того как мы добились того, что сайт не отправляет сессионную куку гостям сайта, настроим nginx на сохранение в кэш запросов без сессионной куки.
В любом удобном месте, например, в конфиге виртуального хоста nginx перед блоком server, добавим строки:

proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=cache:30m max_size=1G;
proxy_temp_path /var/lib/nginx/proxy 1 2;
proxy_ignore_headers Expires Cache-Control;
proxy_cache_use_stale error timeout invalid_header http_502;
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;

Не забываем создать соответствующие каталоги:

mkdir -p  /var/lib/nginx/cache
chown -R www-data /var/lib/nginx/cache
chmod 700 /var/lib/nginx/cache

В соответствующий блок location добавим выделенные три строки:

location / {
	....
	proxy_cache cache;
	proxy_cache_valid 10m;
	proxy_cache_valid 404 1m;
	....
	proxy_pass http://backend;
}

Перезапускаем nginx, удаляем куки из браузера и наслаждаемся скоростью работы сайта.

Тонкая настройка кэширования

Если вы хотите чтобы кэшировались только некоторые страницы, или, например, чтобы некоторые адреса кэшировались на время большее 10 минут, для явного задания времени кэширования используйте:

header("X-Accel-Expires: $seconds");

Для полного отключения кэширования, например, счетчиков, используйте:

header("X-Accel-Expires: 0");

Проверка на прочность

Проверим главную страницу сайта на прочность в сто потоков:

ab -n 1000 -c 100 http://www.example.com/

C использованием кэширования тестовый сайт легко выдавал более 100 rps, оно и понятно.
Без кэша — предсказуемо показывал 503 ошибку на ~70% запросов.

Принудительное обновление кэша

Если в директиву proxy_cache_bypass изменить так:

proxy_cache_bypass $cookie_session $http_x_update;

То можно будет принудительно обновлять кэш страницы при необходимости:

curl -s -o /dev/null  -H "X-Update: 1" www.example.com

Заголовок лучше заменить на любой другой, известный только вам.