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
Просто так рельсы не умеют читать композитные
типы и массивы:
{quot;(vendor_text,{Cavei},string,Производитель)quot;,quot;(name,quot;{quot;quot;CV-PT10 H3quot;quot;}quot;,string,Модель)quot;,quot;(type,
{настольный},string,Тип)quot;,quot;(purpose,quot;{quot;quot;фото- и видеокамерыquot;quot;}quot;,string,quot;Сфера примененияquot;)quot;,quot;(construction,
{трипод},string,Конструкция)quot;,…
Для этого был написан неопубликованный патч
PostgresParser:
>> Device.find_by_permalink('pentax-k10d').cached_properties
=> [#<struct ArrayRetrieval::CachedProperty name=quot;fullnamequot;, values=[quot;Pentax K10Dquot;], data_format=quot;stringquot;,
description=quot;Полное названиеquot;>, #<struct ArrayRetrieval::CachedProperty name=quot;start_datequot;, values=[quot;2006/09/13quot;],
data_format=quot;stringquot;, description=quot;Дата анонсаquot;>, #<struct ArrayRetrieval::CachedProperty name=quot;lens_mountquot;, values=[quot;KAFquot;],
data_format=quot;stringquot;, description=quot;Байонетquot;>
Код ещё медленный, по 20 мс на разбор 120
свойств в текстовом виде вместо 200-300 мс на
вытаскивание из БД.
ActiveRecord внутри — жуть =(
И помогло?
17. РЕЗУЛЬТАТЫ
Лично я стараюсь сейчас придерживаться такой
стратегии: кешировать в базе данные, которые
можно вычислить.
В memcached класть только HTML, который не
требует быть 100% синхронным. Я пока не нашел
разумного способа отслеживать зависимости
фрагментов в мемкеше от данных.
Кеширование в базе разумно делать с помощью
расширенных средств БД, т.к. кеш штука
опциональная.
И помогло?