forum.coppermine-gallery.net

Support => Русский (Russian) => Language Specific Support => cpg1.4.x FAQ (Russian) => Topic started by: GeXu3 on April 30, 2008, 03:07:44 pm

Title: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on April 30, 2008, 03:07:44 pm
Суть проблемы:
запросы на "SELECT count(*)" создают просто неимоверную нагрузку на mysql когда количество фотографий в галерее превышает пару десятков тысяч ;) Время выполнения скрипта ооочень некрасиво возростает (видать не рассчитывал никто на такие нагрузки). Когдато раньше я писал про такую вот проблему - там решение было через кеш кроном. Сильно вдумываццо не стал и решил написать свой велосипед. Сейчас тестирую - вроде работает.

Решение в теории:
Чтобы не ганять по 100 раз count по огромным таблицам нужно делать простое кеширование гдето в базе результатов запроса, чтоб потом обращаццо к ним напрямую через select where не пересчитывая каждый раз таблицы. Желательно для этого не использовать никаких кронов и прочей ерунды.

Решение на практике:
Решение пишу от конкретной таблицы неособо заморачиваясь префиксацией. Думаю и так будет понятно.

Структура таблицы БД:
Code: [Select]
CREATE TABLE `sunph_gx_countcache` (
  `query` text NOT NULL,
  `date` decimal(10,0) NOT NULL,
  `value` bigint(20) NOT NULL,
  PRIMARY KEY  (`query`(255))
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Что здесь к чему:
 - query - здесь мы будем хранить текст запроса. Именно по тексту запроса будет происходить проверка есть у нас кеш или нет. Зачем? Для универсальности решения.
 - date - временная метка в формате date('U'). По ней будем определять устарел ли кеш и надо ли перекешировать.
 - value - собственно само кешируемое значение.

Класс-обработчик:
Code: [Select]
<?php
class api_querycache{

private $time_limit;
private $time_now;
private $time_die;

private $result;

function __construct($query$timelimit 30){
global $CONFIG;

$this->result 0;

$this->time_limit $timelimit*60;
$this->time_now date('U');
$this->time_die $this->time_now+$this->time_limit;

$query_add mysql_real_escape_string($query$CONFIG['LINK_ID']);


$sql_query "SELECT
sunph_gx_countcache.value,
sunph_gx_countcache.`date`
FROM
sunph_gx_countcache
WHERE
sunph_gx_countcache.query =  '
$query_add' AND
sunph_gx_countcache.`date` >  '
$this->time_now'";

$base cpg_db_query($sql_query);

$size mysql_num_rows($base);

if ($size == 1){
$tmp mysql_fetch_object($base);
mysql_free_result($base);

$this->result $tmp->value;
}else {
mysql_free_result($base);

$base cpg_db_query($query);
$size mysql_num_rows($base);
if ($size == 1){
$tmp mysql_fetch_row($base);
mysql_free_result($base);

$this->result $tmp[0];

$sql_query "delete from sunph_gx_countcache
where
sunph_gx_countcache.query = '
$query_add'";
$base cpg_db_query($sql_query);
mysql_free_result($base);

$sql_query "insert  into sunph_gx_countcache
   (`query`, `date`, `value`)
values ('
$query_add', '$this->time_die', '$this->result')";
$base cpg_db_query($sql_query);
mysql_free_result($base);
}
}

return TRUE;
}

function result(){
return $this->result;
}
}
?>
Его надо приинклудить хоть даже вверху init.inc.php (у меня для своих классов специальная папка и файл-инициализатор который прикручен вверху init.inc.php)

Что здесь к чему:
 - $query - запрос по которому нужно провести кеширование или взять данные из кеша. запрос на каурт, сум и т.д. тоесть возвращающий 1x1 таблицу.
 - $timelimit - лимит по времени между кешами. в минутах.

Как это работает:
Сначала в таблице каша ищеццо был ли результат запроса ($query) прокеширован ранее и не истёк ли срок его жизни (`date`>date('U')).
Если вернулась строка размерностью 1 (кеш есть) то запоминаем значение и радуемся.
Если вернулся 0 (или не единица - мало ли какой там глюк произошел) - необходимо перекешировать. Для этого выполняеццо запрос ($query) и если размерность результата 1 происходит кешированье. Сначала удаляюццо все записи где запрос равен кешируемому запросу, после чего в базу заносиццо новый кеш запроса.

Примерение на практике:
Прокешируем, например, один из запросов в index.php.

Старый вариант:
Code: [Select]
$result = cpg_db_query("SELECT count(*) FROM {$CONFIG['TABLE_PICTURES']} as p, {$CONFIG['TABLE_ALBUMS']} as a WHERE p.aid = a.aid AND approved='YES' AND category = {$subcat['cid']}" . $album_filter);
$nbEnr = mysql_fetch_array($result);
mysql_free_result($result);
$pic_count = $nbEnr[0];

Новый вариант:
Code: [Select]
$sql_query = "SELECT count(*) FROM {$CONFIG['TABLE_PICTURES']} as p, {$CONFIG['TABLE_ALBUMS']} as a WHERE p.aid = a.aid AND approved='YES' AND category = {$subcat['cid']}" . $album_filter;
$gx_tmp = new api_querycache($sql_query);
$pic_count = $gx_tmp->result();


В общем вот так вот :)

Жду комментариев, может критики, а может вообще тумаков надаёте и выгоните.

З.Ы.: на безопасность подобный марахай увы не тестил.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on April 30, 2008, 04:04:06 pm
И для примера результат который лежит в таблице кеша (сейв дампа):
Code: [Select]
INSERT INTO `sunph_gx_countcache` VALUES ('SELECT count(*) FROM sunph_pictures as p, sunph_albums as a WHERE p.aid = a.aid AND approved=\'YES\' AND category >= 10000', '1209564972', '58139');
INSERT INTO `sunph_gx_countcache` VALUES ('SELECT count(*) FROM sunph_pictures as p, sunph_albums as a WHERE p.aid = a.aid AND approved=\'YES\' AND category = 2', '1209564972', '301');
INSERT INTO `sunph_gx_countcache` VALUES ('SELECT count(*) FROM sunph_pictures as p, sunph_albums as a WHERE p.aid = a.aid AND approved=\'YES\' AND category = 12', '1209564972', '25');
INSERT INTO `sunph_gx_countcache` VALUES ('SELECT count(*) FROM sunph_pictures as p, sunph_albums as a WHERE p.aid = a.aid AND approved=\'YES\' AND category = 7', '1209564972', '342');
INSERT INTO `sunph_gx_countcache` VALUES ('SELECT count(*) FROM sunph_pictures as p, sunph_albums as a WHERE p.aid = a.aid AND approved=\'YES\' AND category = 6', '1209564972', '143');
INSERT INTO `sunph_gx_countcache` VALUES ('SELECT count(*) FROM sunph_pictures as p, sunph_albums as a WHERE p.aid = a.aid AND approved=\'YES\' AND category = 3', '1209564972', '340');

По сути прокешированы 2 запроса страницы индекса.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: Makc666 on April 30, 2008, 06:07:12 pm

GeXu3, спасибо большое за подробное описание и Вы не против, если я перенесу тему в FAQ?
Title: Re: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on April 30, 2008, 07:32:09 pm
Естественно :)

