SQL кеширование в PHP

sheiko

Новичок
SQL кеширование в PHP

В проектах с высокой посещаемостью нередки проблемы связанные с производительностью сайта. Любой более-менее универсальный репозитарий контента подразумевает помимо таблиц структур данных раздельные таблицы для хранения данных различных типов. Соответственно уже для простейшей выборки списка документов потребуется связывать ряд таблиц, использовать группировку. В результате мы получаем на одну сполна начиненную сервисами страницу целый ряд «тяжелых» запросов. Число процессов на сервере базы данных растет пропорционально росту числа пользователей. Нагруженная база данных будет попросту складывать поступающие запросы в очередь, и выполнять их лишь по мере освобождения ресурсов. Таким образом, время генерации страницы может достигнуть шокирующих показателей – «Страница загружается. Откиньтесь на спинку кресла и отдохните потому, как это займет несколько минут».

Что же делать? В классическом подходе предлагается модифицировать архитектуру базы данных и сократить число SQL-запросов. Но это не всегда приемлемо. Можно попробовать сократить число переборов, выполняемых сервером базы данных для наших запросов. Для этого стоит воспользоваться командой MySQL EXPLAIN (http://dev.mysql.com/doc/refman/4.0/ru/explain.html), чтобы выявить неэффективные индексы таблиц. Не помогает? Тогда в ход идет тяжелая артиллерия: разнесение таблиц по разным базам, master/slave репликация, кластеризация (http://phpclub.ru/detail/download/2005/jun-jul/phpi13_2005.pdf). Однако такой подход может показаться клиенту не очень рентабельным. Давайте подумаем, что же можно сделать еще. А что если мы будем результаты наиболее тяжелых выборок читать не из базы данных, а из файлов? При тонкой настройке такое решение может оказаться вполне эффективным. Данный прием принято называть SQL-кешированием. В частности он достигается посредством функции CacheExecute популярного пакета API AdoDB (http://adodb.sf.net). С другой стороны чуть ли не в каждой статье посвященной AdoDB я встречаю нарекания к данной реализации SQL кеширования. Этот метод не подойдет и в том случае, если вы используете иной API абстрактных слоев баз данных. Например, API базы данных от PHPBB(http://phpbb.com).

Вся прелесть программирования в том, что если вас не устраивает что-либо в известных вам реализованных библиотеках, вы всегда можете написать свою собственную. Итак, давайте рассмотри пример библиотеки классов API SQL-кеширования на примере http://www.phpclasses.org/browse/package/2646.html. API должен позволить нам направлять в кеш-файл массив с результатами «тяжелых» запросов. Допустим «тяжелый» запрос выполняет метод getFetchListing, и его параметры содержание запроса и флаг, сообщающий выполнять SQL-кеширование или нет. При поступление SQL запроса метод проверяет наличие для него файла кеша. Имя файла с кешем результатов соответствует MD5 ключу содержания SQL запроса. Если файл найден, его содержание преобразуется в PHP массив и возвращается методом. Если файла нет, то выполняется запрос, а его результаты записываются в файл кеша. Но как же проконтролировать обновления в таблицах, задействованных в запросе? Для этого, перед тем как создать новый кеш-файл, мы анализируем SQL-запрос и формируем индексную таблицу в базе данных. Таблица содержит пары: MD5 ключ запроса, задействованная таблица. Соответственно если все SQL-запросы по модификации таблиц базы данных будут направляться нами в метод modify(), мы сможем определить модифицируемые таблицы и очистить кеш для них. Кеш-файлы будут автоматически вновь созданы, при следующем «тяжелом» запросы на выборку.

Таким образом, мы выносим результаты наиболее «тяжелых» запросов в файлы кеша, чем сможем существенно разгрузить многострадальную базу данных. Главное не перестараться с определением запросов в кеш, иначе придется ломать голову над тем как разгружать процессор сервера.

Если я кого-нибудь вдохновил на написание собственного API SQL-кеширования, могу посоветовать размещение блоков кеша SQL-запросов в разделяемой памяти сервера (http://www.php.net/shmop_open/, http://phpclub.ru/detail/article/2002-05-26), что существенно повысит производительность системы.
 

Вложения

sheiko

Новичок
Да в 5м MySQL есть такое. Но в куда более распространенном ныне 4м приходится "изобретать велосипед", балансировать запросы между базой, файлами или shared memory
 

Trinux

Guest
Сама идея такого способа кеширования просто как 2 рубля. А вы тут накрутили... апи... классы... ооп. Интересно получается. Т.е. вы для оптимизации кода пишите огромные классы, пропускаете через них данные. Пишите в файлы. Затемм, при выводе, вновь подключаете классы, отправляете запрос на обработку классу итп. Если вам уж так хочется кешировать данные из БД в файлы, то создаваьт их можно простой функцией (одной), а подключать вообще обычным инклудом.
Но сказать я хотел другое. Такой способ кеширования подойдет не всегда Если идет речь о снятии нагркузки - нет ничего лучше стврого доброго html-кеша.

Попробуйте мыслить глобально.
Допустим. У нас есть страница с 10 заголовками новостей, которые мы достаем из БД. Скрипт примерно такой
PHP:
<?
$db=mysql_connect('server', 'login', 'pass');
mysql_select_db('db', $db);
$query=mysql_query("SELECT name, id FROM news ORDER BY id DESC LIMIT 10",$db);
while($a=mysql_fetch_assoc($query)){
	echo '<a href="http://my-uper-puper-site.ru/?id='.$a['id'].'">'.htmlspecialchars($a['name']).'</a>';
}
?>
C SQL кешем полуитмя примерно такая картина

PHP:
<?
if(!include $_SERVER['DOCUMENT_ROOT']."/cash/news.php"){
	$db=mysql_connect('server', 'login', 'pass');
	mysql_select_db('db', $db);
	$query=mysql_query("SELECT name, id FROM news ORDER BY id DESC LIMIT 10",$db);
	$f=fopen($_SERVER['DOCUMENT_ROOT']."/cash/news.php", 'w');
	$dat='<?';
	while($a=mysql_fetch_assoc($query)){
		$dat=$dat.'$data[\''.$a['id'].'\']=\''.htmlspecialchars($a['name']).'\';';
		$data[$a['id']]=$a['name'];
	}
	$dat=$dat.'?>';
	fwrite($f,$dat);
	fclose($f);
}
foreach($data AS $k=>$v){
	echo '<a href="http://my-uper-puper-site.ru/?id='.$k.'">'.htmlspecialchars($v).'</a>';
}
?>
html кеш будет иметь вид

PHP:
<?
if(!include $_SERVER['DOCUMENT_ROOT']."/cash/news.php"){
	$db=mysql_connect('server', 'login', 'pass');
	mysql_select_db('db', $db);
	$query=mysql_query("SELECT name, id FROM news ORDER BY id DESC LIMIT 10",$db);
	ob_start();
	while($a=mysql_fetch_assoc($query)){
		echo '<a href="http://my-uper-puper-site.ru/?id='.$a['id'].'">'.htmlspecialchars($a['name']).'</a>';
	}
	$dat=ob_get_contents();
	ob_end_clean();
	$f=fopen($_SERVER['DOCUMENT_ROOT']."/cash/news.php", 'w');
	fwrite($f,$dat);
	fclose($f);
	echo$dat;
}
?>
Что будет делать сервак в этих трех случаях? Допустим мы имеем 500 000 хитов в сутки

1. Коннектится к БД бесчисленное число раз (почти 6 запорсов в секунду?)
2. Те же 6 раз в секунду открывает и парсит файл на наличие php кода, циклом генерит html
3. тупо вставляет html без лишних действий.

Думаю что не надо долго думать чтобы понять что будет работать быстрее. Хочу еще обратить внимание что в данном примере я не обновляю кеш, скажем, каждые 10 минут. Достаточно просто удалять кеш при создании/редактировании/удалении новости.
А еще лучше nginx`ом или 0w или каким-нить другим легким сервером поситителем отображать именно этот кеш, без обращения к php вообще, а вся скриптовая часть остается администратору. Но это в идеале и на практике это применяется редко, благодаря сверх диномичным интерфейсам, хотя отдельные страницы так отдавать можно.
Например на nnm.ru (порядка 600-700 000 хитов в сутки) индекс для не авторизированных пользователейотображается как статичный html файл, без обращения к php вообще.

Единственный минус html кеширования - нет возможности, например, диномично и просто менять скины сайта. Хотя если руки растут из правельного места и сайт сверстан по стандартам и для смены дизайна достаточно отредактировать style.css, то и эта проблема ршается. А вообще по своему опыту сказать - нет ничего невозможного, выход есть всегда.

-~{}~ 01.11.05 12:56:

Хотя на самом деле, с теоритической точки зрения, БД отдает текст куда быстрее файловой системы =)

-~{}~ 01.11.05 13:00:

Да, кстати, в последнем, третьем, примере более оптимально юзать не include для подгрузки кеша а echo file_get_contents(). Работает быстрее со статикой, что тоже очевидно.
 

sheiko

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

По поводу динамически формируемого HTML - есть большая проблема. Если кешировать страницу целиком, то все запросы использующие ORDER by rand(), и параметры среды окружения (скажем кастомизация страницы под авторизованного пользователя) будут также прокешированны, а содержание отображаемой страницы неактуальным. Т.е. мы должны иметь возможность сообщать какие именно запросы можно кешировать.

Если кешировать блоки - результаты выполнения процессов (логика, SQL-запросы, оформление и т.д.), то здесь вряд ли можно говорить об универсальных решениях.
 

Trinux

Guest
"ООП - для того, что бизнес логика была прозрачной", ага, а катафоты на колесе велосипеда для того, чтобы водитель велика чувствовал себя круче. Один вопрос, общественности это зачем? Никто не видит что твоя бизнес логика в твоих скриптах прозрачна. А вот когда дело заходит до оптимизции кода, вы хоть головой о стену стучите, но вы мне никогда не докажите что ваши классы обрабатывают контент быстрее, чем я это сделаю на стандартных функциях. А насчет html кеширования, внимательней порчитай мой пост, я писал о том что полное html кеширование не подходит для динамического контента. Только вот насчет ORDER BY rand(); надо задумываться, переделывать или отказываться (когда речь заходит о оптимизации).

А вообще, что касается оптимизации, скрипт, который отжирает много ресурсов, либо переисыватся, максиально приблежаясь к статике, либо убирается. Подругому никак.
 

svetasmirnova

маленький монстрик
А насчет html кеширования, внимательней порчитай мой пост, я писал о том что полное html кеширование не подходит для динамического контента. Только вот насчет ORDER BY rand(); надо задумываться, переделывать или отказываться (когда речь заходит о оптимизации).
А ты сам предыдущую дискуссию прочитать что? Поленился? ;)
 

Trinux

Guest
Я прочитал, светасмирнова, толко вот, как мне всегда говорил знакомый программер: "Вам ехать или шашечки?". Вам оптимизировать надо или классы писать? Самой мощной оптимизацией является максимальное превращение динамики на проекте в статитку.
 

sheiko

Новичок
Это, что сам по себе ООП не угодил? Полагаю про АОП (http://blog.redgraphic.ru/sheiko/17-10-05_292/) вообще не стоит упоминать :) Когда-то программы, написанные на сис. коде были определенно самыми быстрыми, но народ постепенно предпочел ассемблер, а ему языки более высокого уровня и т.д. Полагаю здесь все предельно ясно.
 
Сверху