Cache MODX Revolution -> быстрее только на html.

Cache MODX Revolution
Всем привет, давно не писал ничего. Писать есть про что и очень много, да вот с временем не сильно.
MODX — очень хороший конструктор для сайтов, на нём очень легко натягивать любой дизайн, да и реализовывать многие вещи очень удобно и быстро, есть конечно и подводные камни, но это как у всех. Идеального ничего не бывает.
Зачастую у нас в сайтах очень много статических страниц и даже если страницы в кэше, вы можете посмотреть, сколько у нас инклудиться файлов , просто создайте плагин и на событие OnHandleRequest вставьте код:
if ($modx->context->key == 'mgr') break;
echo '<pre>';
print_r(get_declared_classes());
echo '</pre>';
die;

Я не буду говорить, что это самый классный способ, про который я сейчас расскажу, он сырой — но рабочий. Просто делюсь, возможно кому-то и понадобиться.

И так, для простоты понимания возьмём установим стандартные пакеты Wayfinder и getResources.
Для эксперемента нашего так-же я установил пакет Todc.Bootstrap (можно Bootstrap или просто сами стилить будете, на скорость не влияет)
В самом шаблоне Todc.Bootstrap я закооментил тег $Todc.Scripts ([[-$Todc.Scripts]]), так как нам для эксперемента они не нужны и в чанке Todc.Navbar удалил Breadcrumbs.
Создаём контейнер для вывода к примеру новостей и сами новости (я создал для эксперемента три новости)
в документе конейнере ставим вызов getResources:
[[getResources? &tpl=`multTplgetResources` &includeContent=`1`]]

и шаблон, чанк multTplgetResources:
<article class="blog-article">
	<div class="row">
		<div class="span9">
			<div class="blog-content">
				<div class="blog-content-title">
				<h2><a href="[[+uri]]" class="invarseColor">[[+pagetitle]]</a></h2>
				</div>
				<div class="clearfix">
					<ul class="unstyled blog-content-date">
						<li class="pull-left"><i class="icon-calendar"></i>[[+publishedon:strtotime:date=`%d %B, %Y`]]</li>
						<li class="pull-left"><i class="icon-pencil"></i> <a href="#">[[+createdby:userinfo=`fullname`]]</a></li>
					</ul>
				</div>
				<div class="blog-content-entry">
					<p>[[+content:ellipsis=`400`]]</p>
					<a href="[[+uri]]">Contunie →</a>
				</div>
			</div><!--end blog-content-->
		</div><!--end span5-->
</div><!--end row-->
</article>

В футер. чанк Todc.Footer вставил вывод времяни:
<span class="debug"><small>Processing Time: <span>[^t^] </span></small></span>

Наполнили внутреннии ресурсы текстом. Проверяем, с кэша примерно выходит около 0,0463 s. Вроде не плохо, но если глянуть даже в сам APC кэш, то хитов и размер файлов нашей странички среди файлов модекса как иголка в стоге сена.

Ну попробуем по эксперементировать, т.е. я не буду сейчас расписывать выборку ресурсов, которые нам нужно кэшировать нашим способом, а которые пропускать. Просто поэксперементируем.
Итак. создаём плагин myCache, ставим галочку на событие OnWebPagePrerender и вставляем код (если у нас в плагине одно событие. не обязательно использовать $modx->event->name, но у нас ещё будут события, поэтому пока оставим так.):
switch ($modx->event->name) {
	case 'OnWebPagePrerender':
	$file_cache = false;
	// аолучаем с конфига параметры
	$cache_prefix = $modx->getOption('cache_prefix');
	$cache_handler = $modx->getOption('cache_handler');
	// формируем ключ кэша
	$file = md5($_SERVER['REQUEST_URI']);
	// формируем путь для нашего кэша
	$arrayUrl = array_filter(explode('/',$_SERVER['REQUEST_URI']),function($el){ return !empty($el);});
	$pathUri = implode('/',$arrayUrl);
	$cache_path = 'static/'.$pathUri;
	// проверяем какой кэш и существует ли файл в кэше
	switch ($cache_handler) {
		case 'cache.xPDOAPCCache':
			if(apc_exists($cache_path.'/'.$cache_prefix.$file)) {
				$file_cache = true;
			}
		break;
		case 'xPDOFileCache':
			if(file_exists(MODX_CORE_PATH.'cache/'.$cache_path.'/'.$file.'.cache.php')) {
				$file_cache = true;
			}
		break;
	}
	// если файла нету в кэше и кэш у нас активный, то пишем вывод в кэш
	if($file_cache == false && $modx->getCacheManager()) {
		$output = &$modx->resource->_output;
		$time = time();
		$cacheArray = array('output'=>$output, 'time'=>$time);
		$modx->cacheManager->set($file,$cacheArray,0,array(
		xPDO::OPT_CACHE_KEY => $cache_path,
		xPDO::OPT_CACHE_HANDLER => $modx->getOption('cache_resource_handler', null, $modx->getOption(xPDO::OPT_CACHE_HANDLER)),
        xPDO::OPT_CACHE_FORMAT => (integer) $modx->getOption('cache_resource_format', null, $modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)),
		));

	}
	break;
}