Только момент - надо быть внимательным где эту штуку можно применить, а где нет (чёб глюков небыло). Если не тот запрос прокешировать то могут вылезти ошибки ровно пока не пройдёт перепись прокешированого запроса.

Щас подумываю еще над двумя функциями: принудительного перекеширования и полной очистки кеша. Но новые версии выдам когда выловлю все глюки текущей.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on May 01, 2008, 10:12:12 pm
Немного наворотил идею. В процессе возникла необходимость принудительной очистки кеша и перекешированья. Решил добавить в таблицу еще один столбец-метку: page. Тоесть страницу на которой был выполнен запрос. Зачем? Чтобы можно было тереть кеш по нескольким критериям, и если мне надо вытереть кеш только для страницы альбомов то я сделаю это только для неё. В общем обновлённая версия (чувствую что не последняя):

База:
Code: [Select]
CREATE TABLE `sunph_gx_countcache` (
  `query` text NOT NULL,
  `date` decimal(10,0) NOT NULL,
  `value` bigint(20) NOT NULL,
  `page` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`query`(300),`page`(10))
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Класс
Code: [Select]
<?php
class api_querycache{

private $time_limit;
private $time_now;
private $time_die;

private $query_orig;
private $query_escaped;

private $page;

private $result;

function __construct($query=''$timelimit 30){
global $CONFIG;

$this->result 0;

$this->time_limit $timelimit*60;
$this->time_now date('U');
$this->time_die $this->time_now+$this->time_limit;

$this->query_orig $query;
$this->query_escaped mysql_real_escape_string($this->query_orig$CONFIG['LINK_ID']);

$this->page mysql_real_escape_string($_SERVER['PHP_SELF'], $CONFIG['LINK_ID']);

if ($query!=''){
return TRUE;
}else {
return FALSE;
}
}

function exec(){
if ($this->query_orig!=''){
$base cpg_db_query($this->query_orig);
$size mysql_num_rows($base);
if ($size == 1){
$tmp mysql_fetch_row($base);
mysql_free_result($base);

$this->result $tmp[0];
}
return TRUE;
}else {
return FALSE;
}
}

function cache(){
if ($this->query_orig!=''){
$sql_query "SELECT sunph_gx_countcache.value, sunph_gx_countcache.`date` FROM sunph_gx_countcache WHERE sunph_gx_countcache.query =  '$this->query_escaped' AND sunph_gx_countcache.`date` >  '$this->time_now' AND sunph_gx_countcache.`page` = '$this->page'";
$base cpg_db_query($sql_query);
$size mysql_num_rows($base);

if ($size == 1){
$tmp mysql_fetch_object($base);
mysql_free_result($base);
$this->result $tmp->value;
}else {
mysql_free_result($base);
$this->recache();
}
return TRUE;
}else {
return FALSE;
}
}

function recache(){
if ($this->exec()){
$this->insertcache();
}
}

function insertcache(){
if ($this->query_orig!=''){
$this->delete();

$sql_query "insert  into sunph_gx_countcache (`query`, `date`, `value`, `page`) values ('$this->query_escaped', '$this->time_die', '$this->result', '$this->page')";
$base cpg_db_query($sql_query);
mysql_free_result($base);
return TRUE;
}else {
return FALSE;
}
}

function delete(){
if ($this->query_orig!=''){
$sql_query "delete from sunph_gx_countcache where sunph_gx_countcache.query = '$this->query_escaped' and sunph_gx_countcache.`page` = '$this->page'";
$base cpg_db_query($sql_query);
mysql_free_result($base);
return TRUE;
}else {
return FALSE;
}

}

function delete_query($query ''){
global $CONFIG;

if ($query!=''){
$query $this->query_escaped;
}else {
$query mysql_real_escape_string($query$CONFIG['LINK_ID']);
}

$sql_query "delete from sunph_gx_countcache where sunph_gx_countcache.query = '$query'";
$base cpg_db_query($sql_query);
mysql_free_result($base);
}

function delete_page($page ''){
global $CONFIG;

if ($page != ''){
$page $this->page;
}else {
$page mysql_real_escape_string($page$CONFIG['LINK_ID']);
}

$sql_query "delete from sunph_gx_countcache where sunph_gx_countcache.`page` = '$page'";
$base cpg_db_query($sql_query);
mysql_free_result($base);
}

function delete_all(){
$sql_query "delete from sunph_gx_countcache";
$base cpg_db_query($sql_query);
mysql_free_result($base);
}

function result(){
return $this->result;
}
}
?>

