Кэш Для Вики. Часть 2

September 21, 2008Comments Off on Кэш Для Вики. Часть 2

Никто не будет спорить с тем, что mediawiki хоть и мощная система, однако и очень прожорливая. До недавнего момента этот факт меня совершенно не интересовал (посещаемость моего сайта в среднем не превосходит 150 человек в сутки, а время генерации страницы плавало около 1.5-2 секунд). Однако вот пришло ко мне письмо, где интересовались способами "разгона" mediawiki. Что же, задача актуальная и необходимая. К тому же я надеюсь, что посещаемость моего ресурса будет расти, и рано или поздно (а это всегда случается на виртуальных хостингах) мне должно будет придти письмо от провайдера, где попросят умерить аппетиты. Учитывая что дело было в субботу утром, и почти целый день был свободен, то я решил "стряхнуть пыль" с php и "слепить" что-нибудь такое этакое.

Собственно говоря, в mediawiki есть собственная система кэширования и устроена она сложно и хитро т.к. страницы Вики могут правиться кем-угодно, можно работать с историей правок, обсуждать документы. Меня это не устраивает т.к. правлю я свой сайт сам (горе-спамеры не в счет), правлю где-то раз, может два, в неделю. Таким образом количество просмотров (ах-да, у нас же есть google и прочие спайдер-мены) резко превалирует над количество правок и кэш можно сделать и проще и быстрее (читай без инициализации всех тех мегабайт wiki-кода которые будут проверять что-то в своем кэше, к тому же размещенном в БД). По сути, для кэша должно быть три операции: взять из кэша, положить в кэш и удалить из кэша. В качестве ключа кэша выступает адрес (имя страницы). Желательно при этом, чтобы хранилище кэша было максимально быстрым и отделено от хранилищ основной информации. Ведь кэшированная версия страницы (с html-кодом оформления) гораздо больше по размерам, чем голая информация хранимая в БД. Так что вскоре размер БД "зашкалит" за несколько сотен мегабайт и операции поиска и обновления такого кэша будут занимать время большее, чем генерация страницы "с нуля". Плюс проблемы с созданием backup-ов (если ваш тарифный план хостинга не позволяет иметь несколько БД).

Как вывод: хранить снимки страниц в mysql БД глупо. С другой стороны хранить данные в файловой системе еще глупее. Т.к. рано или поздно я захочу получить некоторую статистическую информацию об том, что там внутри кэша делается. Таким образом, на роль кэш-хранилища я выбрал sqlite 3 (об этой СУБД я уже рассказывал ранее в статье про google gears.

Как настроить?
  • Файл index.php в исталляции mediawiki я переименовал в index_core.php. Туда же, в корень инсталляции mediawiki, я поместил следующий скрипт и сохранил его под именем index.php.
  • Затем я создал папку XXX_CACHE в которой будут храниться кэш страниц.
  • Настройка кода скрипта: Константа PATH_TO_CACHE_DIR определяет путь к каталогу, в котором будут храниться базы sqlite 3 и файлы журнала ошибок.

Константа FILE_NAME_SQLITE3 задает шаблон имени файла базы данных. Почему шаблон? Дело в том, что хранить все снимки в одной гигантской БД глупо. Sqlite, конечно продукт замечательный и всякое такое разное, но при внесении правок в БД требуется получить для нее монопольный режим доступа. Плюс, очевидно, что правка небольшого файла будет выполнена быстрее, чем большого. Как вывод, "снимки" страниц будут храниться в нескольких файлах. Имя файла будет вычисляться на основании имени страницы с помощью алгоритма crc32. Предельное количество подобных файлов задается константой CACHE_SPLIT_STRATEGY_SIZE. Имена баз данных формируется по правилу. Для шаблона mediawiki.cache.{0}.db3, например, будут такие файлы:

mediawiki.cache.1.db3, mediawiki.cache.2.db3, mediawiki.cache.3.db3 ...

Константа FILE_NAME_ERROR_LOG задает имя файла журнала ошибок (возникающих в ходе правок БД). Файл должен быть размещен внутри каталога PATH_TO_CACHE_DIR.

Последняя константа CACHE_STOLE_DELTA_MS задает время устаревания кэша (в ms.). Строго говоря, эту величину можно поставить бесконечно большой и тогда фактор времени будет исключен из правил устаревания информации в кэше.

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

Константа URI_EXTRACTOR_FUNCTION_NAME задает имя функции которая играет роль "извлекателя" имени страницы из запроса.

Константа CUSTOM_PAGE_STRATEGY_SELECTOR_FUNCTION_NAME задает имя функции решающей по какой стратегии будет работать кэш. Принимая в качестве параметра "чистый" адрес запрошенной страницы, функция должна вернуть одно из трех значений:
  1. delete-from-cache страницу нужно удалить из кэша.
  2. exclude страница не должна быть запрошена из кэша, а сгенерирована заново.
  3. normal-flow работают стандартные правила кэширования.

Например, моя реализация функции получения стратегии, при запросе страницы на редактирование не берет ее из кэша. При отправке изменений в страницу, то информация об ней из кэша удаляется. И во всех остальных случаях используется стандартный life-cycle: посмотреть есть ли запись об таблице в кэше. Если ее нет или же время генерации страницы устарело, то сгенерировать страницу наново и поместить ее в кэш. В противном случае информация берется из кэша.

Таблица кэша имеет следующий вид:
  1. CREATE TABLE IF NOT EXISTS wiki_cache (
  2.       id         integer PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
  3.       url        varchar(1024) NOT NULL UNIQUE,
  4.       md5url     varchar(1024) NOT NULL UNIQUE,
  5.       hitqty	 INT NOT NULL DEFAULT 0,		
  6.       refreshqty INT NOT NULL DEFAULT 0,		
  7.       refreshtimestamp  INT NOT NULL,
  8.       hittimestamp INT NOT NULL,
  9.       refreshtimestamp_f TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  10.       hittimestamp_f TIMESTAMP NOT NULL  DEFAULT CURRENT_TIMESTAMP,
  11.       content    clob
  12. )
Как видите, я храню в в ней имя страницы, md5 свертку от ее имени (именно это поле, а не имя страницы используется при поиске записей в кэше). Также хранятся статистические сведения, такие как время попадания страницы в кэш, время последнего обращения к странице, количество запросов на обновление страницы (сколько раз ее ложили в кэш) и сколько раз страницу запрашивали. Все это может пригодиться для каких-то аналитических запросов. Последнее поле - content - хранит, собственно, содержимое документа.

Если вы откроете исходный html-код страницы, то в самом ее низу будет написано несколько слов о состоянии кэш системы:
<!-- got from sqlite3 cache (
	timeused => 0.0780429840088;
	hitqty => 3;
	refreshqty => 1;
	hittimestamp => Sun, 21 Sep 08 15:08:46 +0300;
	refreshtimestamp => Sun, 21 Sep 08 15:08:46 +0300;
	comment => really got page from cache;
	used_db_name => mediawiki.cache.7.db3;
	md5url => 3609bfb8cef4042d33de5f20e6fb3842;
) -->
А теперь исходный код: https://github.com/study-and-dev-site-attachments/all-in-one/tree/master/php/mediawikicache/index_cache.php