Т.е. мы сохранили вывод страницы в свой кэш, в качестве ключа использовал урл страницы, я эксперементировал только c:
  • xPDOFileCache — стандартный обработчик по умолчанию, хранит кэш в файлах.
  • cache.xPDOAPCCache — обработчик для расширения php-apc (не забудьте создать префикс с ключом cache_prefix в системных настройках)
Теперь, создадим в корне нашего сайта файл read_cache.php и вставим код:
<?php 
  // функция для вывода времяни (можно не использовать)
  function microtime_float() {
      list($usec, $sec) = explode(" ", microtime());
      return ((float)$usec + (float)$sec);
  }
  // функция извлечения массива с файла
  function geFile($key) {
    $value= null;
    if (file_exists($key)) {
      if ($files = @fopen($key, 'rb')) {      
        if (flock($files, LOCK_SH)) {
          $value= @include $key;
          flock($files, LOCK_UN);
            if ($value === null) {
                fclose($files);
                @ unlink($key);
            }
        }
        @fclose($files);
      }
    }
    return $value;
  }
  // стартуем
  $time_start = microtime_float();
  // Получаем название ключа для кэша
  $file = md5($_SERVER['REQUEST_URI']);
  // формируем путь для нашего кэша
  $arrayUrl = array_filter(explode('/',$_SERVER['REQUEST_URI']),function($el){ return !empty($el);});
  $pathUri = implode('/',$arrayUrl);
  if(strlen($pathUri) > 0) $pathUri .= '/'; 
  // достаём с кэша конфиг файл 
  $system_cache_pach = MODX_CORE_PATH.'cache/system_settings/config.cache.php';
  $valueFile = geFile($system_cache_pach);  
  // берём с настроек модекса префикс кэша и время жизни кэша
  $cache_prefix = $valueFile['cache_prefix'];
  $cache_expires = intval($valueFile['cache_expires']);
  // проверяем. если есть файл в кэше
  switch ($valueFile['cache_handler']) {
    case 'cache.xPDOAPCCache':      
      $cache_key = "static/$pathUri$cache_prefix$file";
      // проверяем, есть ли файл в кэше
      if(apc_exists($cache_key)) {
        // получаем файл с кэша
        $value = apc_fetch($cache_key);
        // Если его время жизни ещё не прошло или cache_expires у нас нуль, то выводим
        if ((time() - $cache_expires) < $value['time'] || $cache_expires == 0) {
            // подсчитываем время
            $time_end = microtime_float();
            $time = $time_end - $time_start;
            // парсим наш тег [^t^] и заменяем на время
            echo str_replace('[^t^]', 'APCCache: '.round($time,6).' s', $value['output']); // Выводим содержимое файла  
            exit;
        } else {
          // удаляем файл кэша
          apc_delete($cache_key);
        }
      }               
    break;
    case 'xPDOFileCache':
      $cache_file = MODX_CORE_PATH."cache/static/$pathUri$file.cache.php"; 
      // проверяем, есть ли файл в кэше
      if (file_exists($cache_file)) {
        // получаем наш файл
        $value = geFile($cache_file);
        // Если его время жизни ещё не прошло или cache_expires у нас нуль, то выводим
        if ((time() - $cache_expires) < $value['time'] || $cache_expires == 0) {
            // подсчитываем время
            $time_end = microtime_float();
            $time = $time_end - $time_start;
            // парсим наш тег [^t^] и заменяем на время
            echo str_replace('[^t^]', 'FileCache: '.round($time,6).' s', $value['output']); // Выводим содержимое файла  
            exit;
        } else {
          // удаляем файл с кэша
          unlink($cache_file);
        }
      }
    break;
  }