Новые переменные:
query_orig - оригинальный запрос как он есть
query_escaped - заслешеный для добавления в базу
page - страница на которой происходило выполнение


Новые функции:
exec - простое выполнение запроса без кеширования
cache - кешированье
recache - принудительное перекеширование независимо от того истекло время жизни или нет
insertcache - добавить кеш текущего запроса в базу
delete - удалить кеш текущего запроса на текущей странице в базу (ключи запрос и страница)
delete_query - удалить кеш текущего запроса (ключ - запрос. вытирает кеш таких же запросов сделаных на всех страницах. По умолчанию $this->query_escaped но можно указать свою)
delete_page - удалить кеш всех запросов текущей старницы (ключ - имя стрницы. По умолчанию берёццо $this->page, но можно указать свою)
delete_all - вытирает вообще всю таблицу кеша
result - результат выполнения запроса (циферка из запроса или из кеша)

Вызов запроса на кеширование:
Слегка изменился потому как процедура кеширования вынесена из конструктора. Теперь её надо вызывать вручную.
Code: [Select]
$sql_query = "SELECT count(*) FROM {$CONFIG['TABLE_PICTURES']} as p, {$CONFIG['TABLE_ALBUMS']} as a WHERE p.aid = a.aid AND approved='YES' AND category = {$subcat['cid']}" . $album_filter;
$gx_tmp = new api_querycache($sql_query);
$gx_tmp->cache();
$pic_count = $gx_tmp->result();


