Если на Вашем сайте практически нет динамики, то Вы легко можете складывать все его страницы в кэш и практически не делать запросов к бекенду. Но что делать если на сайте есть персонализированные данные (авторизация, блок пользователя, баннера)?
ESI

ESI позволяет разбить страницу на логические части, а при обработке страницы делать дополнительные запросы для получения содержимого этих частей. Все выглядит довольно просто:
<HTML>
<BODY>
Всем привет!
<esi:include src="/news.php?UID"/>
Всем пока!!!
</BODY>
</HTML>
Web-сервер, поддерживающий ESI вызовы, просто сделает дополнительный запрос, а результат вставит на место ESI инструкции. Допустим наш скрипт "news.php" содержит такой код:
<?
echo "<ul><li>News title 1</li></ul>";
После обработки первого примера, Web сервер вернет клиенту такую страницу:
<HTML>
<BODY>
Всем привет!
<ul><li>News title 1</li></ul>
Всем пока!!!
</BODY>
</HTML>
ESI
Запросы ESI можно кэшировать. Следовательно, у Вас появляется удобное средство работы с динамическим контентом. Достаточно разделить их на разные блоки и закэшировать те, которые не изменяются (либо изменяются редко).
Как все работает?
Web сервер, который поддерживает ESI, делает запрос к бекенду (в нашем случае, PHP). Далее получив страницу, обрабатывает все ESI вызовы, делая на каждый из них дополнительный запрос для получения содержимого. Далее складывает (или не складывает) все это в кэш и генерирует страницу. Для последующих вызовов Web сервер будет получать данные из кэша и не делать дополнительных ESI запросов.
Порядок подключения ESI
- Сначала нужно определить блоки на странице, вынести их в отдельные скрипты (каждый блок должен иметь свой адрес)
- Вставить ESI инструкции на месте вынесенных блоков
- Включить кэширование ESI блоков на Web сервере
Персонализированные блоки

Самая сложная задача — это кэширование блоков, которые уникальны для каждого пользователя. Например, блок с персональными ссылками на профиль, настройки и превью фотки. В это случае, блок придется сохранять в кэш для каждого пользователя.
Стоит учитывать, что количество таких блоков в кэше будет пропорционально количеству пользователей.
общее количество = количество пользователей х количество блоков на страницу
Подробный пример
Пусть у нас есть сайт с новостями. Новости обновляются каждый час. На сайте также есть блок авторизации и ссылки для авторизированных пользователей. Выделяем такие блоки для ESI:
- Блок авторизации
- Меню
- Блок новостей
Стартовая страница
<HTML>
<BODY>
<h1>Тестируем ESI</h1>
<esi:include src="/app/auth.php?UID"/>
<esi:include src="/app/menu.php?AUTH"/>
<h3>Новости</h3>
<esi:include src="/app/news.php"/>
</BODY>
</HTML>
# Все скрипты для ESI вызовов будут находиться в папке app
Скрипт авторизации
<?
session_start();
if ( $_POST['user'] )
{
$_SESSION['user'] = $_POST['user'];
header('Location: /'); exit;
}
$user = $_SESSION['user'];
?>
<? if ( $user ) { ?>
<div>Привет, <b><?=$user?></b>!</div>
<? } else { ?>
<form method="post" action="/app/auth.php">
Войдите в систему
<input type="text" name="user" />
<input type="submit" name="Войти">
</form>
<? } ?>
Скрипт меню
<? session_start(); ?>
<ul>
<? if ( $_SESSION['user'] ) { ?>
<li><a href="#">Пункт меню только для пользователей</a></li>
<? } ?>
<li><a href="#">Публичный пункт меню</a></li>
</ul>
Скрипт новостей
<?
$rss = file_get_contents('http://feeds.nytimes.com/nyt/rss/HomePage');
$xml = simplexml_load_string($rss);
echo "<ul>";
foreach ( $xml->channel->item as $item )
{
echo "<li><a href=\"{$item->link}\">{$item->title}</a>";
}
echo "</ul>";
Настройка Web сервера
Для приложения будем использовать Nginx, Varnish будет направлять запросы ему (8090 порт):
server {
listen 8090;
# Если включен gzip, обязательно нужно выключить!
gzip off;
location / {
index index.php;
}
location ~* \.(php)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /home/golotyuk/www/localhost/esi/$fastcgi_script_name;
}
}
Varnish + ESI
Давайте настроим кэш по таким правилам:
- Главную страницу кэшируем на 24 часа, блоки — на 1 час
- Кэшировать будем все запросы кроме POST
- Для кэширования персонального блока будем использовать значение сессионных Cookies (PHPSESSID) — для установки ключа
- Для отделения персонализированных блоков от обычных блоков для авторизированных пользователей будем использовать соотв. приставки к запросам: UID (персонализированные блоки) и AUTH (обычные блоки, учитывающие только статус пользователя)

Конфигурация:
backend default { .host = "127.0.0.1"; .port = "8090"; }
# Процедура формирования ключа для кэша
sub vcl_hash {
# Стандартные параметры - имя сервера и URL
set req.hash += req.url;
set req.hash += req.http.host;
# Если установлена сессионная кука, сохраняем ее значение в переменную
if( req.http.cookie ~ "PHPSESSID" ) {
set req.http.X-Varnish-Hashed-On =
regsub( req.http.cookie, "^.*?PHPSESSID=([^;]*?);*.*$", "\1" );
}
# Если в строке запроса мы находим "UID", то необходимо добавить
# значение сессии в параметры кэширования
if( req.url ~ "/app/.*UID" && req.http.X-Varnish-Hashed-On ) {
set req.hash += req.http.X-Varnish-Hashed-On;
}
# Если в строке запроса мы находим "AUTH", то необходимо добавить
# флаг статуса (logged in) в параметры кэширования
if( req.url ~ "/app/.*AUTH" && req.http.X-Varnish-Hashed-On ) {
set req.hash += "logged in";
}
hash;
}
sub vcl_recv {
# Если тип запрос не POST, то ищем объект в кэше
if ( req.request != "POST" )
{
lookup;
}
}
sub vcl_fetch {
# Для запроса "/" используем обработку esi и кэшируем на 1 сутки
if (req.url == "/") {
esi;
set obj.ttl = 24h;
}
# Для запросов "/app" (ESI вызовы) кэшируем результат на 1 час
elseif (req.url ~ "^/app/") {
set obj.ttl = 1h;
}
deliver;
}
После проверки скорости получим такие результаты:
ab -n 100 -c 5 http://127.0.0.1/
Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 0 Processing: 2 5 3.8 3 18 Waiting: 2 5 3.8 3 18 Total: 2 5 3.8 4 19 Percentage of the requests served within a certain time (ms) 50% 4 66% 4 75% 5 80% 8 90% 12 95% 15 98% 17 99% 19 100% 19 (longest request)
На аналогичном скрипте без ESI, который содержит всю туже логику внутри и каждый раз вызывает PHP:
ab -n 100 -c 5 http://127.0.0.1:8090/index_standard.php
Результаты:
Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 0 Processing: 354 579 666.2 458 5484 Waiting: 354 579 666.2 458 5483 Total: 354 579 666.2 458 5484 Percentage of the requests served within a certain time (ms) 50% 458 66% 492 75% 517 80% 539 90% 602 95% 667 98% 3572 99% 5484 100% 5484 (longest request)
Как видим скорость работы отличается в 100 раз!
Самое важное
ESI позволяет использовать кэширование на динамических сайтах с высокой персонализацией. Обратите внимание на альтернативу — SSI в связке с Nginx.