Подключаем наш файл в index.php примерно после строчки 39, после инклуда конфига:
.......
  /* this can be used to disable caching in MODX absolutely */
$modx_cache_disabled= false;

/* include custom core config and define core path */
@include(dirname(__FILE__) . '/config.core.php');
if (!defined('MODX_CORE_PATH')) define('MODX_CORE_PATH', dirname(__FILE__) . '/core/');

// Пытаемся вывести содержимое кэша
require_once "read_cache.php"; 

/* include the modX class */
.......

Очищаем кэш, и пробуем. Первый вызов как обычно без кэша, а второй и последующий Processing Time: APCCache: 0.0004 s — 0.0005 s. Не плохо?

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

Код не вылизанный, просто наброски для эксперемента, чтобы увидеть результат.

Посмотреть в реальности можете по адресу http://talks.artdevue.com/test-multilanguage/ (возможно работать и не будут, так как это тестовый сайт, может кто и успеет увидеть). Там правда вывод идёт с мультиязычностью, об этом тоже напишу топик и выложу код.
В кратце про мультиязычность: Это расширение, в админке добавляете нужные языки указывая к каким контекстам они активны. Для каждого языка можно добавить свои параметры (как в контекстах).
При создании ресурса, у вас будет вкладка для переводов. Всё подгоняеться под стандартные расширение, такие как Wayfinder, getResources, getPage… т.е. никаких изминений в них не нужно делать.

20 комментариев

avatar
Это офигенно! Счас будем эксперементировать на инет-гамазинах. Если чего наэксперементируем — отпишусь.
avatar
на инет магазине? для каталога можно, но для магазина у вас не выйдет, думаю не нужно объяснять. что нужно для работы магаза. :)
avatar
Напиши, пожалуйста, характеристики сервака, на котором тестируешь
И код может на гитхаб выложить?
Комментарий отредактирован 2013-03-27 04:43:44 пользователем SurRealistik
avatar
Это все, конечно, круто. Но пока я вижу 2 самых главных нерешенных проблемы
1) Не всегда выводом дочерних документов занимается только родитель
2) Время жизни кеша который оптимально сможет поставить только опытный программист постоянно и тесно работающий с сайтом. И это очень частая проблема многих кешеров. Ведь если время жизни кеша установить слишком большим — то ловим глюк, что на странице А информация уже обновилась, а на странице Б мы все еще видим старую инфу. В случае, если поставить время жизни маленькое — то смысл такого подхода вообще теряется.

Я как-то предлагал кастрированую версию кешера, но мой вариант дико заминусовали и он не совместим с некоторыми дополнениями. Поэтому я его юзаю сам:-) А смысл там был такой: удалить все лишнее при инициализации парсера и не формировать кеш-файлы по 3 Мбайта с 1 страницы. Именно благодаря тому, что результатирующий объем кеш-файла был существенно меньше, то и загрузка страницы происходила быстрее, т.к. чем меньше объема информации нужно записать/прочитать, тем быстрее скрипт завершит свое выполнение:-) И делалось это все хаками самого ядра (видимо поэтому минуса и были), т.к. переопределить некоторые функции не было возможности из-за того, что они подгружаются раньше, чем все возможность замены парсеров с кешерами. Засим, думаю самым идеальным вариантом будет ковыряние родного кеша в сторону его кастрациии и рефакторинга:-)
avatar
Ну теперь то, официально подобравшись к ядру системы, можно и внедрять системные изменения.
Конечно, если они хорошо проверены и протестированы.
avatar
Ты про EVO или REVO?
avatar
Я же написал. продолжение будут. :) про управлением кэшем.
Самый простой вариант, не устанавливать время жизни кэша, а пусть он будут нуль, т.е. до того, пока его не убъют в ручную.
Если голову не хотите морочить. На событие OnSiteRefresh просто убиваем наш кэш и всё. Но лучше не рубить всё подряд, а сделать логику работы, тогда можно к примеру на событие OnDocFormSave по id уже контролировать свою схему.
Так-что всё думаю зависит от задач и везде можно приспособиться, если оно имеет смысл.
avatar
Похоже на то что реализованно в MODX.jp
но там был очень большой минус не отрабатывались другие плагины тоесть никакой динамики на кешированной страничке( что очень сильно ограничивает

как в вашем решении вопрос с работой плагинов?
avatar
да никак, я же написал изначально, для статических страниц. Если хочеться пострадать и если затраченное время стоит того, то можно создать конектор свой и отправлять на парсер страницу, но думаю здесь уже овчинка выделки стоить не будет. Хотя не пробовал.
avatar
Ясно тогда суть таже что и в MODX.jp ) тоесть мало где можно использовать
особенно где очень нужно )