В общем вот такие вот изменения. Мне вот чего интересно - как создатели копермайна решат подобную проблему.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on May 02, 2008, 03:21:29 am
Забыл добавить в скрипте одно место. В конструкторе где
Code: [Select]
$this->time_limit = $timelimit*60;надо поставить
Code: [Select]
$this->time_limit = $timelimit*60+rand(0,30);
rand по вкусу :)

Прибавляет случайное время смерти кеша. Если на странице выполняеццо одновременно 10-20 запросов то это позволит рассинхронизировать время их следующего перекеширования. Проще говоря при первой загрузке кешируюццо все запросы, а далее - группами по 1-2-3 в зависимости от рандома (например Вася прокеширует запросы 3, 5, 7, а через 5 секунд коля прокеширует запросы 1, 4 а лёша - 2 и 7). Серверу станет немного легче.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on May 31, 2008, 10:59:18 pm
Так, на нагрузках повылазили бока. Всё оттого что insert идёт через cpg_db_query... при высокой нагрузке оч большая вероятность того, что пока один чел заинсертит то перед ним еще трое инсерт кинут. В итоге вылетает ошибка базы данных... исправил пустив insert напрямую через $base = @mysql_query($sql_query, $CONFIG['LINK_ID']); ... кривовато, зато вроде пока держит при среднем 10 человек на сайте (сайт - http://sunphoto.org.ua ... в нём уже внедрена эта система. Кешируюццо почти все запросы на count и sum)...
Title: Re: Патч: попытка оптимизации count-запросов
Post by: Alex Revo on July 22, 2008, 09:07:01 pm
Спасибо GeXu3!

Решение неплохое и достойно дальнейшего развития.
У меня в галерее число фото дошло до 14 000 и тоже столкнулся с проблемой чрезмерной нагрузки на БД.
Патч проанализировал и установил, пока вижу что кешируются только самые тажелые запросы. Это уже хорошо. На счет безопасности думаю волноваться не о чем, все сделано грамотно.

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

Топик перенес в ФАК и прикрепил. Если не сложно отредактируй посты, оставив в первом сообщении текущую версию хака, чтобы посетители долго не искали что к чему.
+1
Title: Re: Патч: попытка оптимизации count-запросов
Post by: mche on August 12, 2008, 06:08:17 am
Я бы вместо `query` text NOT NULL, хранил бы в базе md5 от query
немного ьыстрое поиск и индексация.
А вобще для подобных кэшей лучше использовать не базы mysql,
а кеширование на APC, xcache которые хранят кэшируемые значения в памяти вэбсервера, соответсвенно скорочть выше чем при работе с базой.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: Alex Revo on August 12, 2008, 06:57:42 am
А вобще для подобных кэшей лучше использовать не базы mysql,
а кеширование на APC, xcache которые хранят кэшируемые значения в памяти вэбсервера, соответсвенно скорочть выше чем при работе с базой.

Расскажите подробнее.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: mche on August 12, 2008, 07:43:17 am
Подробнее о чём?
APC, xcache это расширения Php такиеже как GD, модуль по работе с графикой
В стандартную поствку не входят, устанавливаются отдельно, поэтому такой вариант не для всех.
Кроме кеширования пхп кода, могут сохрантть значения переменных в памяти между выполнениями скрипта,
в нашем случае можно хранить результаты count-запросов не используя при этом таблицы mysql
вот пример использования 
http://xcache.lighttpd.net/wiki/XcacheApi#Cacher
Title: Re: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on September 26, 2008, 11:40:14 pm
Давно не писал. Вижу народу понравилось. Думаю скоро буду писать универсальную систему для кеширования результата, чёб кешировать не только цифры, а и остальные тяжелые результаты (пока мысль - сейвить serialized массив). Щас не пишу - идея должна полностью вызреть. Расчитываю встроить кеш напрямую в класс работы с БД...

Quote
Я бы вместо `query` text NOT NULL, хранил бы в базе md5 от query
хммм... хорошая мысль - объёмы БД точно сократяццо, а знач и скорость обработки... но реализовать лучче флагом, тоесть когда флаг=1 то гнать md5, а когда нет - чистый запрос. Почему так? очень просто - когда ты дебажишь систему кеширования, тоесть не знаешь работает она как надо или гдето чегото глючит.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: LeaX on January 13, 2009, 12:14:04 pm
У меня в галерее 10700 файлов и пошли большие нагрузки на процессор и сервер.
Скажите, а вообще в галерее нет своего кэша что ли? Что нужно отдельно ставить?

И кто-нибудь может написать четкую инструкцию этого патча? А то тут вносились какие-то дополнения и сейчас трудно разобраться (мне вот лично) как лучше сделать.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: Alex Revo on January 13, 2009, 12:27:00 pm
Вопрос очень резонный, у меня с ходу наладить этот патч не получилось, переехал на другой сервер и забросил это дело.
Если у вас на сервере apache 2.0, то посмотрите рецепты отсюда (http://apachedev.ru/2007/09/19/5-prostyih-sposobov-uskoreniya-sayta/), должно помочь.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: LeaX on January 13, 2009, 03:23:29 pm
Alex Revo а у вы у кого хоститесь? Неужели для coppermine с картинками больше 10 тыс. нужно отдельный сервер? У меня назгрузка за 7 дней с одной только галереи 600, при лимите в 500.
Хотелось бы понять это нормально или сбой?
Title: Re: Патч: попытка оптимизации count-запросов
Post by: GeXu3 on January 20, 2009, 01:31:36 am
LeaX, отпишись в аську как получилось допатчить, че потом хостер сказал, чего с нагрузкой. Ато аська грохнулась, адрес сайта успешно забыл, а  интересно же.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: LeaX on January 20, 2009, 10:24:18 am
я не допатчила...решила пока на VPS переехать, пока переезжала вылезла проблема с кодировками...  :-[
В общем пока галерею закрыла... вечером сегодня постараюсь на эту тему подумать.
Адрес сайта в профиле - переход по ссылкам с сайта в галерею очень просто найти.  :)
Title: Re: Патч: попытка оптимизации count-запросов
Post by: Sandrina on February 10, 2010, 11:02:20 am
объясните пожалуйста, куда именно надо прописывать код... я поняла только про часть где надо вставить init.inc.php, а другой код, который для БД?
Title: Re: Патч: попытка оптимизации count-запросов
Post by: kastorskiy on July 11, 2011, 11:18:37 am
Я бы вместо `query` text NOT NULL, хранил бы в базе md5 от query
немного ьыстрое поиск и индексация.
А crc32 не подойдёт?

[/quote]А вобще для подобных кэшей лучше использовать не базы mysql,
а кеширование на APC, xcache которые хранят кэшируемые значения в памяти вэбсервера, соответсвенно скорочть выше чем при работе с базой.[/quote]
А я собираюсь заюзать memcache. Но для принудительной очистки кеша видимо вё-таки придётся так-же хранить и в базе данные для кеширования (но обращения к этой таблице буддет минимизированы).
Title: Re: Патч: попытка оптимизации count-запросов
Post by: kastorskiy on July 11, 2011, 12:52:57 pm
Наткнулся на memcache.php – веб-интерфейс для сервера memcached (http://tigor.com.ua/blog/2008/07/31/memcachephp-stats-like-apcphp/). Он умееет получать список ключей мемкеша. Надо будет поковырять его код, и тогда по идее можно будет избавится от дополнительных запросов к БД.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: Alex Revo on July 11, 2011, 02:03:39 pm
Держите в курсе.
Title: Re: Патч: попытка оптимизации count-запросов
Post by: kastorskiy on July 14, 2011, 06:06:04 pm
Держите в курсе.

Вот что у меня получилось на данный момент (писано на скорую руку, код не причёсан и наличествует харкод там, где следовало бы вынести в настройки, т.к. спешил успеть к ожидаемому наплыву посетителей):

Code: [Select]
<?php
class api_querycache{

private $time_limit;
private $time_now;
private $time_die;

private $result;

function __construct($query$timelimit 60){
global $CONFIG;

$this->result 0;

$this->time_limit $timelimit*60+rand(0,30);
$this->time_now date('U');
$this->time_die $this->time_now+$this->time_limit;

$query_add mysql_real_escape_string($query);

$memcache = new Memcache;
$memcache->connect('localhost'11211);
$memcache->get('extreme_sql_'.md5($query));

if ( ($result $memcache->get('extreme_sql_'.md5($query))) !== false ){
$this->result $result;
}else {
$base mysql_query($query);
$size mysql_num_rows($base);
if ($size == 1){
$tmp mysql_fetch_row($base);
mysql_free_result($base);

$this->result $tmp[0];

$memcache->set('extreme_sql_'.md5($query), $this->resultfalse$this->time_limit);
}
}

return TRUE;
}

function result(){
return $this->result;
}
}

class 
api_delcache {

static private 
$MEMCACHE_SERVERS = array('localhost:11211'); // add more as an array

///////////MEMCACHE FUNCTIONS /////////////////////////////////////////////////////////////////////

private function sendMemcacheCommands($command){
$result = array();

foreach(self::$MEMCACHE_SERVERS as $server){
$strs explode(':',$server);
$host $strs[0];
$port $strs[1];
$result[$server] = self::sendMemcacheCommand($host,$port,$command);
}
return $result;
}
private function 
sendMemcacheCommand($server,$port,$command){

$s = @fsockopen($server,$port);
if (!$s){
return false;
}

fwrite($s$command."\r\n");

$buf='';
while ((!feof($s))) {
$buf .= fgets($s256);
if (strpos($buf,"END\r\n")!==false){ // stat says end
    break;
}
if (strpos($buf,"DELETED\r\n")!==false || strpos($buf,"NOT_FOUND\r\n")!==false){ // delete says these
    break;
}
if (strpos($buf,"OK\r\n")!==false){ // flush_all says ok
    break;
}
}
    
fclose($s);
    return 
self::parseMemcacheResults($buf);
}
private function 
parseMemcacheResults($str){
    
$res = array();
$lines explode("\r\n",$str);
$cnt count($lines);
for($i=0$i$cnt$i++){
    $line $lines[$i];
$l explode(' ',$line,3);
if (count($l)==3){
$res[$l[0]][$l[1]]=$l[2];
if ($l[0]=='VALUE'){ // next line is the value
    $res[$l[0]][$l[1]] = array();
    list ($flag,$size)=explode(' ',$l[2]);
    $res[$l[0]][$l[1]]['stat']=array('flag'=>$flag,'size'=>$size);
    $res[$l[0]][$l[1]]['value']=$lines[++$i];
}
}elseif($line=='DELETED' || $line=='NOT_FOUND' || $line=='OK'){
    return $line;
}
}
return $res;

}

private function 
dumpCacheSlab($server,$slabId,$limit){
    list(
$host,$port) = explode(':',$server);
    
$resp self::sendMemcacheCommand($host,$port,'stats cachedump '.$slabId.' '.$limit);

   return 
$resp;

}

private function 
getCacheItems(){
 
$items self::sendMemcacheCommands('stats items');
 
$serverItems = array();
 
$totalItems = array();
 foreach (
$items as $server=>$itemlist){
    
$serverItems[$server] = array();
    
$totalItems[$server]=0;
    if (!isset(
$itemlist['STAT'])){
        continue;
    }

    
$iteminfo $itemlist['STAT'];

    foreach(
$iteminfo as $keyinfo=>$value){
        if (
preg_match('/items\:(\d+?)\:(.+?)$/',$keyinfo,$matches)){
            
$serverItems[$server][$matches[1]][$matches[2]] = $value;
            if (
$matches[2]=='number'){
                
$totalItems[$server] +=$value;
            }
        }
    }
 }
 return array(
'items'=>$serverItems,'counts'=>$totalItems);
}

//////////////////////////////////////////////////////

static function delCache() {
$cacheItemsself::getCacheItems();
$items $cacheItems['items'];
$totals $cacheItems['counts'];

$memcache = new Memcache;
$memcache->connect('localhost'11211);
$memcache->get('extreme_sql_'.md5($query));

foreach($items as $server => $entries) {
        foreach(
$entries as $slabId => $slab) {
    $items self::dumpCacheSlab($server,$slabId,$slab['number']);
            foreach(
$items['ITEM'] as $itemKey=>$itemInfo){
            
if (substr($itemKey012) == 'extreme_sql_') {
            $memcache->delete($itemKey);
                }
            }
}
}
}

}
?>

Первый класс аналогичен сабжевому, только кеш хранится не в базе, а в memcashe. Второй класс служит для очистки кеша, используется так:
Code: [Select]
api_delcache::delCache();
Поскольку в моём случае фотки добавляются редко, я закешировал все агрегатные запросы с таблицами $CONFIG['TABLE_PICTURES'] и $CONFIG['TABLE_ALBUMS']. Т.е. остальные таблицы, например $CONFIG['TABLE_COMMENTS'], пока не кеширую, т.к. обновляются они значительно чаще (но возможно придётся, т.к. $CONFIG['TABLE_COMMENTS'] тоже не маленькая, больше 10000 коментов). А при добавлении фоток очищаю кеш.

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

Пока продолжаю анализировать лог медленных запросов (http://yandex.ua/yandsearch?clid=9582&text=%D0%BB%D0%BE%D0%B3+%D0%BC%D0%B5%D0%B4%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D1%85+%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2&rdrnd=694215&lr=144), чего и другим советую.