Большое спасибо. Очень интересная статья. Благодаря ей я сегодня ночью настроил кэширование в Drupal. Если кому-то интересно, отпишусь позже о результатах тестирования.<br><br><div class="gmail_quote">16 октября 2009 г. 3:41 пользователь Dmitry Koterov <span dir="ltr">&lt;<a href="mailto:dmitry@koterov.ru">dmitry@koterov.ru</a>&gt;</span> написал:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">Я тут статью черканул: <a href="http://dklab.ru/chicken/nablas/56.html" target="_blank">http://dklab.ru/chicken/nablas/56.html</a><br>
Если есть мысли/замечания/комментарии/уточнения, буду рад внести изменения.<br><br><br>=======================<br>
<br><h2><a name="1245a9a1e0a0454a_#top">Подводные камни при использовании кэширования в nginx</a></h2>

<a name="1245a9a1e0a0454a_#top">  </a><table width="100%"><tbody><tr>
  
    <td align="left" width="50%"><font color="#999999">[15 октября 2009 г.]</font></td>
  
  
    <td align="right">
<a href="http://dklab.ru/redir?http://forum.dklab.ru/viewtopic.php?t=35526Z:%5Chome%5Cdklab%5Cwww%5Cchicken%5Cnablas%5C56.html" target="_blank"><img alt="" border="0" height="16" width="16">обсудить статью в форуме</a><br>


    </td>
  
  </tr></tbody></table>

<a name="1245a9a1e0a0454a_#top">  
  
  </a><p>
<a name="1245a9a1e0a0454a_#top">    
    </a></p><p><a name="1245a9a1e0a0454a_#top">В web-сервер и reverse-proxy </a><a href="http://sysoev.ru/nginx/docs/" target="_blank">nginx</a>
встроены очень мощные возможности по кэшированию HTTP-ответов. Однако в
ряде случаев документации и примеров не хватает, в результате не все
получается так легко и просто, как хотелось бы. Этой статьей я попробую
немного улучшить ситуацию.
</p><p></p><table border="0">
<tbody><tr valign="top">
<td><img alt="Лирическое отступление" height="32" width="32">&nbsp;</td>
<td><p style="margin-top:0pt"><i>Я буду предполагать, что вы
используете связку nginx+fastcgi_php. Если вы применяете
nginx+apache+mod_php, просто замените имена директив с fastcgi_cache*
на proxy_cache*.
</i></p></td>
</tr>
</tbody></table><p></p>

<p>Если выбирать, кэшировать ли страницу на стороне PHP или на стороне
nginx, я выбираю nginx. Во-первых, это позволяет отдавать 5-10 тыс.
запросов в секунду без каких-либо сложностей и без умных разговоров о
&quot;высокой нагрузке&quot;. Во-вторых, nginx самостоятельно следит за размером
кэша и чистит его как при устаревании, так и при вытеснении нечасто
используемых данных.
</p><h2><a name="1245a9a1e0a0454a_cont0"></a><span style="font-size:18pt">Кэширование всей страницы целиком</span></h2>

<p>Если на вашем сайте главная страница хоть и генерируется
динамически, но меняется достаточно редко, можно сильно снизить
нагрузку на сервер, закэшировав ее в nginx. При высокой посещаемости
даже кэширование на короткий срок (5 минут и меньше) уже дает огромный
прирост в производительности, ведь кэш работает очень быстро. Даже
закэшировав страницу всего на 30 секунд, вы все равно добьетесь
значительной разгрузки сервера, сохранив при этом динамичность
обновления данных (во многих случаях обновления раз в 30 секунд вполне
достаточно).
</p><p>Например, закэшировать главную страницу можно так:

</p><p></p><table border="0" cellpadding="6" cellspacing="1" width="98%">
  <tbody><tr style="border-top:1px dashed black" bgcolor="#dddddd"> 
          
    
    <td style="border-top:1px solid rgb(240, 240, 240);border-left:1px solid rgb(240, 240, 240);border-right:1px solid rgb(240, 240, 240)" bgcolor="#e5e5e5" width="100%">
            <div style="float:right">
