Comments 83
Сорри, добавил. Тут в хабраредакторе сломался предпросмотр: жуть как неудобно статью форматировать. Вместо превью выдается что-то типа <redirect_url>http://habrahabr.ru/blogs/hi/72539/</redirect_urlok. Вот и приходится «на живом» править.
Firefox. А при чем тут браузер? Это прямо выдача в HTML такая. Видимо, забыли шаблон наложить на XML, либо что-то подобное…
Что делать со страницами, которые полностью динамические? Как принудительно отключить для них кеширование и нужно ли это делать?

Как бы вы посоветовали сделать так, чтобы ключ кеширования можно было бы устанавливать в самом PHP? К примеру, кешировать страницу в зависимости от группы пользователей?
Мне кажется что тут уже дело даже не в жинксе, можно средствами пхп делать тот же кеш и им управлять, добавился комент — переписали кеш если это возможно, инструментов очень много и все зависит от типа сайта. И не бойтесь пользоваться кешированием через пхп, я понимаю что это ресурсозатратно, но доступ к базе и отдача осуществляется намного дольше. А харды у нас сегодня дешевле процов. Если же сайт «очень динамичен» и очень много пользователей а решение нужно с серверной стороны, то думаю что поставив кеш даже на 30 секунд будет видно спад нагрузки. В основном время от прочтения топика и коментирования проходит не меньше.
Кэш PHP на уровне ob_start() — это, конечно, лучше, чем отсутствие полностраничного кэша. Однако все равно потери будут порядка 5-10 мс на вызов, в то время как в случае nginx они практически нулевые.

Что касается
> Как бы вы посоветовали сделать так, чтобы ключ кеширования можно было бы устанавливать в самом PHP?
то использовать кэш nginx в таком режиме не получится: ведь чтобы определить ключ, вам потребуется запустить PHP-скрипт, а это уже промах мимо кэша по определению. Вся суть nginx-кэширования в том, чтобы не допускать ряд запросов вообще до PHP.

Я рекомендую всю логику кэширования прописывать в едином месте: либо в конфиге nginx (и хранить его в системе контроля версий, конечно же), либо — в PHP (но тогда nginx-кэширование не используется). А чтобы для разных групп юзеров кэши были разные, установите текущую группу в куку $group, а потом замешайте $cookie_group (переменная такая в nginx создается для этой куки) в ключ кэширования. Это один из вариантов.
> в то время как в случае nginx они практически нулевые.
а разве nginx не блокируется на файловом IO?
Имелись ввиду страницы, содержимое которых генерируется в зависимости от того, какой пользователь залогинен.
Респект Диме за статью, ее прочитал еще в рассылке на Алехе.

я тут сейчас модуль с маемкешом заканчиваю…
вот только не надо, что изобретаю велосипед… модуль нужен для записи в мемкеш (типа REST).
так вот, о чем я…

он в 80 раз быстрее работает, чем то-же самое ранее делали через пхп.
Это мегабайты, размер shared memory для еще более быстрого доступа к кэшу. Документация рулит.
Время хранения задается в той же строчке примерно так:
fastcgi_cache_path /tmp/nginx/ levels=1:2 keys_zone=fastcgi_cache:16m max_size=256m inactive=1d;
Кроме того, все активные ключи и информация о данных хранятся в разделяемой памяти — зоне, имя и размер которой задаётся параметром keys_zone. Если к данным кэша не обращются в течение времени, заданного параметром inactive, то данные удаляются, независимо от их свежести. По умолчанию inactive равен 10 минутам.
Мда, кеширование на уровне nginx конечно очень призводительно, но по моему, так это как-то криво, ведь страницы кешируются безусловно, без учета мнения приложения, в обход его логики. И вообще, как-то коряво имхо.

Правильнее, наверно, все же, в приложении отдавать правильные http-заголовки, а nginx пусть их интерпретирует :)
> ведь страницы кешируются безусловно, без учета мнения приложения, в обход его логики
Это не совсем так. В nginx неплохие возможности по анализу параметров, кук и т.д.

