Организация самостоятельного кэширования для высоконагруженных проектов

AlexBB

Новичок
Организация самостоятельного кэширования для высоконагруженных проектов

Это не совсем вопрос. Хочется поделится своиими изысканиями в области разработки
проектов с информационным контентом на php, испытывающих сильные пиковые нагрузки.
Возможено кому-то они окажутся полезными. В тоже время, если уважанмое сообщество укажет
на недостатки предлагаемого подхода или предложит усовершенствования буду безмерно благодарен.

Итак, имеем проект реализованный на стандартной связке PHP+MySQL.
Время от времени (конкретно в моем случае раз в полчаса), контент-менеджер выполняет
обновления данных. Т.е. запускается php скрипт, выполнющий большой пакет SQL запросов
INSERT и/или UPDATE по большей части таблиц БД. Назовем его условно insert.php

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

Практика и тестирование показали, что в системах подобной конфигурациях,
в моменты пиковой нагрузки, как правило узким местом оказывется база данных.
Таким образом, наша задача организовать кэширование страниц, чтоб ресурсоемкие
запросы SELECT выполнялись не более одного раза для каждой страницы.

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

1. В БД создается таблица Сache c одним единственным полем Page типа Varchar

2. В скрипт insert.php добавляется следующее

PHP:
$shm_id = shmop_open(SHMOP_LASTUPDATE, "c", 0644, 10); // Резирвируется память с адресом SHMOP_LASTUPDATE для сохранения времени обновления
shmop_write($shm_id, time(), 0); // В память записывается метка текущего времени
mysql_query('TRUNCATE TABLE Cache'); // Очищается Таблица Cache
3. В скрипты отображения страниц обавляется следующее

PHP:
function CheckCache($CACHE) // Функция проверяет надо ли запускать динамическую генерацию страницы или взять ее из кэша. Входной параметр - имя траницы в дисковом кэше.
{
	$res = mysql_query("SELECT COUNT(*) FROM Cache WHERE Page='$CACHE'"); // Проверяем есть ли наша страница уже в кэше
	$wasprocessed = mysql_result($resultsetId, 1); // Если есть то $wasprocessed>0
	if ($wasprocessed)
	{
		return false; // Свежая страница есть в кэше.
	}
	else // Если страницы нет в таблице Page надо ее генерить полюбому.
	{
		$file_time = @filemtime($CACHE); // Читаем время последнего изменения файла в кэше. На случай если он пропал, подавляем ошибку.
		$httpheaders = getallheaders(); // Читаем http заголовки
		$current_time = time(); // Берем текущую метку времени
		$shm_id = shmop_open(SHMOP_LASTUPDATE, "c", 0644, 10); // Открываем память с адресом SHMOP_LASTUPDATE, где хранится метка времени последнего обновления
		$lastupdate = shmop_read($shm_id, 0, 10); // Читаем метку времени последнего обновления
	}
	if (
			!isset($file_time) || // Если вообще нет файла в кэше
			($lastupdate - $file_time > OLDCHACHEFILE_SECONDS) ||  // Или если файл старее чем OLDCHACHEFILE_SECONDS секунд. OLDCHACHEFILE_SECONDS - определяем в зависимости от актуальности свежести данной страницы
			(isset($httpheaders['Cache-Control']) && $httpheaders['Cache-Control'] == 'no-cache' && $file_time < $lastupdate) // Или если пользователь принудительно просит страницу не "из кэша" Ctrl+F5 и файл в кэше устарел. Это условие можно и убрать или добавить здесь проверку ip "своих пользователей".
		)
		return true; // Генерим страницу т.к. или ее нет или она слишком старая
	else
		return false; // Свежая или не слишком старая страница есть в кэше.
}

$CACHE = 'cache/index.html'; // Имя страницы, как она будет называться находясь в кэше. Сюда, в принципе, можно вставить автоматическую генерацию имен по URL
if (CheckCache($CACHE)) // Проверяем надо ли запускать динамическую генерацию страницы или взять ее из кэша
{
	ob_start(); // Включаем буферицацию
	//
	// Здесь все ресурсоемкие запросы SELECT
	//
	$result = ob_get_contents(); // В переменную $result cбрасываем все результаты работы скрипта
	ob_end_clean(); // Очищаем буфер
	
	$res = file_put_contents($CACHE, $result); // Сохраняем резутьтат на диск в кэш
	if ($res) // Если сохранение удачно, записываем в базу данных, что данная страница есть в кэше
	{
		mysql_query("INSERT INTO Саche(PAGE) VALUES('$CACHE')");
	}	
	
}
else // Сразу берем страницу из кэша. Сюда и должно попасть большая часть посетителей.
{
	$result = file_get_contents($CACHE);
}

