Я очень люблю Template Toolkit и считаю его лучшим по набору возможностей, удобству и гибкости шаблонизатором ;)
Но есть одна проблема с этим шаблонизатором, а именно: за всё надо платить.
В данном случае за функциональность и гибкость приходится платить скоростью обработки.
Долгое время считал эту проблему нерешаемой, приходилось с грустью констатировать, что обработка шаблона стоит на первом месте по затратам времени при генерации большинства страниц...
Но хотелось невозможного...
Возникла идея кэшировать так называемые "подшаблоны", т. е. шаблоны, вызываемые из шаблонов более верхнего уровня.
То есть, например, у нас есть шаблон index.tt, в нём инклюдятся (с помощью INCLUDE или PROCESS) подшаблоны header.tt и footer.tt (общие шапка и подвал для всего сайта).
В header.tt и footer.tt инклюдится ещё что-то и т. п.
Так вот, мы заметили, что именно обработка подшаблонов в нашем случае занимает довольно много времени, до 90% и более от общего времени работы TT. При этом многие подшаблоны тянут данные из базы (с помощью моделей Catalyst) или ещё откуда-то, содержат кучу сложной логики. Всё это обрабатывается достаточно долго...
При этом, что самое обидное, выдача подшаблонов (те данные, что подставляются в вышестоящий шаблон) практически статична, мало меняется во времени.
Конечно, с кэшированием не всё так просто: подшаблоны могут отдавать разную выдачу в разных контекстах --
например, выдача может зависеть от того, залогинен ли пользователь в системе и от того, какой выбран язык сайта.
Но все эти проблемы решаемы -- я расскажу как.
Так вот...
Путь к невозможному начался с попыток профайлинга времени обработки подшаблонов.
В процессе исследования темы наткнулся на замечательную статью Profiling in Template Toolkit via overriding, без которой этот текст бы сейчас не писался.
Затем исследование продолжилось.
И, наконец, закончилось написанием модуля Template::Context::SRS.
Хотел рассказать об этом модуле на YAPC 2010 и к тому времени "причесать" и выложить на CPAN, но чувствую не дотерплю и выкладываю сейчас. ;)
Перед выкладкой на CPAN модуль, конечно, нужно причесать: написать документацию и тесты и отвязать от нашего внутреннего модуля "SRS::Cache", но это очень легко делается, просто нужно этим заняться. Может быть кто-то даже поможет с этим ;)
Тем не менее, всё это работает. Работает стабильно. Уже несколько месяцев на production: "не единого разрыва" по причине механизма кэширования.
* * *
Инструкция по эксплуатации.
Всё очень просто:
1. Где-то в проекте тупо делается "use Template::Context::SRS;".
2. Затем в шаблоне, в котором хотим закэшировать выдачу какого-то подшаблона, делаем как-то так:
[% PROCESS index_reg_bar.inc
lang = lang
pricegroup = pricegroup
__cache_time = -1
%]
Ключевым здесь является передача служебной переменной "__cache_time". Она и включает кэширование.
"-1" означает -- кэшировать вечно. Но можно задать конкретное время устаревания кэша в секундах.
Прочие параметры имеют не менее сакральный и важный смысл -- это не просто передача параметров в подшаблон,
а ещё и критерий для формирования ключа кэширования.
Ключи кэширования имеют значение в том случае, если выдача подшаблона зависит от каких-то условий: текущего языка и т.п.
Так вот, уникальная комбинация имён / значений передаваемых параметров и будет служить уникальным ключом кэширования.
Например, в предыдущем примере выдача шаблона зависит от двух параметров -- языка (lang) и тарифного плана пользователя (pricegroup).
В нетривиальных случаях, когда подшаблон достаточно чувствителен к контексту и зависит от множества параметров, вызов может быть достаточно сложным:
[%- PROCESS
header_screen_body_cached.inc
new_messages_num = user_messages.size() || user_messages_num || 0
__nocache_banner_no = banner_no
__nocache_lang = lang
__nocache_wide_flag = wide_flag
__nocache_path_query = path_query
__nocache_user_id = user_id
__nocache_user_balance = user_balance
__nocache_servername = servername
__nocache_redirect_after_login = redirect_after_login
__nocache_path = path
__nocache_show_tooltips = show_tooltips
__nocache_show_tipsofday = show_tipsofday
__nocache_nostdheader = nostdheader
__nocache_tab_item = tab_item
__nocache_subtab_item = subtab_item
__nocache_load_token_js = load_token_js
__nocache_title_nopad = title_nopad
__nocache_page_title_body = page_title_body
__nocache_page_title = page_title
__nocache_req_method = c.req.method
__cache_time = user_messages.size() ? 0 : -1
-%]
Параметры, начинающиеся с "__nocache_" -- фиктивные, используются только для формирования ключа кэширования.
В подшаблон они не попадают -- удаляются перед его вызовом.
В данном случае значения параметров, передаваемых через "__nocache_", всё равно попадают в шаблон -- как соответствующие глобальные переменные.
Например, "page_title" -- переменная, видимая в текущем неймспейсе и она же будет видна в неймспейсе подшаблона.
Можно было бы, конечно написать "page_title = page_title", но, по моему, это выглядит как-то странно.
С "__nocache_" вроде бы это более явно -- ясно, что этот параметр нужен только для формирования ключа кэширования и не используется в подшаблоне.
* * *
Итак, всё это работает. Результат достигнут -- обработка многих тяжёлых шаблонов, например, главной страницы, ускорилась в разы.
Этот текст занимает больше байт, чем сам модуль ;)