> Правильнее, наверно, все же, в приложении отдавать правильные http-заголовки, а nginx пусть их интерпретирует :)
Так далеко не всегда получается, в статье частично обосновывается, почему. Штука в том, что до приложения дело не доходит при кэш-попадании. Соответственно, никто, кроме nginx, и не может решить, валиден кэш или нет.
Да, кстати, хочу подчеркнуть, что полностраничное nginx-кэширование — это не панацея, это просто один из инструментов, который иногда очень хорошо подходит. Большинство кэширования в реальных приложениях, наверное, располагается между слоем модели и слоем доступа к БД. Но это совсем-совсем другое. Здесь же речь идет только о полностраничном кэшировании.
UFO landed and left these words here
Не столько PHP уродлив, сколько программисты непредсказуемы. Карандаш на острие стьит, но недолго; лучше его перевернуть и поставить устойчиво. Отсюда и совет.
UFO landed and left these words here
Вы так уверенно говорите… Хочу спросить, была ли у Вас практика в этом вопросе именно применительно к nginx?
UFO landed and left these words here
ОК, значит, у нас есть альтернативное мнение. Может, потом как-нибудь появится и альтернативная статья… это всегда интересно.
если бы по невнимательности программиста все вылилось действительно лишь в незакешированную страницу — то да.
Допустим для главной страницы мы настроили кеширование. И логика «описывать правила кеширования в одном месте» призывает нас настроить кеширование и для всех прочих страниц. А как быть в случае когда этих страниц очень много и требуются очень отличающиеся правила.

Можно разделить условно страницы, которые очень часто обновляются и очень редко, а также существует некая золотая середина.

Соответственно получается такая картина:

Вариант1: Мы оптимизируем выдачу часто-обновляемых страниц. При этом получаем дополнительную нагрузку на редко-обновляемые страницы (то есть они будут дергаться в любом случае раз в секунду, так как PHP уже не может сказать, что страница не менялась, так?).

Вариант2: противоположный случай (просто для контрпримера) когда мы оптимизируем выдачу редко-обновляемых и получаем неактуальную информацию.

Я так понимаю, что логичным выходом из этого будет индивидуальный подход к разным разделам и даже конкретным страницам. Но не создаст ли это еще большие сложности с тем, что надо будет индивидуально затачивать кеш огромного числа страниц (а потом добавится какой-нибудь баннер и придется снова менять логику кеширования), а конфиг nginx разрастется до безумных пределов (и соответственно сложность управления им сильно возрастет)?
Если предполагается хороший выигрыш, то можно так поступить. А иначе это лишняя работа :-)
Когда в приложении огромное количество стратегий для полностраничного кэширования, ИМХО стоит подумать, а нельзя ли их все свести к одному-двум. На моей практике именно так происходило: в одном проекте было вообще всего одно правило для всех страниц (все страницы для Гостя кэшировались), в другом — два правила (одно для склеенных CSS+JS, другое — для группы страниц с высокой нагрузкой). Полностраничное кэширование — это не кэширование блоков/выборок из БД, оно обычно сильно проще и гораздо менее универсально.
Кеширование для гостя это интересно… Это не на webo.in случаем? :-)

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

а вот подход к кешированию действительно нужен комплекксный: где надо логику отдаем энджиниксу,
а где нужно -приложению. Где нужно кешируем блок и собираем через SSI
а где надо — через шаблонизатор.
Отличная статься, огромное спасибо.

Можно задавать Last-Modified явно в конфиге и обновлять по крону раз в сутки или реже/чаще.
Конфиг с этой директивой можно инклюдить, для простоты.
Важно, чтобы Last-Modified менялся каждую секунду, т.е. реально содержал текущее время. Иначе сервер будет отдавать Not Modified при рефреше страницы, что в случае ротации не очень хорошо. Впрочем, можно поэкспериментировать с Cache-Control: no-cache, no-store, must-revalidate. Возможно, тогда с Last-Modified и не понадобятся манипуляции, я пока не проверял во всех браузерах, как они реагируют.
да, статья хорошая. Но все же хорошо было бы озвучить общий подход к серверному кэшированию. Если это — только один из инструментов, то каковы критерии применения остальных? Когда мы можем перекинуть расчет валидности кэша на фронтенд, когда — на бэкенд, а когда — вообще не кэшировать, либо кэшировать отдельные блоки.

Да, по поводу4 блоков по 200мс — это сильно :) Особенно, если число ядер у бэкенда меньше, чем 4 — потоки будут изумительно параллелиться :)
Бэкендов же много. Не обязательно на один все запросы пойдут, они распределятся по кластеру.
Правильно ли я понял первую часть статьи в этом месте:

# Гарантируем, что разные пользователи не получат одну и ту же сессионную Cookie.
fastcgi_hide_header «Set-Cookie»;

Сценарий — заходит Пользователь1, кеш пустой, запрос обрабатывается php, который ставит куку с идентификатором сессии, nginx кладет ответ в кеш и прячет куку.

Заходит Пользователь2. Если я правильно понял, он получит данные из кеша, но при этом не получит куки с идентификатором сессии?
практически правильно, только один нюанс — в закешированной странице кука останется, а прячется она при отдаче ответа.

