Блочное кеширование и MVC

Lightning

Трудоголик
Блочное кеширование и MVC

Как делаю я.
В контроллере проверяю существует ли кэш данного блока. Если существует, то загружаем этот кэш, если нет, то создаем объект(ы) модели, информация из которого(ых) выводится в данный блок.
В шаблоне приходится делать что-то вроде этого:
PHP:
if( block( 'name' ) ) {
    //блок
} endBlock( 'name' );
В принципе, это та же проверка кэша блока (+создание кэша, если его нет). Получается, одна и та же структура проверок повторяется и в контроллере, и в шаблонах.
Я пока не знаю как от этого уйти. Мне кажется, что в рамках MVC никак. Первое, что приходит в голову - активные шаблоны. Эта идея мне тоже не нравится, т.к. часто контроллер может использовать разные шаблоны, а один шаблон может использоваться разными контроллерами, т.е. view и controller приходится разделять.

Вопрос:
- Как лучше (в плане качества кода) реализовать логику блочного кэширования в рамках MVC?
 

john.brown

просто кулибин
А зачем еще чего то в шаблоне проверять? Имхо, шаблону должно быть глобоко по барабану сушествование кэша - он должен получать готовый блок/данные (как уж у тебя там реализовано), и не париться...

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

MiksIr

miksir@home:~$
Предпочитаю унести тот кеш, о котором речь, на урвень выше ;) Т.е. вообще на уровень веб-сервера (проверка по статик-кешу, если мисс - на бекенд) и блочность (когда она нужна) реализовывать обычными SSI инструкциями. А второй уровень кеша ниже - кеширует объекты моделей и результаты выборок.
 

Lightning

Трудоголик
john.brown
В любом случае до шаблона всегда доходит одно и тоже... А ему пофиг, от куда оно взялось.
Это кэширование данных, которые возвращает модель. Его я тоже использую. Сейчас я говорю про кэширование блоков страницы.
MiksIr
блочность (когда она нужна) реализовывать обычными SSI инструкциями
Круто. Только я пока не совсем понимаю как тут можно SSI применить...
 

Alexandre

PHPПенсионер
Только я пока не совсем понимаю как тут можно SSI применить..
MiksIr использует nginx
если используешь аппач, то это не для тебя
есть хорошая статья Котерова про кеширование на сервере
см раздел Динамическое "окно" в закэшированной странице

думаю, если еще немного подкрутить колесики, то можно использовать в качестве кеша, не файловый кеш, а - мемкеш.

MiksIr я еще не перегорел идеей вообще делать шаблонизацию на стороне сервера. Времени нет все хорошо обдумать.
 

MiksIr

miksir@home:~$
Ну немного подробнее. У нас есть основной контроллер - тот, что роутер вызывает на основе запрошенного узла, и есть дополнительные блоки данных - рекламные блоки, всякие "последние новости" и т.д. Для главной страницы по сути основной контроллер ничего не делает - все состоит из таких вот блоков.

Блок может быть встроен в страницу двумя способами. В первом случае это просто вызов соответствующего его контроллера, получение данных и размещение на странице. Но при этом эти данные в статическом кеше невозможно обновить, не грохнув всю страницу.

Второй способ - блок выдает SSI инструкцию. Эта страница кладется в статический кеш. Когда сервер (nginx) берет ее с диска, отрабатывает SSI инструкции и делает подзапрос на бекенд (PHP) на определенный специальный URL, по которому роутер вызывает именно контроллер этого маленького блока.
Причем, данные этого маленького блока так же кладутся в статический кеш - nginx это позволяет.

У второго способа есть минусы - это подзапросы, т.е. что бы первый раз собрать страницу с 10 такими блоками - уходит 10 запросов на бекенд. По-этому ими не злоупотребляем, а используем когда время жизни блока мало по сравнению со всей страницей или же если этот блок на многих страницах сайта. Решается это руками.

По тому же принципу сделана выдача разного контента для разных пользователей. Например, погода в зависимости от города - nginx определяет по траблице IP город посетителя и делает запрос на URL с городом проверяя - не лежит ли все это в кеше уже.

-~{}~ 18.11.09 15:50:

Alexandre - начинай использовать XSLT :) модули для nginx есть. С моей точки зрения это не очень удобно и стремно в плане безопасности - верстальщик делает шаблоны для фронт-енда, т.е. потенциально может случится обвал ;)