echo $result; // Выводим страницу
Буду, очень благодарен за любую критику и дополнения.
 

zarus

Хитрожопый макак
1. Всю страницу кэшируем? Ну-ну.
А вот зайдет на сайт "админчег", а потом обычный "гест". И что увидит "гест"? Правильно, увидит то, что ему нельзя видеть.
Так что нужно как минимум добавить уровень доступа к кэшу.
Кэшировать страницу целиком - это непозволительная роскошь, а отдельные части - проблематично.

2. А нафига козе баян. В смысле накой фиг кэшировать ВЕСЬ сайт раз в полчаса? Вы нагрузку уменьшить хотите или сервер загрузить по самый swap? Кэшировать нужно тогда, когда это нужно и только то, что действительно требует кэширования.
Дополнительное поле в базе - время создания кэша по странице. Пользователь обратился - посмотрели есть ли кэш, посмотрели на время кэша. Если кэш устарел - делаем новый, если нет - грузим из файла.
Лишняя проверка на наличие файла - если в базу вносится запись о кэше только после успешного сохранения, то исчезновение файла с кэшем после этого - ситуация мягко говоря не понятная.
 

crocodile2u

http://vbolshov.org.ru
Не лучше ли использовать файловый кэш в таком случае? Если нужный файл с кэшем найден, и не слишком старый - вообще не понадобится соединяться с базой. А ведь это тоже экономия.
 

zarus

Хитрожопый макак
Автор оригинала: crocodile2u
Не лучше ли использовать файловый кэш в таком случае? Если нужный файл с кэшем найден, и не слишком старый - вообще не понадобится соединяться с базой. А ведь это тоже экономия.
А что будет быстрее - кэшированный SELECT или проверка существования файла и время его создания/изменения? Это вопрос не осведомленного.
 

AlexBB

Новичок
2zarus

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

2. Зачем сохраниять в базе? А затем, что у меня пиковые нагрузди. В один момент времени на страницу ломится куча народу, т.е. файл еще не успевает сгенерится, а уже запускается параллельно куча процессов на его генерацию.
 

zarus

Хитрожопый макак
Автор оригинала: AlexBB
2zarus

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

2. Зачем сохранять в базе? А затем, что у меня пиковые нагрузди. В один момент времени на страницу ломится куча народу, т.е. файл еще не успевает сгенерится, а уже запускается параллельно куча процессов на его генерацию.
1. Это неправильное упрощение. Бесмысленное. Имхо.
2. LOCK TABLE возможно спасет отца русской php-комьюнити. Лочишь таблицу кэша на чтение - остальные скрипты будут ждать анлока. Как закончил с кэшем, анлок таблицы.
 

AlexBB

Новичок
1. Это неправильное упрощение. Бесмысленное. Имхо.

Почему? Подавляющее большинство информационных сайтов именно такие. Новостные ленты например.

2. LOCK TABLE возможно спасет отца русской php-комьюнити. Лочишь таблицу кэша на чтение - остальные скрипты будут ждать анлока. Как закончил с кэшем, анлок таблицы.

С LOCK TABLE можно поэксперементировать, но это не избавляет от специальной таблицы кэша в базе, на что собственно было указано как на недостаток.
 

zarus

Хитрожопый макак
Автор оригинала: AlexBB
С LOCK TABLE можно поэксперементировать, но это не избавляет от специальной таблицы кэша в базе, на что собственно было указано как на недостаток.
Это не было указано, как на недостаток, был задан вопрос "почему бы не...?" Вы аргументировали тем, что нагрузка очень высокая. Но тут возникает другой вопрос, а действительно ли там такая нагрузка. Может скрипты у Вас не оптимизированные и стоит сначала заняться ими, а потом уже кэшированием.
 

AlexBB

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