а для индентификатора сессии всегда можно использовать встроенное средство nginx — модуль userid
все таки не очень понятно, а если у меня допустим не session id в куке а какие то полезные данные приложения (допустим, настройки языка)?

1. сохранятся ли они в кеш?
2. каким образом они будут обработаны для второго пользователя?
Эта директива блокирует выдачу в браузер ЛЮБЫХ команд установки кук. Но, заметьте, только УСТАНОВКИ. Сами куки, уже установленные где-то в другой части сайта, никуда не денутся (потому что они управляются браузером, сервер их не ставит, а только читает).

Да, в кэш-файле куки все останутся. Туда вообще попадет ровно то, что выдал PHP, без изменений (и, кажется, на это нельзя повлиять). Можно вырезать только при отдаче.
Значит, если скрипт, ответ которого кешируется, ставит куки, а nginx их режет, данный способ не подходит. Нужно либо отдельно обрабатывать установку кук, в обход данного механизма кеширования, либо писать куки из скрипта вообще.

Спасибо!
nginx пишет в файл кеша весь документ, который получает, включая заголовки (т.е. все куки, которые вы передаете клиенту сохранятся). Что с ними делать дальше — это уж как вам нравится — прячьте куки, оставляйте. Только один кукис спрятать, а другой показать не получится (использование модуля sub для этих целей рассматривать не будем:) ).

Если вы прячете куки — то они не будут отданы и первому пользователю.
Дмитрий можно вопрос?
У меня главная — индивидуальна для каждого зареганого пользователя и статична для всех анонимов.

Т.е. посетитель N1 видит одно, а посетитель 2 — совсем другое.

Второй нюанс, для пользователей заведены псевдосубдомены. Насколько я понял nginx «их не видит», и когда пользователь заходит, например на страницу recoilme.mp3tter.com/ nginx послает его на индексную страницу, в которой уже определяется принадлежность соответствующему субдомену.

Третье — пользователь загрузил файл (оставил сообщение), при обновлении надо отразить с последними изменениями главную…
Кругом жопа…

Я отчаялся реализовать кэш в данных условиях. Просто насколько я понял Вы тоже боролись с твиттер-лайк сайтом, может быть подскажете реализуемо ли это в принципе нгинксом? Или на уровне приложения реализовывали? В каком примерно направлении копать?
Заранее спасибо.
В таком сайте лучше всего хранить главную страницу в memcache и отдавать ее отуда. Если изменяется — просто перерисовываем в памяти.
Не выйдет мне кажется. Главная для всех разная. Более того, recoilme.mp3tter.com для рекойлми != recoilme.mp3tter.com для другого посетителя.
Я в отчаянии. Единственное что наверно можно придумать тут — кешировать запросы. Но и тут жопа. Сиквуль заточен для операций над пулом записей, а для кеша эфективней порезать запросы по одной записи и потом собирать результаты в массив. Как это разрулил twitter — вообще непонятно…
Либо я чего то недопонимаю…
у меня появилась такая идея — кладем данные для хозяина поддомена в мемкеш с ключем, скажем ид его сессии + хост, при заходе на страницу пользователями проверяем, наличие такого ключа, если нет — идем в именованный локейшн из которого проверяем наличие ключа «гость + хост», если нет — лезем в еще один именованный локейшн, который лезет в пхп. Единственное — я не помню, можно ли переопределять error_page внутри именованного локейшена.

слегка сумбурно, но может чем поможет :)
Почему невыйдет?
Для анонимных она одинаковая, верно?
А для зарегенных можно подготавливать статику на диск и отдавать ее напрямую
По первому пункту — нгинкс можно научить смотреть есть ли сессия (допустим, что у неавторизованных её нет) и если да, то передавать управление пхп, если нет — отдать кеш.
Да, кстати, я применял такую технику выключения кэша для залогина. Надо будет дописать это в статье.
Если у вас контент страницы зависит от некоего user_id, то засуньте тогда значение этого user_id в fastcgi_cache_key — тогда будут кешироваться разные варианты страницы в зависимости от cache_key.
Спасибо за статью, как раз подумываю переложить кеширование с Catalyst::Plugin::PageCache на nginx, руки не доходили проверить, будет ли кешироваться страница с обработанными SSI-директивами, или без изменений. Было бы замечательно, если:
1. Можно было бы кешировать страницу только для неавторизованных пользователей. Описанный в документации способ fastcgi_cache_key "...$cookie_user";, не подходит — нужно кешировать только в случае пустой куки, а не для каждого её значения. Хотя, можно попробовать загнать директиву fastcgi_cache в if.
2. Была бы возможность принудительно очищать закешированную страницу по запросу из приложения. Надо проверить, будет ли работать просто удаление файла с именем, равным md5 от fastcgi_cache_key.
Эх, тогда, видимо, придется оставить использование Catalyst::Plugin::PageCache — абсолютно прозрачно для приложения и можно быстро сменить кеширующий бэкенд с файлов на тот же мемкеш или memory-mapped файлы. Слишком уж негибкое получается кеширование nginx'ом, хоть, наверное, и быстрей на порядок, но мне пока и кеша на уровне приложения хватает.
Хотелось-бы увидеть пример отключения кэша при наличие определённой куки (отключение кэша для зарегеных) (реализация сейчас заставляет юзать X-Accel-Expires в приложении)
Примерно вот так:

location ~ ^(здесь_урлы_которые_нужно_кэшировать)$ {
    set $test_cache_on "$cookie_debugMode|$arg_nocache|любые_другие_значения_непустые_для_отключения_кэша";
    if ($test_cache_on = "") {
        rewrite .* /php_cache last;
    }
    rewrite ^ /php_no_cache last;
}

location /php_cache {
    internal;
    ...
    fastcgi_cache wholepage;
    ...
}

Я допишу в статье попозже.
set $tocache 1;
if ($http_cookie ~* "sessionId=([^;]+)(?:;|$)") {
set $tocache 0;
}
...
if ($tocache) {
fastcgi_pass ...
}

примерно так
наверное, вместо set $tocache 0 можно поставить и fastcgi_cache off, хотя я не гуру настроек нгинкса.
Директива fastcgi_cache не может находиться внутри блока if. Так что это не сработает.
А в чём смысл этой конструкции? fastcgi_pass для всех должен быть один.
У меня немного для других целей, не для fastcgi_cache. Кеш ровно такой же (по логике) пишет приложение, а нгинкс при наличии соотв. файла (if -f) и отсутствию сесии отдаёт кеш. Я полагал, что можно приспособить слегка такой подход под описываемое автором.
А при кэшировании nginx+memcache есть какой-нибудь аналог директиве fastcgi_hide_header «Set-Cookie»?
а она там не нужна, в мемкеш вы кладете сами все, что вам захочется
а как быть с SE оптимищацией и Last-Modified? гугл насколько мне известно смотрит на этот заголовок.
В статье есть предложение поэкспериментировать. Возможно, если указать более жесткий Cache-Control, то не потребуется удалять Last-Modified.

Нужно только учитывать, что PHP выставляет Last-Modified при вызова session_start(). Причем выставляет он его равным… дате изменения файла PHP, который запустился в результате запроса. В большинстве случаев это какой-нибудь index.php из FrontController, который вообще не меняется. Соответственно, те, кто использует сессии, чаще всего имеют неправильный Last-Modified.
Судя по всему, приведенные примеры настроек примерно наполовину представляет из себя летопись хождения по граблям — как говорит сам автор, «каждая строчка писана кровью».

Сначала программист, не подозревающий о Cache-Control, наступает на первые грабли и случайно отключает кеширование главной страницы. Автор предполагает, что лучшее решение это запрет Cache-Control на корню, но в результате наступает на несколько очередных грабель — во-первых, из-за отсутствия Cache-Control начинают кешироваться Cookie, а во-вторых, из-за отрезанных Expires страницы начинают плохо кешироваться в броузерах, которые в результате шлют слишком много запросов с If-Modified-Since. С первым справляемся отрезанием самих Cookie, а со вторым — кешированием ответов 304 Not Modified. Напоследок же автор пытается эмулировать Cache-Control через отрезание Last-Modified, что уже напрямую идет вразрез RFC на HTTP/1.1 и скорее всего, чревато очередными граблями.

В итоге же все это комсомольское путешествие оказывается всего-лишь следствием одного из возможных решений специфической проблемы автора и совсем не обязательно разламывать управление кешированием HTTP из-за того, что какой-то программист однажды накосячил. Так что рекомендую воспринимать эту статью с хорошей долей grain of salt и думать самостоятельно о последствиях отключения чего-либо.
По поводу истории — не угадали. :-) Были совсем другие грабли. Но насчет «самостоятельного думания» — Вы совершенно правы.
fastcgi_cache_key "$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri"; — это полный пэ. ладно бы оно было только на личном сайте автора, а так и на хабру попало, и по всему рунету расползлось… рекомендация по поводу восприятия этой статьи — лучшее что есть на этой странице вообще…
Кстати, в рассылку nginx предложили элегантное решение для Last-Modified. Оно работает, проверил. Я изменил статью соответствующим образом.
Only those users with full accounts are able to leave comments. Log in, please.