на визитках то и так пофигу там ускорять то нету смысла сильно а вот на хороших сложных проектах )
avatar
смотря что обрабатывать, если посмотреть место вклинивания, то как раз у нас есть весь конфиг модекса, ну и некоторые вещи в нагруженных страницых можно реализовать простым парсером тегов и прямым обращением к БД (вывод динамических данных). Просто нужно задачу смотреть. Если есть смысл, можно решить — в противном случае и не париться :).
К тому-же это просто как эксперемент, так как очень много в проектах статики.
avatar
Меня смущает большое количесво нужно смотреть думать делать тестировать. В итоге вместо работы над проектом получается работа над MODX.
avatar
А если не думать? Как реализованно в MODX? Грохнуть весь кэш и не париться :) Взять любой фрейм, чтобы построить сайт — нужно думать и управлять всем, так-же и кэшированием. Или реализовывать логику для работы, но снова, без участия человека, сама система не имеет интелекта и как написал выше
Agel_Nash
1) Не всегда выводом дочерних документов занимается только родитель
, т.е. вот здесь уже стандартная логика не подойдёт и нужно думать.
Так-что если есть в этом смысл — то можно подумать, если-же нету, то и не париться. а использовать как есть (ито, всё равно нужно думать :) )
avatar
особенно где очень нужно
Никто не запрещает допилить кеш и юзать его только на определенных страницах. Например на станицах каталога с фильтрами. Как правило там плагинов нет, а сами фильтры очень сложные и порой долго отрабатывают. Но вот если результат фильтра закешировать и сбрасывать его только во время обновления документов по которым происходит фильтрация — то получается оооооочень гибко и быстро. Вот тот же autoFilter взять. Разве там не такая же логика? Да много где такой подход в сниппеты зашивают и ничего — живут себе спокойно))
avatar
xFPS кеширует полностью страницу. Получается html страничка.
avatar
не пробовал и не знал о нём.
Вы пробовали его? Есть тесты? Так как если будет $modx->initialize();, то овчинка выделки малого стоит. Я привёл изначально тест — стандарт кэширование (тоже всё в html, можете глянуть файл кэша) 0,0463 s.
Это расширение я не пробовал. Был бы рад, если вы поделитесь впечатлениями. Может и не нужно изобретать велосипед…
avatar
К стате, спасибо за линк, сейчас посмотрю на это расширение. Жаль что, мало кто описывает подобные расширения и свои впечатления.
avatar
Пробовал. Файлы кеша создаются. Чисто визуально разницы не заметил, потому что и без него работало быстро, а цифры не узнаешь, потому что кеширует страницу полностью, включая время генерации и т.д. Глубже не копал.
Узнал о нем отсюда wdevblog.net.ru/blog/plagin-xfpc-dlya-modx-revolution.html.
Комментарий отредактирован 2013-03-27 15:43:51 пользователем Shin
avatar
Поставил на тест. Я так понял, всё действие происходит через снипет. который отдаёт пустую страницу и аяксом её наполняет.
Только вот даже пустую страницу отдать через модекс, нужно и памяти побольше да и честно говоря, потеря то не большая — модекс тоже кэшированный ресурс отдаёт не плохо (гляньте файл кэша, там всё есть).
В моём предложении была идея миновать всю эту инициализацию и ещё до модекса отдать страницу если она есть. Ну и как идею предложить, так как возможно в плагине кэшировать не весь сайт а определённые ресурсы только.
avatar
спасибо за статью, буду ждать ваш вариант решения уже наболевшего вопроса с мультиязычностью!
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.