2. ПРИВЕТ
О чем буду рассказывать?
Меня зовут Макс Лапшин
•max@maxidoors.ru
•http://github.com/maxlapshin
•модератор ror2ru
3. ПРОБЛЕМЫ КЕШИРОВАНИЯ
Это точно кому-то нужно?
Кеширование — сохранение однажды
вычисленных данных для последующего их
повторного использования.
Кеширование затрагивает следующие проблемы:
•запись кешируемых данных;
•извлечение закешированных данных;
•очистка неактуальных закешированных данных,
т.е. поддержание когерентности кеша
Последний пункт, пожалуй, самый проблемный.
4. ЗАЧЕМ КЕШИРОВАТЬ?
А чего кешировать?
•Быстрый рендеринг одной страницы даже на
малопосещаемом сайте
•Приемлемая загрузка при большом потоке
посетителей
•Быстрые апдейты
•Быстрые выборки
•Быстрый рендеринг
5. КАНДИДАТЫ В КЕШ
Да ну, чего там медленного?
Примеры с живых сайтов:
•Счетчик непрочитанных сообщений;
•Корреспонденты по переписке в личных
сообщениях на lookatme.ru;
•Сложно сгруппированные атрибуты из 4-
табличного JOIN-а в спецификации камеры на
new.prophotos.ru
6. ОТКУДА НАГРУЗКА?
При выборке списка корреспондентов из таблицы
сообщений происходит JOIN и группировка
таблиц больше 1M записей. Это чудовищная
нагрузка на любую базу.
При показе счетчика сообщений происходит
выборка из огромной таблицы на каждом показе
страницы.
Это неоправданная нагрузка на базу и замедление
отдачи страницы.
7. МОЖЕТ MEMCACHED?
Чем же плох memcached?
Очень быстрая сетевая, прекрасно
масштабирующаяся, хеш-таблица с
гарантированным максимальным временем жизни
записи и гарантированным отсутствием гарантий
жизни чего-бы то ни было.
Он вообще имеет право ничего не сохранять.
8. ЧЕМ ПЛОХ MEMCACHED?
А куда кешировать?
МНОГИМ
•Нельзя получить список ключей;
•Нет100% гарантии когерентности кеша;
•Данные из Memcached нельзя использовать в
SQL выборках;
•Его надо разогревать;
•Не решает проблему медленного показа редко
посещаемых страниц.
9. НЕУЖЕЛИ ВСЁ ТАКИ В БД?
А подетальнее?
•Счетчик непрочитанных сообщений в переписке
между двумя пользователями будет
использоваться при рендеринге раздела
«непрочитанные». Использовать memcached
невозможно.
•Сохранить в событие список идущих на него
user_id, сохранить пользователю список friends_id
и можно прям в БД получить количество идущих
на событие друзей.
10. СТРАТЕГИИ КЕШИРОВАНИЯ
А подетальнее?
•Со счетчиками всё ясно: их стоит кешировать
всегда. @user.friends.count @user.friends.size
•Кеширование может породить целую модель,
решающую проблему кеша. Например
«Переписка», в ней будут храниться все данные
для быстрого рендеринга: user_login, sender_login,
last_message_id;
•Специфичные средства БД (PostgreSQL).
11. СЧЕТЧИКИ
А чего со списком друзей?
•Всегда делайте NOT NULL DEFAULT 0;
•Лучше инкрементить/декрементить из
after_create/after_destroy дочерней модели;
•Если не лочить таблицу, то возможны коллизии,
надо быть готовым всегда пересчитать сбившийся
счетчик (-5 непрочитанных сообщений);
•Всегда использовать @user.friends.size, потому
что он использует @user.friends_count
12. СПИСКИ
•Rails (без патчей) не позволяют иметь свой
формат сериализуемого атрибута, поэтому если
сохранять через serialize, в базе будетYAML. Это
большой overhead;
•Нет ничего страшного в том, что бы
сгенерироватьYAML SQL-запросом, это может
быть в 200 раз быстрее, чем через find_each и save;
•В PostgreSQL есть тип ARRAY, который можно
заполнять, пересекать и обрабатывать прям в базе:
13. СЛОЖНЫЕ ДАННЫЕ
Что ещё за композитные типы?
•При показе спецификации фотоаппарата, надо
вытянуть список свойств камеры, список типов
свойств и по нему сгруппировать свойства;
•Если у вас Mysql, то делайте всё в рельсах,
сохраняйте группированные данные вYAML. Это
медленно, но другого способа нет. На обработку
5000 записей уходит до получаса.
•В PostgreSQL есть композитные типы —
палочка-выручалочка.
14. КОМПОЗИТНЫЕ ТИПЫ
Как с таким нонконформизмом работать?
На new.prophotos.ru была опробована методика
сохранения группированных данных
специфичными средствами БД:
15. WRITE ONCE
А кто это будет читать?
Такие данные можно только переписывать и
читать. Дописывать в такую колонку
нецелесообразно. Апдейт всей таблицы 30 сек
16. READ FAST
Просто так рельсы не умеют читать композитные
типы и массивы:
{"(vendor_text,{Cavei},string,Производитель)","(name,"{""CV-PT10 H3""}",string,Модель)","(type,
{настольный},string,Тип)","(purpose,"{""фото- и видеокамеры""}",string,"Сфера применения")","(construction,
{трипод},string,Конструкция)",…
Для этого был написан неопубликованный патч
PostgresParser:
>> Device.find_by_permalink('pentax-k10d').cached_properties
=> [#<struct ArrayRetrieval::CachedProperty name="fullname", values=["Pentax K10D"], data_format="string",
description="Полное название">, #<struct ArrayRetrieval::CachedProperty name="start_date", values=["2006/09/13"],
data_format="string", description="Дата анонса">, #<struct ArrayRetrieval::CachedProperty name="lens_mount", values=["KAF"],
data_format="string", description="Байонет">
Код ещё медленный, по 20 мс на разбор 120
свойств в текстовом виде вместо 200-300 мс на
вытаскивание из БД.
ActiveRecord внутри — жуть =(
И помогло?
17. РЕЗУЛЬТАТЫ
Лично я стараюсь сейчас придерживаться такой
стратегии: кешировать в базе данные, которые
можно вычислить.
В memcached класть только HTML, который не
требует быть 100% синхронным. Я пока не нашел
разумного способа отслеживать зависимости
фрагментов в мемкеше от данных.
Кеширование в базе разумно делать с помощью
расширенных средств БД, т.к. кеш штука
опциональная.
И помогло?