VARNISH FOR 
AUTHENTICATED 
USERS 
Шуменко А.Е. (ashumenko@adyax.com)
О чём мы будем говорить 
◻ Varnish Reverse Proxy 
◻ Принципы конфигурации Varnish 
◻ Зачем и как разделять кэш 
◻ Кэширование для пользователей 
◻ Кэширование для ролей 
◻ Очистка кэша
Varnish Reverse Proxy 
❑ Browser – Varnish – Web server 
❑ Жизнь запроса внутри Varnish 
❑ Конфигурация Varnish (*.VCL файл)
Browser – Varnish – Web server 
Browser 
Varnish 
Cache 
First 
request 
2nd (nth ) 
request 
❑ Первый запрос попадает к web серверу 
❑ Остальные запросы обслуживаются из кэша 
Web 
server
Скорость работы Varnish 
Условия теста: $ ab -c10 -n50 http://mysite.my/ 
Условия теста Время теста (с) Время запроса (с) 
Web server (вся страница) 62.301 1.246 
Web server (bootstrap) 23.041 0.461 
Web server (return) 0.048 0.001 
Varnish (Холодный кэш) 1.121 0.022 
Varnish (Горячий кэш) 0.029 0.0006
Жизнь запроса внутри Varnish 
Varnish 
Cache 
vcl_recv{} 
lookup 
vcl_hash{} Web 
server 
pass 
vcl_deliver{} 
vcl_fetch 
{} 
hit cache 
miss cache 
Request (HTTP) 
Response
Конфигурация Varnish 
(vcl_recv) 
Нормализировать 
данные для web- 
приложения. 
Выбрать политику 
кэширования. 
Управление 
доступом. 
sub vcl_recv { 
/* Add custom header. */ 
set req.http.X-Forwarded-For = client.ip; 
/* Do not cache POST. */ 
if (req.request == “POST”) { 
return (pass); 
} 
/* Do not cache authenticated users and Authorization 
request. */ 
if (req.http.Authorization || req.http.Cookie) { 
return (pass); 
} 
/* Mark other request as cacheable. */ 
return (lookup); 
}
Конфигурация Varnish 
(vcl_hash) 
Возвращает ID 
запроса. 
Позволяет иметь 
разный кэш одной 
и той же страницы. 
ВАЖНО: Hash 
строится только из 
переменных 
запроса. 
sub vcl_hash { 
/* Make page ID based on request. */ 
hash_data(req.url); 
if (req.http.host) { 
hash_data(req.http.host); 
} 
else { 
hash_data(server.ip); 
} 
return (hash); 
}
Конфигурация Varnish 
(vcl_fetch) 
Указать время 
кэширования 
страницы. 
sub vcl_fetch { 
/* Cache TTL can be controlled from backend. */ 
if (beresp.http.cache-control !~ “s-maxage”) { 
/* Cache JPG images for 60 seconds. */ 
if (req.url ~ “.jpg$”) { 
set beresp.ttl = 60s; 
} 
} 
/* Unset Cookies for cached pages. */ 
if (beresp.ttl > 0s) { 
unset beresp.http.Set-Cookie; 
} 
return (deliver); 
}
Зачем и как разделять кэш 
❑ Кэширование per-user 
❑ Кэширование per-role 
❑ User-blocks для per-role кэширования
Кэширование per-user: 
VCL functions 
На практике почти не 
применимо само по 
себе. 
В большинстве, 
пользователи 
посещают разные 
страницы 
Размер кэш базы 
может колоссально 
возрасти 
sub vcl_recv { 
/* Remove cookie from pages that should be same for all 
users. */ 
if (req.url ~ ".(png|gif|jpg|swf|css|js)$") { 
unset req.http.cookie; 
} 
} 
sub vcl_hash { 
/* Add User specific (session) Cookie to page ID. */ 
if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") { 
hash_data(regsub(req.http.Cookie, "^.*?SESS([^;]*);*.*$", 
"1")); 
} 
}
Кэширование per-role: 
Drupal Hooks 
В Drupal необходимо установить Cookie уникальное для роли пользователя (комбинации 
ролей пользователя). Используем hash() и шум для усложнения подбора Cookie чужой роли. 
/** Implements hook_user_login(). */ 
function hook_user_login($edit, $user) { 
$roles = array_filter(array_keys($user->roles)); 
sort($roles); 
$bin = hash('sha256', implode('_', $roles) . 'SECRET_KEY_ABC'); 
$params = session_get_cookie_params(); 
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; 
setcookie('BIN', $bin, $expire, $params['path'], $params['domain'], FALSE, 
$params['httponly']); 
} 
/** Implements hook_user_logout(). */ 
function hook_user_logout() { 
$params = session_get_cookie_params(); 
setcookie('BIN', '', REQUEST_TIME - 3600, $params['path'], $params['domain']); 
}
Кэширование per-role: 
VCL functions 
В Varnish необходимо добавить этот Cookie к ID (hash) всех запросов. 
sub vcl_recv { 
/* Remove cookie from pages that should be same for all users. */ 
if (req.url ~ ".(png|gif|jpg|swf|css|js)$") { 
unset req.http.cookie; 
} 
} 
sub vcl_hash { 
/* Add Role specific Cookie to page ID. */ 
if (req.http.Cookie ~ "^.*?BIN=([^;]*);*.*$") { 
hash_data(regsub(req.http.Cookie, "^.*?BIN=([^;]*);*.*$", "1")); 
} 
}
Кэширование per-role:блоки “паразиты” 
(user blocks) 
Необходимо сделать механизм получения информации специфической для каждого 
пользователя (user links, корзина, избранное, личные сообщения и т.д.).
Кэширование per-role:дополненный 
vcl_hash 
Нужен путь который будет возвращать всю персональную информацию пользователя. Его 
нужно кэшировать per-user. 
sub vcl_hash { 
if (req.http.Cookie ~ "^.*?BIN=([^;]*);*.*$") { 
/* Require session Cookie. */ 
if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") { 
/* Enable per-user cache for selected path. */ 
if (req.url ~ "^/user_blocks.*$") { 
hash_data(regsub(req.http.Cookie, "^.*?SESS([^;]*);*.*$", "1")); 
} 
} else { error 750 “Sanity error”; } 
hash_data(regsub(req.http.Cookie, "^.*?BIN=([^;]*);*.*$", "1")); 
} 
}
User-blocks для per-role кэширования:Основные 
вопросы при проектировании 
◻ Решить как получать данные (AJAX vs ESI). 
◻ Как держать кэшированную версию 
/user_blocks актуальной («правильно» чистим 
кэш в varnish). 
◻ Адаптировать чужие модули (на примере flag 
модуля)
User-blocks для per-role кэширования: получаем 
данные AJAX 
<span class=“user-blocks" data-arg="…"></span> 
За Против 
◻ Простота 
◻ Один запрос независимо от 
количества боков 
◻ Страница «мигает» 
◻ Пользователь видит лишние запросы 
(и технические URL) 
◻ Не работает с выключенным JS 
Browser 
Varnish 
Per-role 
cache 
Per-user 
cache 
Main 
request 
AJAX 
response 
Web 
server
User-blocks для per-role кэширования: 
получаем данные ESI 
<!--esi <esi:include src="/user_blocks/... " /> --> 
За Против 
◻ Пользователь видит готовую страницу 
◻ Не зависит от включенного JS* 
◻ Каждый блок создаст свой запрос в 
Drupal* 
◻ Сложность реализации 
Browser 
Varnish 
Per-role 
cache 
Per-user 
cache 
Main 
request 
ESI 
Web 
server
User-blocks для per-role кэширования: включаем 
поддержку ESI в Varnish 
Чтобы Varnish обработал ESI, необходимо ему явно указать сделать это для каждой страницы 
которая использует ESI. Лучше помечать такие страницы в Drupal каждый раз когда вы генерируете 
ESI тэг. 
Drupal: 
drupal_add_http_header(‘DOESI’, 1); 
Varnish: 
sub vcl_fetch { 
/* Check if we should process ESI on this page. */ 
if (resp.http.DOESI == "1") { 
set beresp.do_esi = true; 
} 
}
User-blocks для per-role кэширования:чем 
можно чистить кэш в varnish 
Purge Ban 
sub vcl_hit { 
if (req.request == "PURGE") { 
purge; 
error 200 "Purged."; 
} 
} 
sub vcl_miss { 
if (req.request == "PURGE") { 
purge; 
error 200 "Purged.“; 
} 
} 
sub vcl_xxxx { 
ban("req.url ~ ^/user_blocks.*$"); 
ban("obj.http.myheader == myvalue"); 
} 
◻ Для ban доступны любые 
метаданные. 
◻ ban фильтрует только уже 
кэшированные объекты, и не мешает 
новым попасть в кэш. 
◻ ban можно вызвать из терминала. 
◻ Позволяет «тегать» кэш.
User-blocks для per-role кэширования:тегаем Varnish 
кэш из Drupal 
Лучше собирать все теги в статик переменную, и выводить в заголовки ответа в delivery 
callback страницы. Но можно и иметь разные заголовки для разных тегов. 
/* Page callback for /user_blocks. */ 
function mymodule_page_user_blocks() { 
global $user; 
drupal_add_http_header("userblocks", $user->uid); 
/* Prepare user block data. */ 
… 
}
User-blocks для per-role кэширования:очищаем 
Varnish кэш из Drupal 
Используем API модуля varnish (https://drupal.org/project/varnish) для отсылки команд в 
терминал Varnish. Для этого необходим PHP с --enable-sockets. 
/** Purge user blocks for specific user. */ 
function mymodule_purge_user_blocks($uid) { 
_varnish_terminal_run(array("ban obj.http.userblocks ~ "$uid"")); 
} 
/** Implements hook_flag(). */ 
function mymodule_flag($op, $flag, $content_id, $account, $fcid) { 
/** Invalidate user blocks (user favorites block and list) */ 
mymodule_purge_user_blocks($account->uid); 
… 
}
User-blocks для per-role кэширования:очищаем 
Varnish кэш во время запроса. 
Если нету доступа к терминалу Varnish, можно очистить кэш user-blocks прямо во время 
запроса, если в качестве тега использовалась имя сессии. 
sub vcl_recv { 
/* If user make flag action we need to clean up user-blocks cache. */ 
if (req.url ~ "^/flag/.*$") { 
/* Require session Cookie. */ 
if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") { 
/* Clean user-blocks for this session. */ 
ban("obj.http.userblocks == " + hash_data(regsub(req.http.Cookie, "^.*? 
SESS(w*);*.*$", "1"))); 
} else { error 750 “Sanity error”; } 
return(pass); 
} 
}
Полезные ссылки 
Описание работы Varnish и VCL 
◻ https://www.varnish-cache.org/docs/3.0/reference/vcl.html 
◻ https://www.varnish-software.com/static/book/VCL_Basics.html 
Полезные модули Drupal.org 
◻ https://drupal.org/project/varnish - доступ к терминалу Varnish, позволяет быстрее 
начать с ним работу. 
◻ https://drupal.org/project/esi_api - API для создания ESI тегов, встроенный фоллбек 
на AJAX.
Спасибо за внимание!

Александр Шуменко - Varnish for authenticated users

  • 1.
    VARNISH FOR AUTHENTICATED USERS Шуменко А.Е. (ashumenko@adyax.com)
  • 2.
    О чём мыбудем говорить ◻ Varnish Reverse Proxy ◻ Принципы конфигурации Varnish ◻ Зачем и как разделять кэш ◻ Кэширование для пользователей ◻ Кэширование для ролей ◻ Очистка кэша
  • 3.
    Varnish Reverse Proxy ❑ Browser – Varnish – Web server ❑ Жизнь запроса внутри Varnish ❑ Конфигурация Varnish (*.VCL файл)
  • 4.
    Browser – Varnish– Web server Browser Varnish Cache First request 2nd (nth ) request ❑ Первый запрос попадает к web серверу ❑ Остальные запросы обслуживаются из кэша Web server
  • 5.
    Скорость работы Varnish Условия теста: $ ab -c10 -n50 http://mysite.my/ Условия теста Время теста (с) Время запроса (с) Web server (вся страница) 62.301 1.246 Web server (bootstrap) 23.041 0.461 Web server (return) 0.048 0.001 Varnish (Холодный кэш) 1.121 0.022 Varnish (Горячий кэш) 0.029 0.0006
  • 6.
    Жизнь запроса внутриVarnish Varnish Cache vcl_recv{} lookup vcl_hash{} Web server pass vcl_deliver{} vcl_fetch {} hit cache miss cache Request (HTTP) Response
  • 7.
    Конфигурация Varnish (vcl_recv) Нормализировать данные для web- приложения. Выбрать политику кэширования. Управление доступом. sub vcl_recv { /* Add custom header. */ set req.http.X-Forwarded-For = client.ip; /* Do not cache POST. */ if (req.request == “POST”) { return (pass); } /* Do not cache authenticated users and Authorization request. */ if (req.http.Authorization || req.http.Cookie) { return (pass); } /* Mark other request as cacheable. */ return (lookup); }
  • 8.
    Конфигурация Varnish (vcl_hash) Возвращает ID запроса. Позволяет иметь разный кэш одной и той же страницы. ВАЖНО: Hash строится только из переменных запроса. sub vcl_hash { /* Make page ID based on request. */ hash_data(req.url); if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); } return (hash); }
  • 9.
    Конфигурация Varnish (vcl_fetch) Указать время кэширования страницы. sub vcl_fetch { /* Cache TTL can be controlled from backend. */ if (beresp.http.cache-control !~ “s-maxage”) { /* Cache JPG images for 60 seconds. */ if (req.url ~ “.jpg$”) { set beresp.ttl = 60s; } } /* Unset Cookies for cached pages. */ if (beresp.ttl > 0s) { unset beresp.http.Set-Cookie; } return (deliver); }
  • 10.
    Зачем и какразделять кэш ❑ Кэширование per-user ❑ Кэширование per-role ❑ User-blocks для per-role кэширования
  • 11.
    Кэширование per-user: VCLfunctions На практике почти не применимо само по себе. В большинстве, пользователи посещают разные страницы Размер кэш базы может колоссально возрасти sub vcl_recv { /* Remove cookie from pages that should be same for all users. */ if (req.url ~ ".(png|gif|jpg|swf|css|js)$") { unset req.http.cookie; } } sub vcl_hash { /* Add User specific (session) Cookie to page ID. */ if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") { hash_data(regsub(req.http.Cookie, "^.*?SESS([^;]*);*.*$", "1")); } }
  • 12.
    Кэширование per-role: DrupalHooks В Drupal необходимо установить Cookie уникальное для роли пользователя (комбинации ролей пользователя). Используем hash() и шум для усложнения подбора Cookie чужой роли. /** Implements hook_user_login(). */ function hook_user_login($edit, $user) { $roles = array_filter(array_keys($user->roles)); sort($roles); $bin = hash('sha256', implode('_', $roles) . 'SECRET_KEY_ABC'); $params = session_get_cookie_params(); $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; setcookie('BIN', $bin, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']); } /** Implements hook_user_logout(). */ function hook_user_logout() { $params = session_get_cookie_params(); setcookie('BIN', '', REQUEST_TIME - 3600, $params['path'], $params['domain']); }
  • 13.
    Кэширование per-role: VCLfunctions В Varnish необходимо добавить этот Cookie к ID (hash) всех запросов. sub vcl_recv { /* Remove cookie from pages that should be same for all users. */ if (req.url ~ ".(png|gif|jpg|swf|css|js)$") { unset req.http.cookie; } } sub vcl_hash { /* Add Role specific Cookie to page ID. */ if (req.http.Cookie ~ "^.*?BIN=([^;]*);*.*$") { hash_data(regsub(req.http.Cookie, "^.*?BIN=([^;]*);*.*$", "1")); } }
  • 14.
    Кэширование per-role:блоки “паразиты” (user blocks) Необходимо сделать механизм получения информации специфической для каждого пользователя (user links, корзина, избранное, личные сообщения и т.д.).
  • 15.
    Кэширование per-role:дополненный vcl_hash Нужен путь который будет возвращать всю персональную информацию пользователя. Его нужно кэшировать per-user. sub vcl_hash { if (req.http.Cookie ~ "^.*?BIN=([^;]*);*.*$") { /* Require session Cookie. */ if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") { /* Enable per-user cache for selected path. */ if (req.url ~ "^/user_blocks.*$") { hash_data(regsub(req.http.Cookie, "^.*?SESS([^;]*);*.*$", "1")); } } else { error 750 “Sanity error”; } hash_data(regsub(req.http.Cookie, "^.*?BIN=([^;]*);*.*$", "1")); } }
  • 16.
    User-blocks для per-roleкэширования:Основные вопросы при проектировании ◻ Решить как получать данные (AJAX vs ESI). ◻ Как держать кэшированную версию /user_blocks актуальной («правильно» чистим кэш в varnish). ◻ Адаптировать чужие модули (на примере flag модуля)
  • 17.
    User-blocks для per-roleкэширования: получаем данные AJAX <span class=“user-blocks" data-arg="…"></span> За Против ◻ Простота ◻ Один запрос независимо от количества боков ◻ Страница «мигает» ◻ Пользователь видит лишние запросы (и технические URL) ◻ Не работает с выключенным JS Browser Varnish Per-role cache Per-user cache Main request AJAX response Web server
  • 18.
    User-blocks для per-roleкэширования: получаем данные ESI <!--esi <esi:include src="/user_blocks/... " /> --> За Против ◻ Пользователь видит готовую страницу ◻ Не зависит от включенного JS* ◻ Каждый блок создаст свой запрос в Drupal* ◻ Сложность реализации Browser Varnish Per-role cache Per-user cache Main request ESI Web server
  • 19.
    User-blocks для per-roleкэширования: включаем поддержку ESI в Varnish Чтобы Varnish обработал ESI, необходимо ему явно указать сделать это для каждой страницы которая использует ESI. Лучше помечать такие страницы в Drupal каждый раз когда вы генерируете ESI тэг. Drupal: drupal_add_http_header(‘DOESI’, 1); Varnish: sub vcl_fetch { /* Check if we should process ESI on this page. */ if (resp.http.DOESI == "1") { set beresp.do_esi = true; } }
  • 20.
    User-blocks для per-roleкэширования:чем можно чистить кэш в varnish Purge Ban sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged."; } } sub vcl_miss { if (req.request == "PURGE") { purge; error 200 "Purged.“; } } sub vcl_xxxx { ban("req.url ~ ^/user_blocks.*$"); ban("obj.http.myheader == myvalue"); } ◻ Для ban доступны любые метаданные. ◻ ban фильтрует только уже кэшированные объекты, и не мешает новым попасть в кэш. ◻ ban можно вызвать из терминала. ◻ Позволяет «тегать» кэш.
  • 21.
    User-blocks для per-roleкэширования:тегаем Varnish кэш из Drupal Лучше собирать все теги в статик переменную, и выводить в заголовки ответа в delivery callback страницы. Но можно и иметь разные заголовки для разных тегов. /* Page callback for /user_blocks. */ function mymodule_page_user_blocks() { global $user; drupal_add_http_header("userblocks", $user->uid); /* Prepare user block data. */ … }
  • 22.
    User-blocks для per-roleкэширования:очищаем Varnish кэш из Drupal Используем API модуля varnish (https://drupal.org/project/varnish) для отсылки команд в терминал Varnish. Для этого необходим PHP с --enable-sockets. /** Purge user blocks for specific user. */ function mymodule_purge_user_blocks($uid) { _varnish_terminal_run(array("ban obj.http.userblocks ~ "$uid"")); } /** Implements hook_flag(). */ function mymodule_flag($op, $flag, $content_id, $account, $fcid) { /** Invalidate user blocks (user favorites block and list) */ mymodule_purge_user_blocks($account->uid); … }
  • 23.
    User-blocks для per-roleкэширования:очищаем Varnish кэш во время запроса. Если нету доступа к терминалу Varnish, можно очистить кэш user-blocks прямо во время запроса, если в качестве тега использовалась имя сессии. sub vcl_recv { /* If user make flag action we need to clean up user-blocks cache. */ if (req.url ~ "^/flag/.*$") { /* Require session Cookie. */ if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") { /* Clean user-blocks for this session. */ ban("obj.http.userblocks == " + hash_data(regsub(req.http.Cookie, "^.*? SESS(w*);*.*$", "1"))); } else { error 750 “Sanity error”; } return(pass); } }
  • 24.
    Полезные ссылки Описаниеработы Varnish и VCL ◻ https://www.varnish-cache.org/docs/3.0/reference/vcl.html ◻ https://www.varnish-software.com/static/book/VCL_Basics.html Полезные модули Drupal.org ◻ https://drupal.org/project/varnish - доступ к терминалу Varnish, позволяет быстрее начать с ним работу. ◻ https://drupal.org/project/esi_api - API для создания ESI тегов, встроенный фоллбек на AJAX.
  • 25.

Editor's Notes

  • #12 Пример сайта розетка, просматриваю много продуктов по 1 разу Сделать акцент на том что не применимо само по себе как дефолтное правило для разделения кеша, но необходимо для кешировани по ролям.
  • #13 Роли могут возвратиться в произвольном порядке поэтому необходимо их сортировать. Используем мд5 чтобы усложнить подбор куки для другой роли. Процедура формирования куки такая же как в сессион инк.
  • #14 В таком виде не применимо так как зареганые пользовати должны видеть разные страницы
  • #16 Ошибки обрабатываются vcl_error но мы ее не рассматриваем. Если мы собираемся учитывать куку роли, нам необходимо чтобы была установлена кука сесии Также путь к юзер блокам мы кэшируем пер юзер. /user_blocks/%uid не подходит так как юид будет для всех одинаковый.
  • #19 Стоит сказать про СЕО но для авторизированых польователей СЕО не есть приоритет.
  • #21 Правильно выбрать ТТЛ пол дела, очистку некоторых объектов предсказать нельзя, и нам обязательно иметь их актуальными.
  • #23 PHP с --enable-sockets по дефолту, но некоторые провайдеры его могут отключить.
  • #24 Потенциальное место для инжекшена ибо вы исполняем юзер инпут важно правильно сделать регулярку.