<font size="-1"><a href="http://dklab.ru/chicken/nablas/56.html#" target="_blank"><span>скопировать код в буфер обмена</span></a>
</font>
</div>
<font size="-1">
            <b>
                    <a name="1245a9a1e0a0454a_#list1" href="http://dklab.ru/chicken/nablas/56.html#list1" title="Ссылка на текущий листинг." target="_blank">Листинг 1</a>
            </b>
</font></td>
  </tr>
  <tr bgcolor="#f0f0f0">
    
    <td><pre style="margin:0px"><font size="+0">fastcgi_cache_path /var/cache/nginx levels= keys_zone=wholepage:50m;<br>...<br>server {<br>  ...<br>  location / {<br>    ...<br>    fastcgi_pass <a href="http://127.0.0.1:9000" target="_blank">127.0.0.1:9000</a>;<br>

    ...<br>    <font color="green"><font color="#008800"># Включаем кэширование и тщательно выбираем ключ кэша.</font></font><br>    fastcgi_cache wholepage;<br>    fastcgi_cache_valid 200 301 302 304 5m;<br>    fastcgi_cache_key &quot;$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri&quot;;<br>

    <font color="green"><font color="#008800"># Гарантируем, что разные пользователи не получат одну и ту же сессионную Cookie.</font></font><br>    fastcgi_hide_header &quot;Set-Cookie&quot;;<br>    <font color="green"><font color="#008800"># Заставляем nginx кэшировать страницу в любом случае, независимо от</font></font><br>

    <font color="green"><font color="#008800"># заголовков кэширования, выставляемых в PHP.</font></font><br>    fastcgi_ignore_headers &quot;Cache-Control&quot; &quot;Expires&quot;;<br>  }<br>}</font></pre></td>
  </tr>
</tbody></table><p></p>

<p>Я не сильно преувеличу, если скажу, что каждая строчка в этом
конфиге написана кровью. Здесь много подводных камней, давайте их все
рассмотрим.
</p><h4><a name="1245a9a1e0a0454a_cont1"></a>fastcgi_cache_path: простота отладки тоже важна</h4>

<pre>fastcgi_cache_path /var/cache/nginx levels= keys_zone=wholepage:50m;</pre>

<p>В директиве <a href="http://sysoev.ru/nginx/docs/http/ngx_http_fastcgi_module.html#fastcgi_cache_path" target="_blank">fastcgi_cache_path</a>
я выставляю &quot;пустое&quot; значение для levels. Хотя это немного снижает
производительность (файлы будут напрямую создаваться в
/var/cache/nginx, без разбиения по директориям), но зато на порядок
облегчает отладку и диагностику проблем с кэшем. Поверьте, вам еще не
раз придется руками залезать в /var/cache/nginx и смотреть, что там
хранится.
</p><h4><a name="1245a9a1e0a0454a_cont2"></a>fastcgi_cache_valid: кэшируем код ответа 304 тоже</h4>

<pre>fastcgi_cache_valid 200 301 302 304 5m;</pre>

<p>В директиве <a href="http://sysoev.ru/nginx/docs/http/ngx_http_fastcgi_module.html#fastcgi_cache_valid" target="_blank">fastcgi_cache_valid</a>
мы заставляем кэшировать не только стандартные коды 200 ОК, 301 Moved
Permanently и 302 Found, но также и 304 Not Modified. Почему? Давайте
вспомним, что означает 304. Он выдается с пустым телом ответа в двух
случаях:
</p><ul><li>Если браузер послал заголовок &quot;If-Modified-Since: date&quot;, в котором
date больше либо равна значению заголовка ответа &quot;Last-Modified: date&quot;.
Т.е. клиент спрашивает: &quot;Есть ли новая версия с момента date? Если нет,
верни мне 304 и сэкономь трафик. Если есть, отдай мне тело страницы&quot;.
</li><li>Если браузер послал заголовок &quot;If-None-Match: hash&quot;, где hash
совапдает со значением заголовка ответа &quot;ETag: hash&quot;. Т.е. клиент
спрашивает: &quot;Отличается ли текущая версия страницы от той, что я
запросил в прошлый раз? Если нет, верни мне 304 и сэкономь трафик. Если
да, отдай тело страницы&quot;.
</li></ul>В обоих случаях Last-Modified или ETag будут взяты, скорее
всего, из кэша nginx, и проверка пройдет очень быстро. Нам незачем
&quot;дергать&quot; PHP только для того, чтобы скрипт выдал эти заголовки,
особенно в свете того, что клиентам, которым уйдет ответ 200, он будет
отдан из кэша.
<h4><a name="1245a9a1e0a0454a_cont3"></a>fastcgi_cache_key: внимательно работаем с зависимостями</h4>

<pre>fastcgi_cache_key &quot;$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri&quot;;</pre>

<p>Особого внимания заслуживает значение в директиве <a href="http://sysoev.ru/nginx/docs/http/ngx_http_fastcgi_module.html#fastcgi_cache_key" target="_blank">fastcgi_cache_key</a>.
Я привел минимальное рабочее значение этой директивы. Шаг вправо, шаг
влево, и вы начнете в ряде случаев получать &quot;неправильные&quot; данные из
кэша. Итак:
</p><ul><li>Зависимость от $request_method нам нужна, т.к. HEAD-запросы в
Интернете довольно часты. Ответ на HEAD-запрос никогда не содержит
тела. Если убрать зависимость от $request_method, то может так
совпасть, что кто-то до вас запросил главную страницу HEAD-методом, а
вам потом по GET отдастся пустой контент.
</li><li>Зависимость от $http_if_modified_since нужна для того, чтобы
кэш с ответом 304 Not Modified не был случайно отдан клиенту, делающему
обычный GET-запрос. Иначе клиент может получить пустой ответ из кэша.
</li><li>То же самое и с $http_if_none_match. Мы должны быть застрахованы от выдачи пустых страниц клиентам!
</li><li>Наконец, зависимость от $host и $request_uri не требует комментариев.
</li></ul>


<h4><a name="1245a9a1e0a0454a_cont4"></a>fastcgi_hide_header: решаем проблемы с безопасностью</h4>

<pre>fastcgi_hide_header &quot;Set-Cookie&quot;;</pre>

<p>Директива <a href="http://sysoev.ru/nginx/docs/http/ngx_http_fastcgi_module.html#fastcgi_hide_header" target="_blank">fastcgi_hide_header</a>
очень важна. Без нее вы серьезно рискуете безопасностью: пользователи
могут получить чужие сессии через сессионную Cookie в кэше. (Правда, в
последних версиях nginx что-то было сделано в сторону автоматического
учета данного фактора.) Понимаете, как это происходит? На сайт зашел
Вася Пупкин, ему выдалась сессия и сессионная Cookie. Пусть кэш на тот
момент оказался пустым, и в него записалась Васина Cookie. Затем пришел
другой пользователь, получил ответ из кэша, а в нем - и Cookie Васи. А
значит, и его сессию тоже.
</p><p></p><table border="0">
<tbody><tr valign="top">
<td><img alt="Чайник" height="40" width="40">&nbsp;</td>
<td><p style="margin-top:0pt"><i>Можно, конечно, сказать: давайте не
будем вызывать session_start() на главной странице, тогда и с Cookies
проблем не будет. В теории это так, но на практике данный способ очень
неустойчив. Сессии часто стартуют &quot;отложено&quot;, и достаточно какой-либо
части кода &quot;случайно&quot; вызвать функцию, требующую доступа к сессии, как
мы получим дыру в безопасности. А безопасность &mdash; такая штука, что если в той или иной методике <i>может</i>
возникнуть дыра по неосторожности, то эта методика считается &quot;дырявой&quot;
по определению. К тому же есть и другие Cookies, кроме сессионной; их
тоже не надо записывать в кэш.
</i></p></td>
</tr>
</tbody></table><p></p>


<h4><a name="1245a9a1e0a0454a_cont5"></a>fastcgi_ignore_headers: не даем сайту &quot;лечь&quot; от нагрузки при опечатке</h4>

<pre>fastcgi_ignore_headers &quot;Cache-Control&quot; &quot;Expires&quot;;</pre>

<p>Сервер nginx обращает внимание на заголовки Cache-Control, Expires и
Pragma, которые выдает PHP. Если в них сказано, что страницу не нужно
кэшировать (либо что она уже устарела), то nginx не записывает ее в
кэш-файл. Это поведение, хотя и кажется логичным, на практике порождает
массу сложностей. Поэтому мы его блокируем: благодаря <a href="http://sysoev.ru/nginx/docs/http/ngx_http_fastcgi_module.html#fastcgi_ignore_headers" target="_blank">fastcgi_ignore_headers</a> в кэш-файлы попадет содержимое любой страницы, независимо от ее заголовков.

</p><p>Что же это за сложности? Они опять связаны с сессиями и функцией
session_start(), которая в PHP по умолчанию выставляет заголовки
&quot;Cache-Control: no-cache&quot; и &quot;Pragma: no-cache&quot;. Здесь существует три
решения проблемы:
</p><ul><li>Не пользоваться session_start() на странице, где предполагается
кэширование. Один из минусов этого способа мы уже рассмотрели выше:
достаточно одного неосторожного движения, и ваш сайт, принимающий
тысячи запросов в секунду на закэшированную главную страницу,
моментально &quot;ляжет&quot;, когда кэш отключится. Второй минус &mdash;
нам придется управлять логикой кэширования в двух местах: в конфиге
nginx и в PHP-коде. Т.е. эта логика окажется &quot;размазанной&quot; по
совершенно разным частям системы.
</li><li>Выставить ini_set(&#39;session.cache_limiter&#39;, &#39;&#39;). Это заставит
PHP запретить вывод каких-либо заголовков, ограничивающих кэширование
при работе с сессиями. Проблема здесь та же: &quot;размазанность&quot; логики
кэширования, ведь в идеале мы бы хотели, чтобы все кэширование
управлялось из единого места.
</li><li>Игнорировать заголовки запрета кэширования при записи в
кэш-файлы при помощи fastcgi_ignore_headers. Кажется, это
беспроигрышное решение, поэтому я его и советую.
</li></ul>

<p></p><table border="0">
<tbody><tr valign="top">
<td><img alt="Чайник" height="40" width="40">&nbsp;</td>
<td><p style="margin-top:0pt"><i>
Думаете, это все аспекты кэширования?<br>
Ничего подобного, мы только на середине. 
</i></p></td>
</tr>
</tbody></table><p></p>



<h2><a name="1245a9a1e0a0454a_cont6"></a><span style="font-size:18pt">Кэширование с ротацией</span></h2>

<p>Статическая главная страница &mdash; это не так уж и
интересно. Что делать, если на сайте много материалов, а Главная
выступает в роли своеобразной &quot;витрины&quot; для них? На такой &quot;витрине&quot;
удобно отображать &quot;случайные&quot; материалы, чтобы разные пользователи
видели разное (и даже один пользователь получал новый контент,
перезагрузив страницу в браузере).
</p><p>Решение задачи &mdash; <i>кэширование с ротацией</i>:
</p><ol><li>Мы заставляем скрипт честно выдавать элементы главной странице в
случайном порядке, выполняя необходимые запросы в базу данных (пусть
это и медленно). </li><li>Затем мы сохраняем в кэше не одну, а, скажем, 10 вариантов страницы. 
</li><li>Когда пользователь заходит на сайт, мы показываем ему один из
этих вариантов. При этом, если кэш пуст, то запускается скрипт, а если
нет, то результат возвращается из кэша. </li><li>Устанавливаем время устаревания кэша малым (например, 1
минута), чтобы за день разные пользователи &quot;отсмотрели&quot; все материалы
сайта.
</li></ol>

<p>В итоге первые 10 запросов к скрипту-генератору выполнятся &quot;честно&quot;
и &quot;нагрузят&quot; сервер. Зато потом они &quot;осядут&quot; в кэше и в течение минуты
будут выдаваться уже быстро. Прирост производительности тем больше, чем
больше посетителей на сайте.
</p><p>Вот кусочек конфига nginx, реализующий кэширование с ротацией:


</p><p></p><table border="0" cellpadding="6" cellspacing="1" width="98%">
  <tbody><tr style="border-top:1px dashed black" bgcolor="#dddddd"> 
          
    
    <td style="border-top:1px solid rgb(240, 240, 240);border-left:1px solid rgb(240, 240, 240);border-right:1px solid rgb(240, 240, 240)" bgcolor="#e5e5e5" width="100%">
            <div style="float:right">
<font size="-1"><a href="http://dklab.ru/chicken/nablas/56.html#" target="_blank"><span>скопировать код в буфер обмена</span></a>
</font>
</div>
<font size="-1">
            <b>
                    <a name="1245a9a1e0a0454a_#list2" href="http://dklab.ru/chicken/nablas/56.html#list2" title="Ссылка на текущий листинг." target="_blank">Листинг 2</a>
            </b>
</font></td>
  </tr>
  <tr bgcolor="#f0f0f0">
    
    <td><pre style="margin:0px"><font size="+0">fastcgi_cache_path /var/cache/nginx levels= keys_zone=wholepage:50m;<br>perl_set $rand &#39;sub { return int rand 10 }&#39;;<br>...<br>server {<br>  ...<br>  location / {<br>

    ...<br>    fastcgi_pass <a href="http://127.0.0.1:9000" target="_blank">127.0.0.1:9000</a>;<br>    ...<br>    <font color="green"><font color="#008800"># Включаем кэширование и тщательно выбираем ключ кэша.</font></font><br>
    fastcgi_cache wholepage;<br>
    fastcgi_cache_valid 200 301 302 304 1m;<br>    fastcgi_cache_key &quot;$rand|$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri&quot;;<br>    <font color="green"><font color="#008800"># Гарантируем, что разные пользователи не получат одну и ту же сессионную Cookie.</font></font><br>

    fastcgi_hide_header &quot;Set-Cookie&quot;;<br>    <font color="green"><font color="#008800"># Заставляем nginx кэшировать страницу в любом случае, независимо от</font></font><br>    <font color="green"><font color="#008800"># заголовков кэширования, выставляемых в PHP.</font></font><br>

    fastcgi_ignore_headers &quot;Cache-Control&quot; &quot;Expires&quot;;<br><br>    <font color="green"><font color="#008800"># Заставляем браузер каждый раз перезагружать страницу (для ротации).</font></font><br>    fastcgi_hide_header &quot;Cache-Control&quot;;<br>

    add_header Cache-Control &quot;no-cache&quot;;<br>    fastcgi_hide_header &quot;Pragma&quot;;<br>    add_header Pragma &quot;no-cache&quot;;<br><br>    <font color="green"><font color="#008800"># Никогда не выдаем браузеру заголовок Last-Modified.</font></font><br>

    fastcgi_hide_header &quot;Last-Modified&quot;;<br>    add_header Last-Modified &quot;&quot;;<br>  }<br>}</font></pre></td>
  </tr>
</tbody></table><p></p>

<p>Вы можете заметить, что по сравнению с предыдущим примером мне
пришлось добавить еще 6 директив в location. Они тоже написаны кровью.
Но не будем забегать вперед, рассмотрим все по порядку.
</p><h4><a name="1245a9a1e0a0454a_cont7"></a>perl_set: зависимость-рандомизатор</h4>

<pre>perl_set $rand &#39;sub { return int rand 10 }&#39;;</pre>

<p>С директивой <a href="http://sysoev.ru/nginx/docs/http/ngx_http_perl_module.html#perl_set" target="_blank">perl_set</a>
все просто. Мы создаем переменную, при использовании которой nginx
будет вызывать функцию встроенного в него Perl-интерпретатора. По
словам автора nginx, это достаточно быстрая операция, так что мы не
будем &quot;экономить на спичках&quot;. Переменная принимает случайное значение
от 0 до 9 в каждом из HTTP-запросов.
</p><h4><a name="1245a9a1e0a0454a_cont8"></a>fastcgi_cache_key: зависимость от рандомизатора</h4>

<pre>fastcgi_cache_key &quot;$rand|$request_method|...&quot;;</pre>

<p>Теперь мы замешиваем переменную-рандомизатор в ключ кэша. В итоге
получается 10 разных кэшей на один и тот же URL, что нам и требовалось.
Благодаря тому, что скрипт, вызываемый при кэш-промахе, выдает элементы
главной страницы в случайном порядке, мы получаем 10 разновидностей
главной страницы, каждая из которой &quot;живет&quot; 1 минуту (см.
fastcgi_cache_valid).
</p><h4><a name="1245a9a1e0a0454a_cont9"></a>add_header: принудительно выключаем браузерный кэш</h4>

<pre>fastcgi_hide_header &quot;Cache-Control&quot;;<br>add_header Cache-Control &quot;no-cache&quot;;<br>fastcgi_hide_header &quot;Pragma&quot;;<br>add_header Pragma &quot;no-cache&quot;;</pre>

<p>Выше мы говорили, что nginx чувствителен к кэш-заголовкам,
выдаваемым PHP-скриптом. Если PHP-скрипт возвращает заголовки &quot;Pragma:
no-cache&quot; или &quot;Cache-Control: no-cache&quot; (а также еще некоторые), то
nginx не будет сохранять результат в кэш-файлах. Специально чтобы
подавить такое его поведение, мы используем fastcgi_ignore_headers (см.
выше).
</p><p></p><table border="0">
<tbody><tr valign="top">
<td><img alt="Чайник" height="40" width="40">&nbsp;</td>
<td><p style="margin-top:0pt"><i>
Чем отличается &quot;Pragma: no-cache&quot; от &quot;Cache-Control: no-cache&quot;? Только тем, что Pragma &mdash; наследие HTTP/1.0 и сейчас поддерживается для совместимости со старыми браузерами. В HTTP/1.1 используется Cache-Control.
</i></p></td>
</tr>
</tbody></table><p></p>

<p>Однако есть еще кэш в браузере. И в некоторых случаях браузер может
даже не пытаться делать запрос на сервер, чтобы отобразить страницу;
вместо этого он достанет ее из собственного кэша. Т.к. у нас ротация,
нам такое поведение неудобно: ведь каждый раз, заходя на страницу,
пользователь должен видеть новые данные. (На самом деле, если вы все же
хотите закэшировать какой-нибудь один вариант, то можно
поэкспериментировать с заголовком Cache-Control.)
</p><p>Директива <a href="http://sysoev.ru/nginx/docs/http/ngx_http_headers_module.html#add_header" target="_blank">add_header</a>
как раз и передает в браузер заголовок запрета кэширования. Ну а чтобы
этот заголовок случайно не размножился, мы вначале убираем из
HTTP-ответа то, что записал туда PHP-скрипт (и то, что записалось в
nginx-кэш): директива <a href="http://sysoev.ru/nginx/docs/http/ngx_http_fastcgi_module.html" target="_blank">fastcgi_hide_header</a>.
Ведь вы, когда пишете конфиг nginx-а, не знаете, что там надумает
выводить PHP (а если используется session_start(), то он точно
надумает). Вдруг он выставит свой собственный заголовок Cache-Control?
Тогда их будет два: PHP-шный и добавленный нами через add_header.
</p><h4><a name="1245a9a1e0a0454a_cont10"></a>add_header Last-Modified: гарантируем перезагрузку страницы</h4>

<pre>fastcgi_hide_header &quot;Last-Modified&quot;;<br>add_header Last-Modified &quot;&quot;;</pre>

<p>Еще один трюк: мы должны исключить из HTTP-ответа заголовок
Last-Modified, либо же выставить его равным текущему времени. (К
сожалению, nginx пока не поддерживает установку Last-Modified в текущее
время без использования встроенного Perl, поэтому мы просто удаляем
заголовок.) Гарантировано исключить заголовок можно директивой
add_header Last-Modified &quot;&quot;. Но на всякий случай (а также для
симметрии) я еще добавил сюда fastcgi_hide_header, он не помешает.
</p><p>Почему же так важно удалять (или выставлять текущим временем) этот заголовок? Все довольно просто. 
</p><ol><li>Давайте представим, что PHP выдал заголовок &quot;Last-Modified: некоторая_дата&quot;. 
</li><li>Данный заголовок будет записан в кэш-файл nginx (можете
проверить: в нашем примере файлы хранятся в /var/cache/nginx), а потом
отдан в браузер клиенту. </li><li>Браузер запомнит страницу и дату ее модификации...
</li><li>...поэтому при следующем заходе пользователя на сайт в HTTP-запросе будет заголовок-вопрос &quot;If-Modified-Since: некоторая_дата&quot;. 
</li><li>Что же сделает nginx? Он достанет страницу из своего кэша,
разберет ее заголовки и сравнит Last-Modified с If-Modified-Since. Если
значения совпадут (или первое окажется меньше второго), то nginx вернет
ответ &quot;304 Not Modified&quot; с пустым телом. И пользователь не увидит
никакой ротации: он получит то, что уже видел раньше.
</li></ol>

<p></p><table border="0">
<tbody><tr valign="top">
<td><img alt="Лирическое отступление" height="32" width="32">&nbsp;</td>
<td><p style="margin-top:0pt"><i>На самом деле, большой вопрос, как
поведет себя браузер при наличии одновременно Last-Modified и
Cache-Control no-cache. Будет ли он делать запрос If-Modified-Since?
Кажется, что разные браузеры ведут тут себя по-разному.
Экспериментируйте.
</i></p></td>
</tr>
</tbody></table><p></p>

<p>Есть, правда, один минус. Говорят, что Яндекс не очень охотно
индексирует страницы, у которых не выставлен Last-Modified. Так что,
если кто-то придумает и обоснует альтернативный способ, я буду раз его
услышать.
</p><h2><a name="1245a9a1e0a0454a_cont11"></a><span style="font-size:18pt">Динамическое &quot;окно&quot; в закэшированной странице</span></h2>

<p>Ну и напоследок упомяну одну технику, которая может быть полезна в
свете кэширования. Если вам хочется закэшировать главную (или любую
другую) страницу сайта, однако мешает один маленький блок, который
обязательно должен быть динамическим, воспользуйтесь <a href="http://sysoev.ru/nginx/docs/http/ngx_http_ssi_module.html" target="_blank">модулем для работы с SSI</a>.

</p><p>В ту часть страницы, которая должна быть динамической, вставьте вот такой &quot;HTML-комментарий&quot;:

</p><pre>&lt;!--<font color="#008800"># include virtual=&quot;/get_user_info/&quot; --&gt;</font></pre>

<p>С точки зрения кэша nginx данный комментарий &mdash; обычный
текст. Он будет сохранен в кэш-файле именно в виде комментария. Однако
позже, при прочтения кэша, сработает модуль SSI nginx, который
обратится к динамическому URL. Конечно, по адресу /get_user_info/
должен быть PHP-обработчик, который выдает содержимое данного блока.
Более подробно данный способ описан в <a href="http://habrahabr.ru/blogs/nginx/65809/" target="_blank">этой статье</a>.

</p><p>Ну и, естественно, не забудьте включить SSI для этой страницы или даже для всего сервера:
</p><pre>ssi on;</pre>

<p></p><table border="0">
<tbody><tr valign="top">
<td><img alt="Чайник" height="40" width="40">&nbsp;</td>
<td><p style="margin-top:0pt"><i>
Директива SSI include имеет еще одно, крайне важное свойство. Когда на
странице встречаются несколько таких директив, то все они начинают
обрабатываться <b>одновременно</b>,
в параллельном режиме. Так что, если у вас на странице 4 блока, каждый
из которых загружается 200мс, в сумме страница будет получена
пользователем через 200мс, а не через 800.
</i></p></td></tr></tbody></table><p></p><br>
</blockquote></div><br>