Доброго времени суток всем.
Вопрос кеширования маячил передо мной давно, в связи с не разумной схемой кеширования, полным сбросом кеша через определенное время и прочим.
Но вот сейчас вопрос кеширования встал ребром, в связи с тем, что
не работает нормальный режим кеширования для анонимов.
Вот ссылки по теме:
http://drupal.ru/node/18873
http://drupal.org/node/231190
http://drupal.ru/node/18162
Вот я и решил писать свою схему кеширования.
Начну с самого легкого, с кеширования синонимов ссылок (по мотивам вот этого топика: http://drupal.ru/node/19163)
Логика кеширования:
- При инициализации загружаем набор алиасов из специальной таблицы кеша (ее надо сделать). Ключ это данная, конкретная url - ка.
- Функцию drupal_lookup_path() изменяем таким образом, что она ищет сначала алиас в загруженном наборе алиасов и если не находит, то делает запрос к базе, одновременно устанавливаем флаг "обновить кеш алиасов".
- При выходе смотрим есть ли флаг "обновить кеш алиасов" и если есть, то складываем наш массив алиасов для данной страницы в базу данных. Одновременно увеличиваем счетчик обновления кеша на 1.
- При запуске крона удаляем те записи кеша, количество обновлений которого, скажем, больше 5-10 (на ваш выбор).
Технические детали:
Изменяем файл path.inc таким образом:
Дописываем в конец функции drupal_init_path() следующий код:
<?php
$result = db_query("SELECT data FROM url_alias_cache WHERE cid = '%s' AND page = %d", $_GET['q'], $_GET['page']);
$alias_result = db_fetch_array($result);
if (!is_null($alias_result['data'])) {
$GLOBALS['alias_cache'] = unserialize($alias_result['data']);
}
?>
функцию drupal_lookup_path() изменяем совсем чуть чуть:
делаем раз:
<?php $map = (array)$GLOBALS['alias_cache']; ?>
делаем два:
<?php
$GLOBALS['alias_cache'][$path] = $alias;
$GLOBALS['alias_cache']['save'] = TRUE;
?>
это в случае если алиас в нашем массиве не найден и нужно сделать запрос в базу.
делаем три:
<?php
$GLOBALS['alias_cache'][$src] = $path;
$GLOBALS['alias_cache']['save'] = TRUE;
?>
это дублирует предыдущий случай, только для случая если мы ищем системный путь по заданному алиасу.
Дописываем функцию path_exit(). Она сохранит новый кеш алиасов, если были изменения:
<?php
// path_alias_cache_insert
function path_exit() {
$alias_cache = $GLOBALS['alias_cache'];
if ($alias_cache['save']) {
unset($alias_cache['save']);
$alias_cache['count'] = $alias_cache['count'] + 1;
$data = serialize($alias_cache);
db_query("DELETE FROM {url_alias_cache} WHERE cid = '%s' AND page = %d", $_GET['q'], (int)$_GET['page']);
db_query("INSERT INTO {url_alias_cache} (cid, page, count, data) VALUES ('%s', %d, %d, '%s')", $_GET['q'], (int)$_GET['page'], $alias_cache['count'], $data);
db_query ('OPTIMIZE TABLE {url_alias_cache}');
}
}
?>
Для чистки кеша дописываем функцию path_cron()
<?php
function path_cron() {
db_query("DELETE FROM {url_alias_cache} WHERE count > %d", 5);
db_query ('OPTIMIZE TABLE {url_alias_cache}');
}
?>
Новая таблица в базе данных
Как вы уже поняли необходима новая таблица в базе данных url_alias_cache
Вот ее структура:
Те, кого испугала необходимость ковыряться в базе могут не бояться. Я прикладываю к этому посту архив с модулем, который инсталирует все необходимые таблицы при включении, а так же позволит их удалить. В этом архиве, так же, приложен измененный файл path.inc (для Drupal 5.10), который вы можете просто скопировать в папку includes
Предупреждение
Если вы скопируете файл path.inc из приложенного архива в папку includes, то может произойти ошибка базы данных т.к. необходима таблица. Поэтому сначала включите модуль.
Для гуру привожу измененный код drupal_lookup_path(). Изменения подчеркнуты.
<?php
function drupal_lookup_path($action, $path = '') {
// $map keys are Drupal paths and the values are the corresponding aliases
static $map = array(), $no_src = array();
static $count;
$map = (array)$GLOBALS['alias_cache'];
--------------------------------------
// Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
if (!isset($count)) {
$count = db_result(db_query('SELECT COUNT(pid) FROM {url_alias}'));
}
if ($action == 'wipe') {
$map = array();
$no_src = array();
}
elseif ($count > 0 && $path != '') {
if ($action == 'alias') {
if (isset($map[$path])) {
return $map[$path];
}
$alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s'", $path));
$map[$path] = $alias;
$GLOBALS['alias_cache'][$path] = $alias;
-----------------------------------------
$GLOBALS['alias_cache']['save'] = TRUE;
-----------------------------------------
return $alias;
}
// Check $no_src for this $path in case we've already determined that there
// isn't a path that has this alias
elseif ($action == 'source' && !isset($no_src[$path])) {
// Look for the value $path within the cached $map
if (!$src = array_search($path, $map)) {
if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s'", $path))) {
$map[$src] = $path;
$GLOBALS['alias_cache'][$src] = $path;
---------------------------------------
$GLOBALS['alias_cache']['save'] = TRUE;
---------------------------------------
}
else {
// We can't record anything into $map because we do not have a valid
// index and there is no need because we have not learned anything
// about any Drupal path. Thus cache to $no_src.
$no_src[$path] = TRUE;
}
}
return $src;
}
}
return FALSE;
}
?>
Вложение | Размер |
---|---|
url_alias_cache.zip | 3.86 КБ |
Комментарии
По тестам на локальной машине.
Без кеша: 98 запроса к базе (идет установка кеша алиасов)
С кешем : 51 запроса к базе
Имеем минус 47 запросов к базе. Это на главной странице форума.
Это для друпал 5?
просто на 6 версии ваш модуль не включается
Да, точно. Я делал для пятерки.
Для Drupal 6 видимо нужно "обработать напильником"
Нечно подобное предлагается здесь: http://drupal.org/node/100301 для шестерки, и даже куча патчей приложено. Разбираться в это времени не было. Было легче написать самому.
Просто может тогда убрать из тегов друпал 6?
а то кто-нить попробует
Спасибо, убрал.
Некоторые, кстати, предлагают ввести новое поле в таблицу node.
Предлагают туда записывать либо прямо алиасы - в таком случае мы теряем возможность создавать несколько алиасов на одну страницу, либо ключи на таблицу алиасов, чтоб все обрабатывать одним джоинтом (по моему минусы те же).
По поводу ключей не понял, а вот поле добавить это разумно.
Вообще не в тему выскажусь но я заметил два типа ускорения работы любого сайта.
Как я выяснил для себя визуальный работает более стабильно чем фактический сделанный путём ковыряния ядра.
Почему?
1) Ковыряя ядро я каждый раз создавал себе проблему при обновлении до новой версии так как был вынужден сравнивать версии.
2) Пару раз я так хорошо "отоптимизировал" что убил нафиг системную таблицу и мучался потом часов пять восстанавливая.
3) В итоге после файлового кеширования я получал некоторый лаг до момента начала загрузки которого небыло до файл-кэширования стало шустрее но лаг всё портил вроде быстро грузится но в начале торможение.
Что я называю визуальным ускорением? Оптимизацию структуры сайта таким образом чтобы последовательность была следующей:
Сначала контент потом стили и в конце скрипты.
Для меня актуально кол-во картинок на сайте так как каждая из них делала небольшой лаг в виду задержки обращения к серверу из за расстояния.
Чем больше элементов пусть даже маловесящих тем больше время, но задержка всегда примерно равна и она обычно больше чем грузится мини гифчик.
И ещё у меня очевидно большой лаг из за переадресации на https
Но это я решил предложив посетителям два способа входа один защищённый другой нет.
Просто разные домены сделал.
Для себя я выбрал приоритетом визуальное ускорение, а именно страница начинает загружается сразу и шустро и уже можно читать и смотреть хотя полностью она загрузится не сразу и большие скрипты конечно будут подгружены в конце но это не помешает читать и смотреть
Кеширование на файлах я так же отношу к тупиковому пути.
Т.к. Друпал, все таки должен загрузиться, хотя бы на "ядерном" уровне.
Ну там, счетчик просмотров, сообщения системы, динамические блоки и пр.
Вообще вопреки сложившемуся мнению которое я заметил.
Друпал 6 визуально шустрее пашет чем 5 (даже если его ускорить).
Конечно исходя из тестов так не скажешь но визуально точно шустрее
но для этого надо подшаманить немого как я описал выше.
поле в таблицы ноды совсем не решение проблемы, ибо множество алиасов существует еще и помимо нодов
Вячеслав, посмотри сюда. http://drupal.org/node/308685
Мне думается, это можно реализовать для любой версии Drupal.
Честно говоря не очень представляю каким образом осуществить выборку сразу нескольких (но не всех) алиасов из базы данных.
Каким образом вы будете оформлять параметр WHERE ???
К тому же, придется затрагивать систему темизации. Я бы ее трогать не стал.
Как то очень сложно у Вас получается и еще не факт что вообще получится.
Всё просто.
Предположим, у нас Drupal 6, с двумя параметрами на запрос.
function get_path_hash($path, $lang)
{
return '%%%macro'. md5($path. ':'. $lang). '%%%';
}
lookup_path($path, $lang)
{
// делаем так, чтобы лукапы к одному пути попадали в одну ячейку хэша, чтобы устранить дублирование. Хотя это и не обязательно.
$hash = get_path_hash($path, $lang);
$wanted_subs[$hash] = array('path' => $path, lang => $lang);
return $hash;
}
after_render($html)
{
$sql = 'SELECT ...AS result_path, ...AS source_path, ...AS lang FROM .. WHERE (0=1)';
for ($wanted_subs as $hash => &$v)
{
// формируем список параметров для ...OR ((lang = $lang) AND (path = $path))
$sql .= ...;
$v = $v['path']; // теперь тут будущая подстановка по умолчанию.
}
$rows = drupal_db_query($sql);
for ($rows as $row)
{
$hash = get_path_hash($row['source_path'], $row['lang']);
$wanted_subs[$hash] = $row['result_path']
}
$what = array_keys($wanted_subs);
$replace_with = array_values($wanted_subs);
return str_replace($what, $replace_with, $html);
}
Как вы можете заметить - всего один запрос к БД.
И одна замена строк, даже без регулярок, причём одним вызовом.
Количество циклов минимально.
Запостил на Хабр.
http://habrahabr.ru/blogs/drupal/39897/
Замечен баг в первой версии этого патча.
Наблюдения показали, что некоторые ссылки принимают вид http://site/1 или http://site/save
Мне так мыслится, что не уничтожается переменная count - счетчик обновлений кеша и она может быть подставлена вместо какой либо ссылки.
Новый патч скоро выложу.
Интересный материал, спасибо.
Сначала отнёсся немного скептически, но когда увидел что количество запросов к БД сокращается почти в два раза, был удивлён.
Пока нет времени самому дорабатывать способ, буду ждать стабильный релиз.
Увы, хак - это большой минус. Если бы как-то можно было обойтись без этого...
А из-за чего могло появиться уйма одинаковых ссылок в меню ?
Т.е. ранее было например
Главная страница|О нас|Конфиденциальность|Пресс-центр|Форум|Обратная связь|Поиск
А теперь
Главная страница|Главная страница|Главная страница|Главная страница|Главная страница|Главная страница|Главная страница|О нас|Конфиденциальность|Пресс-центр|Форум|Обратная связь|Поиск
и не только в одном меню так, в нескольких местах выплыло это.
А не может ли это быть связано с тем, что на том сайте переопредела стартовая (frontpage) страница на отдельную страницу (frontpage_2008) ?
UPD: P.S.
А может ли этот вариант конфликтовать с модулем GlobalRedirect. Он все ссылки вида node/232 заменяет на алиасы.
UPD2: после отключения GlobalRedirect та же ситуация, но только для анонимомв. зарегистрированные видят ссылки как надо, а вот гостям выдается "туча" одинаковых ссылок на сайт/1
GlobalRedirect полезный модуль и нужно бы учесть его в работе модуля кеширования.
Он действительно позволяет одному системному пути иметь несколько синонимов и редиректит на основной.
Нет, модуль GlobalRedirect здесь не причем.
У меня таже ситуация. Этот баг я уже описал.
Причем по предварительным наблюдениям корябятся пункты меню, имеющие абсолютное значение
(включая http:// )
Поведение совершенно не объяснимое ни чем. Будем разбираться.
Всем, кто воспользовался моим патчем я рекомендую откатиться обратно к системному path.inc
Новую версию модуля я предполагаю сделать более похожей на "модуль". Все функции будут прошиты в нем.
В наиболее оптимальном варианте файл path.inc нужно будет хакнуть в одном месте.
Написать в начале функции drupal_lookup_path()
$map = get_alias_cache($map);
Эх, как бы всё можно сделать шустренько, если бы drupal был на java) Там и покэшировать можно было прямо в памяти, и соединение к БД постоянно держать, и статистику всякую писать в память, а сохранять отдельным потоком в БД параллельно обработке запросов, и java не критична на большие объемы кода, которые написаны, но не используются (но всё равно парсятся каждый раз в пхп).
Почти все проблемы решает кеш php - берите серваки под дрю!
Почти все проблемы решает кеш php - берите серваки под дрю!
Да стоит eaccelerator... но на большом сайте с тысячами нод и обросшим большим кол-вом модулей всё равно шевелится не шустро. У нас генерация главной страницы 350-400 мс, новостная лента (views+cck) 900-1000 мс, статическая страница 300-350 мс, страница с большим кол-вом комментариев 1000 мс. При этом средняя загрузка процессоров (всего 4) 20-25%.
А на маленьком сайте со статическими страницами на том же серваке: статическая страница 130 мс.
Сейчас подробно изучил Drupal path/theme, в общем, я уже закодировал своё решение.
Выложу после тестирования.
В продложенном решении в статье, кстати, если недоработки. В первую очередь не отрабатывается корректно $action = 'wipe'.
А моё решение просто неработоспособно в текущей архитектуре Drupal.
Как минимум увидел проблему с модулем Global Redirect, надо вводить новые action в lookup_path.
А тем временем на drupal.org...
Вариация с кэшем алиасов в БД, но с insert delayed
Опять-таки с теми же pros'n'cons
Ну, это для семерки.
Чуть выше я выкладывал ссылку на патчи для шестерки.
Я же, как ископаемый экспонат, занимаюсь пятеркой.
спасибо, полезно
Вот, подготовил новую версию модуля кеширования алиасов путей.
В этот раз без глюков, я все протестировал.
http://drupal.ru/node/19837
Глюки всегда есть
И все так же без цифр статистики?
Глюков нет, попробуйте найти...
При ускорении работы сайта на 5 версии Drupal я для себя открыл чудесную связку модулей Advanced cache http://drupal.org/project/advcache и Cache Router http://drupal.org/project/cacherouter - они как раз занимаются кешированием всего подряд, в том числе и алиасы путей. В качестве хранилища можно выбрать базу данных (эту же или другую), файл, APC cache, memcache, xcache и можно подключить ещё что угодно. Так что мне кажется правильнее будет допиливать этот модуль чем пытаться кешировать вручную какие-то элементы... Да и с обновлениями будет попроще....
Продолжение темы здесь: http://drupal.ru/node/19895