-~{}~ 18.11.09 16:00:

думаю, если еще немного подкрутить колесики, то можно использовать в качестве кеша, не файловый кеш, а - мемкеш.
Его плюс - это не нужно парится с синхронизацией кешей - любая нода может удалить данные. В случае однонодовой конфигурации файлы все же предпочтительнее, имхо ;)

-~{}~ 18.11.09 16:03:

PS
В принципе что-то подобное можно реализовать и без всяких внешних серверов и SSI. В принципе тогда получится приблизительно то, о чем john.brown говорит.
 

Lightning

Трудоголик
есть хорошая статья Котерова про кеширование на сервере
см раздел Динамическое "окно" в закэшированной странице
Второй способ - блок выдает SSI инструкцию. Эта страница кладется в статический кеш. Когда сервер (nginx) берет ее с диска, отрабатывает SSI инструкции и делает подзапрос на бекенд (PHP) на определенный специальный URL, по которому роутер вызывает именно контроллер этого маленького блока.
Понятно.
Если использовать SSI, то придется хранить шаблоны блоков в отдельных файлах. У меня сейчас в одном файле с шаблоном может быть описано несколько блоков. Но если я сделаю так, чтобы шаблоны блоков хранились в отдельных файлах, то мне SSI будет не нужен, можно обойтись без него. Только шаблоны станут неудобными, раздробленными.

-~{}~ 18.11.09 17:25:

Если на странице одно или два "динамических окна", тогда SSI это конечно круто.
 

MiksIr

miksir@home:~$
Хм, а как это - шаблоны блоков в одном файле? Или у вас один контроллер собирает из кучи моделей страницу со всеми блоками?

Обычно есть шаблон страницы, где проставлены места для блоков, а сами блоки - это независимая MVC цепочка - свой контроллер, свой шаблон... иначе как одинаковые блоки размещать на разных страницах?
 

john.brown

просто кулибин
Lightning
Нет, это не кэширование данных. Как правильно подметил, шаблоны блоков хранятся в отдельных файлах. А общий шаблон токмо содержит метки, куда какой блок вставить. Кэшируется именно хтмл блоков, а не данные. Т.е. то, что описал MiksIr, токмо без SSI.

По поводу "шаблоны станут неудобными, раздробленными" не соглашусь - удобнее небольшие шаблоны на каждую логическую единицу, чем один большой на все случаи жизни. Это как с ооп - приятнее много маленьких классов, чем один большой ;) Но это, конечно, мое имхо...
 

Lightning

Трудоголик
MiksIr
Хм, а как это - шаблоны блоков в одном файле? Или у вас один контроллер собирает из кучи моделей страницу со всеми блоками?
Нет. Для блоков, повторяющихся на нескольких страницах, делаю независимую цепочку MVC. А страница "собирается" из таких независимых блоков. Но это тут не при чем.
Я говорю про блоки, которые делаются исключительно для того, чтобы раздельно кэшировать часто меняющиеся и редко меняющиеся части, а не про блоки, которые делаются для того, чтобы включать в разные страницы одинаковые части.
 

john.brown

просто кулибин
чтобы раздельно кэшировать часто меняющиеся и редко меняющиеся части
Или я чего то не понял, или как то очень заумно... Что мешает при обновлении модели пометить кэш соответствующий как невалидный? Или (если по времени кэшировать) поставить разное время валидности для кэша разных блоков?
 

Lightning

Трудоголик
john.brown
Нет, это не кэширование данных. Как правильно подметил, шаблоны блоков хранятся в отдельных файлах. А общий шаблон токмо содержит метки, куда какой блок вставить. Кэшируется именно хтмл блоков, а не данные. Т.е. то, что описал MiksIr, токмо без SSI.
Обычно так разбиваю только когда нужно сделать одинаковые блоки на разных страницах. А если мне просто нужно раздельно кэшировать части блока (т.к. одна из них практически не меняется, а вторая меняется очень часто), не буду же я разбивать его на два и строить для каждой части свой конроллер, свой шаблон и т.д. Достаточно для каждой части свою модель.
 

MiksIr

