КЕШИРОВАНИЕ
ДАННЫХ В БД
Макс Лапшин
апрель 2009
ПРИВЕТ
О чем буду рассказывать?
Меня зовут Макс Лапшин
•max@maxidoors.ru
•http://github.com/maxlapshin
•модератор ror2ru
ПРОБЛЕМЫ КЕШИРОВАНИЯ
Это точно кому-то нужно?
Кеширование — сохранение однажды
вычисленных данных для последующего их
повторного использования.
Кеширование затрагивает следующие проблемы:
•запись кешируемых данных;
•извлечение закешированных данных;
•очистка неактуальных закешированных данных,
т.е. поддержание когерентности кеша
Последний пункт, пожалуй, самый проблемный.
ЗАЧЕМ КЕШИРОВАТЬ?
А чего кешировать?
•Быстрый рендеринг одной страницы даже на
малопосещаемом сайте
•Приемлемая загрузка при большом потоке
посетителей
•Быстрые апдейты
•Быстрые выборки
•Быстрый рендеринг
КАНДИДАТЫ В КЕШ
Да ну, чего там медленного?
Примеры с живых сайтов:
•Счетчик непрочитанных сообщений;
•Корреспонденты по переписке в личных
сообщениях на lookatme.ru;
•Сложно сгруппированные атрибуты из 4-
табличного JOIN-а в спецификации камеры на
new.prophotos.ru
ОТКУДА НАГРУЗКА?
При выборке списка корреспондентов из таблицы
сообщений происходит JOIN и группировка
таблиц больше 1M записей. Это чудовищная
нагрузка на любую базу.
При показе счетчика сообщений происходит
выборка из огромной таблицы на каждом показе
страницы.
Это неоправданная нагрузка на базу и замедление
отдачи страницы.
МОЖЕТ MEMCACHED?
Чем же плох memcached?
Очень быстрая сетевая, прекрасно
масштабирующаяся, хеш-таблица с
гарантированным максимальным временем жизни
записи и гарантированным отсутствием гарантий
жизни чего-бы то ни было.
Он вообще имеет право ничего не сохранять.
ЧЕМ ПЛОХ MEMCACHED?
А куда кешировать?
МНОГИМ
•Нельзя получить список ключей;
•Нет100% гарантии когерентности кеша;
•Данные из Memcached нельзя использовать в
SQL выборках;
•Его надо разогревать;
•Не решает проблему медленного показа редко
посещаемых страниц.
НЕУЖЕЛИ ВСЁ ТАКИ В БД?
А подетальнее?
•Счетчик непрочитанных сообщений в переписке
между двумя пользователями будет
использоваться при рендеринге раздела
«непрочитанные». Использовать memcached
невозможно.
•Сохранить в событие список идущих на него
user_id, сохранить пользователю список friends_id
и можно прям в БД получить количество идущих
на событие друзей.
СТРАТЕГИИ КЕШИРОВАНИЯ
А подетальнее?
•Со счетчиками всё ясно: их стоит кешировать
всегда. @user.friends.count @user.friends.size
•Кеширование может породить целую модель,
решающую проблему кеша. Например
«Переписка», в ней будут храниться все данные
для быстрого рендеринга: user_login, sender_login,
last_message_id;
•Специфичные средства БД (PostgreSQL).
СЧЕТЧИКИ
А чего со списком друзей?
•Всегда делайте NOT NULL DEFAULT 0;
•Лучше инкрементить/декрементить из
after_create/after_destroy дочерней модели;
•Если не лочить таблицу, то возможны коллизии,
надо быть готовым всегда пересчитать сбившийся
счетчик (-5 непрочитанных сообщений);
•Всегда использовать @user.friends.size, потому
что он использует @user.friends_count
СПИСКИ
•Rails (без патчей) не позволяют иметь свой
формат сериализуемого атрибута, поэтому если
сохранять через serialize, в базе будетYAML. Это
большой overhead;
•Нет ничего страшного в том, что бы
сгенерироватьYAML SQL-запросом, это может
быть в 200 раз быстрее, чем через find_each и save;
•В PostgreSQL есть тип ARRAY, который можно
заполнять, пересекать и обрабатывать прям в базе:
СЛОЖНЫЕ ДАННЫЕ
Что ещё за композитные типы?
•При показе спецификации фотоаппарата, надо
вытянуть список свойств камеры, список типов
свойств и по нему сгруппировать свойства;
•Если у вас Mysql, то делайте всё в рельсах,
сохраняйте группированные данные вYAML. Это
медленно, но другого способа нет. На обработку
5000 записей уходит до получаса.
•В PostgreSQL есть композитные типы —
палочка-выручалочка.
КОМПОЗИТНЫЕ ТИПЫ
Как с таким нонконформизмом работать?
На new.prophotos.ru была опробована методика
сохранения группированных данных
специфичными средствами БД:
WRITE ONCE
А кто это будет читать?
Такие данные можно только переписывать и
читать. Дописывать в такую колонку
нецелесообразно. Апдейт всей таблицы 30 сек
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 внутри — жуть =(
И помогло?
РЕЗУЛЬТАТЫ
Лично я стараюсь сейчас придерживаться такой
стратегии: кешировать в базе данные, которые
можно вычислить.
В memcached класть только HTML, который не
требует быть 100% синхронным. Я пока не нашел
разумного способа отслеживать зависимости
фрагментов в мемкеше от данных.
Кеширование в базе разумно делать с помощью
расширенных средств БД, т.к. кеш штука
опциональная.
И помогло?

Кеширование данных в БД

  • 1.
  • 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% синхронным. Я пока не нашел разумного способа отслеживать зависимости фрагментов в мемкеше от данных. Кеширование в базе разумно делать с помощью расширенных средств БД, т.к. кеш штука опциональная. И помогло?