Кеширование часть 2: кешируем имена пользователей

Аватар пользователя seaji seaji 30 сентября 2008 в 15:54

Привет всем!
Как известно системная функция node_load() статически кеширует внутри себя загруженные ноды. Скажем, если вы просматриваете ноду с id = 1000, то система выполняет функцию node_load(array(nid=>1000)). Происходит запрос к базе и результат кешируется внутри функции node_load(). Таким образом, при повторном вызове node_load() не происходит еще одного запроса к базе, а данные берутся из кеша.
Я так предполагал, что такая же логика должна действовать и в функции user_load(), но я ошибся. Как ни странно, при вызове user_load() запрос к базе происходит каждый раз, даже если данный юзер уже был загружен.
Как это лечится?

Достаточно просто.
Нужно создать в файле template.php вашей темы следующую функцию: имя_вашей_темы_username($object)
В нее копируется содержимое функции theme_username() с некоторыми изменениями (изменения прокомментированы):
<?php
function имя_вашей_темы_username($object) {
static $users = array(); /* Статически кешируем загруженный пользователей */
if ($object->uid && $object->name) {
if ($users[$object->uid]->uid) { /* Смотрим наличие юзера в кеше */
$user_object = $users[$object->uid]; /* пользователь уже загружен, запрос к базе не нужен */
}
else {
$user_object = user_load(array('uid' => $object->uid)); /* пользователя нет - загружаем */
$users[$object->uid] = $user_object;
}
// Shorten the name when it is too long or it will break many tables.
if (drupal_strlen($object->name) > 20) {
$name = drupal_substr($object->name, 0, 15) .'...';
}
else {
$name = $object->name;
}
if (user_access('access user profiles')) {
$output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.')));
}
else {
$output = check_plain($name);
}
}
elseif ($object->name) {
// Sometimes modules display content composed by people who are
// not registered members of the site (e.g. mailing list or news
// aggregator modules). This clause enables modules to display
// the true author of the content.
if ($object->homepage) {
$output = l($object->name, $object->homepage);
}
else {
$output = check_plain($object->name);
}
$output .= ' ('. t('not verified') .')';
}
else {
$output = variable_get('anonymous', t('Anonymous'));
}
return $output;
}
?>

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

Комментарии

Аватар пользователя kosilko kosilko 30 сентября 2008 в 16:04

то же самое замутил на днях, правда в static $users впихнул $output, чтобы не проходить каждый раз всю портянку функции от нуля и до хвоста.

Аватар пользователя kosilko kosilko 30 сентября 2008 в 23:16

<?php
function phptemplate_username($object) {
static $users_loaded;
if (isset($users_loaded[$object->uid]))
return $users_loaded[$object->uid];//если уже есть - то тут же выходим с готовым результатом из массива

if(!$object->name) {
$db_result=db_fetch_object(db_query('SELECT * FROM {users} WHERE uid=%d AND uid<>0 LIMIT 1',$object->uid));
$object->name=$db_result->name;
}
$class=array('userlink');//добавляем CSS класс типа "юзерлинк"
if ($object->uid && $object->name) {
// Shorten the name when it is too long or it will break many tables.
if (drupal_strlen($object->name) > 20) {
$name = drupal_substr($object->name, 0, 15) .'...';
}
else {
$name = $object->name;
}

if (user_access('access user profiles')) {
$output = l($name, 'user/'. $object->uid, array('attributes' => array('title' => t('View user profile.'))));
}
else {
$output = check_plain($name);
}
}
else {
$class[]='guest';//если незареген - добавляем класс "гость"
$object->uid=0;
if ($object->name) {
// Sometimes modules display content composed by people who are
// not registered members of the site (e.g. mailing list or news
// aggregator modules). This clause enables modules to display
// the true author of the content.
if (!empty($object->homepage)) {
$output = l($object->name, $object->homepage, array('attributes' => array('rel' => 'nofollow')));
}
else {
$output = check_plain($object->name);
}

// $output .= ' ('. t('not verified') .')';
//тут я закомментировал, чтобы убрать мало кому понятное "не проверено".

}
else {

$output = variable_get('anonymous', t('Anonymous'));
}
}
$users_loaded[$object->uid]=''.$output.'';
//оборачиваем ссылку юзера в классифицированный span,
//дабы потом через CSS можно было разукрашивать юзеров по статусу.
// Темизированный результат пихаем в статик $users_loaded

return $users_loaded[$object->uid];
//вернули результат
}
?>

Аватар пользователя andypost@drupal.org andypost@drupal.org 2 октября 2008 в 16:37

Похожую тему поднял http://drupal.org/node/290777
Кешировать вывод несколько оптимальнее, особенно когда вместо ников нужно выводить имена.
Но в статье затронуто кеширование user_load^api

2 kosilko запрос к базе данных в таком виде неправилен! нужно как минимум проверять статус пользователя status<>0 и дать остальным модулям обработать hook_user^api

Аватар пользователя kosilko kosilko 2 октября 2008 в 23:09

"<a href="mailto:andypost@drupal.org">andypost@drupal.org</a>" wrote:
2 kosilko запрос к базе данных в таком виде неправилен!

забыл убрать, этот запрос я писал под свои нужды

Аватар пользователя seaji seaji 3 октября 2008 в 1:50

Следует отдать должное тому факту, что при загрузке ноды и при загрузке комментариев функция user_load() не вызывается. Там у них свои запросы со своими джоинтами.
Функция user_load() вызывается только тогда, когда нужно ПОЛНОСТЬЮ загрузить пользовательский профиль.
А у меня необходимость вызова этой функции возникла в связи с тем, что я использую в качестве имени пользователя поле профиля user_name (я его сам создал), а если это поле не заполнено, то используется логин (как обычно). Поэтому мне нужно было вызывать user_load() для каждого имени пользователя. И я решил кешировать результат.
Честно говоря, если вы не используете поля профиля пользователей в разных местах сайта, то вам использовать это кеширование нет особой необходимости. Оно даже создаст лишнюю нагрузку.

Аватар пользователя neochief neochief 10 ноября 2015 в 11:45

Для пятого друпала использую данный патч. Написан он был мною для оптимизации одной соц-сети. Тогда производительность выросла в 2 раза. Патч попросту добавляет статик-кеширование юзеров.

Аватар пользователя Murz Murz 4 ноября 2008 в 21:50

Для 5 Drupal также рекомендую посмотреть модули:
http://drupal.org/project/advcache
http://drupal.org/project/blockcache
http://drupal.org/project/cacherouter

Модуль advcache может кешировать всё что кешируется (включать можно частично):
block, comment, node, taxonomy, search, path, page...

Модуль cacherouter позволяет хранить кеши разных типов в разных хранилищах (бд, файлы, memcache, apc cache и т.д.)

Модуль blockcache дополнительно кеширует блоки без привязки к страницам (т.е. если везде блок одинаковый - то он закешируется 1 раз, а не для каждой страницы по 1 экземпляру.

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

Для 6 версии к сожалению этих модулей пока нет, есть какие-то похожие, но ещё не их тестировал, так что порекомендовать пока ничего не могу.