Кэширование результатов с разбивкой на страницы, очистка при обновлении - как решить?

Я создал форум, и мы внедряем решение для кэширования apc и memcache, чтобы избавить базу данных от некоторой работы.

Я начал реализовывать слой кеша с такими ключами, как «Categories::getAll», и если бы у меня были пользовательские данные, я бы добавил к ключам такие вещи, как идентификатор пользователя, так что вы получите "User::getFavoriteThreads|1471". Когда пользователь добавлял новый любимый поток, я удалял ключ кеша, и он воссоздавал запись.

Однако здесь возникает проблема:

Я хотел кэшировать темы на форуме. Достаточно просто, "Forum::getThreads|$iForumId". Но... С нумерацией страниц мне пришлось бы разделить это на несколько записей кеша, например

"Forum::getThreads|$iForumId|$iLimit|$iOffset".

Все в порядке, пока кто-нибудь не создаст новую тему на форуме. Теперь мне придется удалить все ключи под "Forum::getThreads|$iForumId", независимо от предела и смещения.

Что было бы хорошим способом решения этой проблемы? Я действительно предпочел бы не перебирать все возможные ограничения и смещения, пока не найду что-то, что больше не соответствует.

Спасибо.


person Rexxars    schedule 20.09.2008    source источник


Ответы (8)


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

Если вы обнаружите, что 80% просмотров вашего форума просматривают первую страницу темы, вы можете решить кэшировать только эту страницу. Это означало бы, что как чтение, так и запись кэша реализовать намного проще.

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

person Josh    schedule 21.09.2008

Просто обновление: я решил, что точка зрения Джоша на использование данных была очень хорошей. Люди вряд ли будут продолжать просматривать 50-ю страницу форума.

Основываясь на этой модели, я решил кэшировать 90 последних тем на каждом форуме. В функции извлечения я проверяю ограничение и смещение, чтобы увидеть, находится ли указанный фрагмент потоков в кеше или нет. Если он находится в пределах ограничения кеша, я использую array_slice(), чтобы получить нужную часть и вернуть ее.

Таким образом, я могу использовать один ключ кеша для каждого форума, и для очистки/обновления кеша требуется очень мало усилий :-)

Я также хотел бы отметить, что в других более ресурсоемких запросах я использовал модель flungabunga, сохраняя отношения между ключами. К сожалению, переполнение стека не позволяет мне принять два ответа.

Спасибо!

person Rexxars    schedule 22.09.2008

Мне удалось решить эту проблему, расширив класс memcache пользовательским классом (скажем, ExtendedMemcache), который имеет защищенное свойство, которое будет содержать хэш-таблицу групповых значений ключей.

Метод ExtendedMemcache->set принимает 3 аргумента ($strGroup, $strKey, $strValue). Когда вы вызываете set, он сохраняет отношение между $strGroup и $strKey в защищенном свойстве, а затем сохраняет отношение $strKey к $strValue в memcache.

Затем вы можете добавить в класс ExtendedMemcache новый метод под названием «deleteGroup», который при передаче строки будет находить ключи, связанные с этой группой, и очищать каждый ключ по очереди.

Это будет примерно так: http://pastebin.com/f566e913b Надеюсь, все это имеет смысл и работает. для вас.

PS. Я предполагаю, что если вы хотите использовать статические вызовы, защищенное свойство может быть сохранено в самом memcache под своим собственным ключом. Просто мысль.

person flungabunga    schedule 20.09.2008

По сути, вы пытаетесь кэшировать представление, что всегда будет сложно. Вместо этого вы должны попытаться кэшировать только данные, потому что данные редко изменяются. Не кэшируйте форум, кэшируйте строки темы. Тогда ваш вызов БД должен просто вернуть список идентификаторов, который у вас уже есть в вашем кеше. Вызов db будет выполняться молниеносно на любой таблице MyISAM, и тогда вам не придется выполнять большое соединение, которое съедает память db.

person Brent    schedule 20.09.2008
comment
Я не знаю, о какой структуре таблицы вы думаете, но в любом случае соединение не понадобится, если у вас есть таблица потоков. Выгода с точки зрения использования кеша будет незначительной. - person Nick Johnson; 21.09.2008
comment
Это, вероятно, хорошее решение, хотя с моей стороны потребовалось бы довольно большое переписывание - нужно получить много данных (количество сообщений в потоке, ник авторов должен быть присоединен к пользовательской таблице, количество просмотров и т. д.). Спасибо за предложение! - person Rexxars; 21.09.2008
comment
Похоже, вы могли бы добиться эквивалентного ускорения, немного денормализуя. Сохраняйте количество сообщений, имя автора, количество просмотров и т. д. в записи темы. - person Nick Johnson; 21.09.2008

Одно из возможных решений — не разбивать кеш тредов на форуме, а поместить информацию о треде в Forum::getThreads|$iForumId. Затем в вашем PHP-коде вытащите только те, которые вам нужны для данной страницы, например.

$page = 2;
$threads_per_page = 25;
$start_thread = $page * $threads_per_page;

// Pull threads from cache (assuming $cache class for memcache interface..)
$threads = $cache->get("Forum::getThreads|$iForumId");

// Only take the ones we need
for($i=$start_thread; $i<=$start_thread+$threads_per_page; $i++)
{
    // Thread display logic here...
    showThread($threads[$i]);
}

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

person ConroyP    schedule 20.09.2008
comment
Я думал об этом, но я преобразовываю существующий форум в этот, а в одном форуме 220 000 тем, что было бы очень много данных для хранения таким образом. Это, вероятно, лучшее решение, если данных было меньше. Спасибо! - person Rexxars; 21.09.2008

flungabunga: Ваше решение очень близко к тому, что я ищу. Единственное, что удерживает меня от этого, — необходимость сохранять отношения в memcache после каждого запроса и загружать их обратно.

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

person Rexxars    schedule 20.09.2008

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

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

person troelskn    schedule 21.09.2008

В ответ на флюнгабунга:

Другой способ реализовать группировку — поместить имя группы и порядковый номер в сами ключи и увеличить порядковый номер, чтобы «очистить» группу. Вы сохраняете текущий допустимый порядковый номер для каждой группы в своем собственном ключе.

e.g.

get seqno_mygroup
23

get mygroup23_mykey
<mykeydata...>
get mygroup23_mykey2
<mykey2data...>

Затем «удалить» группу просто:

incr seqno_mygroup

Вуаля:

get seqno_mygroup
24

get mygroup24_mykey
...empty

и т.д..

person Community    schedule 13.01.2009