Оптимизировать скрипты - это само собой. Это делается постоянно. Но это отдельная тема ...

-~{}~ 27.11.06 17:54:

Автор оригинала: magic
AlexBB
Мсье слышал про PEAR Cache/Cache_Lite ?
Слышал, коненчо. Правда пока не понял, чем именно это лучше предложенного подхода.
Если поясните, буду благодарен. Не исключаю, что тут я не "слишком глубоко" залез в Pear и чего-то не увидел.
 

Alexandre

PHPПенсионер
Не лучше ли использовать файловый кэш в таком случае? Если нужный файл с кэшем найден, и не слишком старый - вообще не понадобится соединяться с базой. А ведь это тоже экономия.
а если 5 000 страниц?
 

HraKK

Мудак
Команда форума
Alexandre
База от файлов не имеет принципиальной разницы.
Только гемора больше.
 

Popoff

popoff.donetsk.ua
http://popoff.donetsk.ua/text/work/libs/cms/cache/

Только при действительно больших нагрузках такого рода кеши могут быть использованы лишь как цепочка большого количества других оптимизаций. Например, разделить по разным компьютерам динамический/статический контент, вынести БД на отдельный комп, разбить БД на части. Что-то, что возможно, кешируется в файлах, что-то - в БД. Ну и сами скрипты должны быть нормально написаны: я, например, всегда смотрю за временем генерации элементов кеша, и если оно больше 1-2 секунд, мучаю скрипты до победного, и только после этого включаю кеш.
 

AlexBB

Новичок
2Popoff

Абсолютно согласен. Еще стараюсь не забывать про индексы. Оптимизировать SQL. Поиграться встроенными переменными БД. Отключать построение индексов при большом количестве последовательных INSERT. И.т.д. и.т.п.

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

Alexandre

PHPПенсионер
База от файлов не имеет принципиальной разницы.
Только гемора больше.
при большом числе файлов (более 1000), собранных в одной директории - файловая система начинает тормозить, пропорционально экспоненте кол-ва файлов. По этому нужно организовать "умное" кеширование.

Организация кеширования в БД - не лучший вариант.

Мое решение - кеширование организуется блоками. Блоки могут находится в оперативном или статическом кеше.
Оперативный кеш - мемкеш
Статический - файловая система или БД ( например наиболее лучший вариант именно для кеширования подходит BerckleyDB)
Сборщик блков - быстрый шаблонизатор, либо php_templates, либо blitz, либо php_ctpp
Сам шаблонизатор - желательно использовать один из этих же, но в моем проекте остался смарти... (это уже прихоти дизайнера, да и лень мне было переделывать все шаблоны смарти под новый шаблонизатор...)

Саму БД, в которой хранится контент, лучше оставить в покое (кеширование не нагружать)

все задачи решаются в комплексе.
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Автор оригинала: Alexandre
при большом числе файлов (более 1000), собранных в одной директории - файловая система начинает тормозить, пропорционально экспоненте кол-ва файлов. По этому нужно организовать "умное" кеширование.
Про экспоненту это ты сильно загнул. :D
В простейшей файловой системе время доступа будет O(N), в более продвинутых O(lnN), наверное, т.к. используются хэши или btree.
Не говоря уже о том, что каталогов можно наплодить несколько.
 

AlexBB

Новичок
O(N) - это как раз "примерно линейная зависимость". )

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

Alexandre

PHPПенсионер
O(N) - это как раз "примерно линейная зависимость". )
Линейной не может быть в принципе
скорость выборки в директории зависит от скорости выбора имени файла из экстента. Скорость выбора зависит от величины экстентов и длины имен файлов, плюс списочное построение экстентов, происходит повторный проход при использовании strlen() (недостатки использования strlen() ) - читайте и считайте как работает фс.
Это это классический недостаток использования strlen()

сервер упал от суперперегрузки
ну тут ты загнул конечно....
но от нескольких миллионов файлов - может и упасть :) хотя скорость обработки настолько мала - наврядли...
 

AlexBB

Новичок
вы представляете во сколько раз вырастает значение экспоненты (а значит по вашему и нагрузки), при изменении числа файлов ну хотя бы со 100 до 101. Про тысячи даже не говорим.

Посчитайте ... на калькуляторе. Если разрядов, конечно хватит :)
 
Сверху