miksir@home:~$
Тогда есть два варианта. Или кешировать сами данные в контроллере или искать/учить/писать шаблонизатор, которому можно указать блок для кеширования. Но смысла во втором варианте не вижу никакого - все равно у нас сработает шаблонизатор, а выигрыш - сгенерить блок на основе данных vs взять этот блок из статики ничтожно мал. Так что кешировать данные для того, что бы разгрузить базу.
 

korchasa

LIMB infected
Автор оригинала: MiksIr
Но смысла во втором варианте не вижу никакого - все равно у нас сработает шаблонизатор, а выигрыш - сгенерить блок на основе данных vs взять этот блок из статики ничтожно мал.
Насчет ничтожно можно сильно поспорить. Проверочное слово - "облако тэгов" :)
Хотя нет, тут спустить данные тоже можно.

-~{}~ 18.11.09 19:27:

Зато, как хорошо тэг cache ложиться на ситауцию, когда данные тянуться самим шаблоном :)
 

MiksIr

miksir@home:~$
Хотя нет, тут спустить данные тоже можно.
Конечно... ибо даже самые сложная логика в шаблоне обычно весьма примитивна.
Зато, как хорошо тэг cache ложиться на ситауцию, когда данные тянуться самим шаблоном
Если данные тянуться шаблоном - значит есть откуда тянуть. Раз есть откуда тянуть - посредине можно влепить кеширующую прослойку. Нет, конечно можно влепить это все и в шаблонизатор, но выглядит излишком. По мне, так кеширующий датамапер решает большинство задач, а сложные расчетные задачи типа облака тегов, стараемся считать на апдейтах и хранить в базе уже посчитанное.
 

korchasa

LIMB infected
Автор оригинала: MiksIr
Нет, конечно можно влепить это все и в шаблонизатор, но выглядит излишком.
С другой стороны зачем делать, если можно не делать. Уж больно красивым он получается:
PHP:
{{cache key="tags" ttl="180"}}
   <?php $tags = Tags::find()->sort('count', 'DESC')->limit(10); ?>
   {{include file="_blocks/tags_cloud.phtml" tags="$tags"/}}
{{/cache}}
В итоге никаких запросов к данным, никакой отрисовки, никакого дублирования логики.
 

MiksIr

miksir@home:~$
Это в случае тупого кеширования на TTL=180. Возможно. Хотя
PHP:
   <?php $tags = Tags::find()->sort('count', 'DESC')->limit(10); ?>
   {{include file="_blocks/tags_cloud.phtml" tags="$tags"/}}
И кеширование внутри find - не менее красиво ;)
А вот стоит начать думать про валидацию кеша... а про него все же стоит думать, ибо не дело ставить TTL=180 тому, у кого среднее время жизни несколько суток, к примеру ;)

-~{}~ 18.11.09 20:54:

Т.е. кеширование внутри find удобнее во-первых тем, что не привязано к шаблону, а значит потенциально можно использовать для разных целей - и в других шаблонах, и в контроллерах... ну и тем, что легче проставлять маркеры, по которым этот кеш можно сбрасывать.

А лепить одновременно кеш внутри мапера и еще добавить кеш внутри шаблона.... по мне, так излишество. ИМХО, конечно, но дополнительный кеш шаблона даст копейки выигрыша, так как запрос все-равно придет в PHP, поднимет всю цепочку фильтров и т.д. и т.п.
 

korchasa

LIMB infected
Автор оригинала: MiksIr
Т.е. кеширование внутри find удобнее во-первых тем, что не привязано к шаблону, а значит потенциально можно использовать для разных целей - и в других шаблонах, и в контроллерах... ну и тем, что легче проставлять маркеры, по которым этот кеш можно сбрасывать.
Я в курсе :)
Просто для нас, как ты понимаешь, поставить такой кэш очень дешевое решение, как по реализации, так и по поддержке. Поэтому мест, куда это можно воткнуть - миллион. В общем все что едино для многих пользователей, и где разрешено отставание. Последние сообщения, популярные статьи, список категорий с количеством статей, etc.
 

MiksIr

miksir@home:~$
Э, ну, если задача - приделать костыль быстрого кеширования в архитектуру, где об этом кешировании и не думали, и при этом данные с коротким но известным временем жизни - то почему бы и нет.
Но все ж речь ведем об изначальном проектировании... ;)
 
Сверху