Кэширование
Небольшая заметка о кэшировании. Для начала тезис:
Кэш — это лекарство, которое подчас опасней болезни. Пользоваться им нужно аккуратно, а еще лучше — не попадать в ситуацию, когда он нужен.
Добавление кэша в проект несет много проблем. Например, на сколько его устанавливать? Если данные обновились, как его сбросить? Кажется, что ответить просто: давай на час, плюс добавим хук в ORM, чтобы после сохранения модели сбрасывался такой-то ключ.
На практике все сложнее. Данные могут поступать не только через ORM, но и пакетным импортом или вставкой в таблицу из скрипта. Легко прозевать сброс ключа, и клиент увидит старые данные. Можно оказаться в ситуации, когда кэши будут зависимы: один строится на другом, и какой сбрасывать — не ясно.
Помню, когда программировал на Python и Django, считалось нормой обмазываться кэшами на каждый чих. Фреймворк всячески поощрял это: были различные бэкенды кэша, декораторы, которые навесил на функцию — и готово. У кэшей даже были версии, представляете? С ними отстрелить ногу было еще проще.
По наивности разработчики думают, что если запрос к базе медленный, то его нужно кэшировать. Это крайне наивно. База данных для того и создана, чтобы выполнять запросы. Современные базы могут выполнять одновременно много, очень много запросов. Например, в текущем проекте база, к которой я изрядно приложил руку, держит 300 транзакций в секунду — а внутри огромные JSON-документы.
Вот что можно предположить, если база отвечает медленно:
- не используется пул соединений
- на каждый запрос открывается новое соединение
- запрос делает полное сканирование таблицы (нет индекса)
- индекс есть, но из-за кривого where он не работает
- разработчик делает 100500 get-by-id в цикле
- в запрос передают 65К параметров вместо массива или json
- неотимальная вставка, например insert в цикле вместо copy from
Ни один из этих пунктов не рассосется, если добавить кэш. Поэтому сначала нужно разобраться, что идет не так. А когда разобрались, проще исправить на месте, чем добавлять кэш.
Разумеется, бывают очень сложные запросы, которые нельзя выполнить быстро в силу их природы. Но погодите со своим кэшем: такой запрос легко материализовать в таблицу и время от времени обновлять. На таблицу можно повесить индекс и выбирать из нее. Выходит, кэш легко организовать в базе.
В широком смысле сетевой кэш — это вторая база данных. Если вы не справились с одной базой, с чего вы решили, что с двумя будет проще?
Какие еще проблемы кэширования можно назвать? Да хотя бы следующие.
Сериализация. Когда вы пишете данные в Memcache, как они сериализируется? Если это json, вы теряете даты и типы коллекций, например, множество становится списком. Если у вас словарь с целыми числами в ключах, это тоже станет проблемой: обратно вы получите строки. Хорошо, когда есть библиотека для бинарной сериализации. Но если один и тот же кэш читают в разных языках, это станет проблемой: вряд ли такая библиотека есть под все языки (кроме protobuf).
Прогрев. Пока кэш актуален, все хорошо. Но когда тысячи клиентов начнут обновлять его, кэш-серверу станет плохо. Нужен фоновый процесс, который обновит кэш в фоне за несколько минут до того, как он протух. Кто будет следить за этим процессом?
Транзакционность. В одном проекте мы так увлеклись кэшами, что словили глюк,
когда несколько клиентов обновляли один и тот же ключ. Чтобы это разрулить,
ведущий разработчик написал джанговский Cache с подобием транзакции. Когда
кто-то хотел прочитать ключ foo_bar
, код проверял, есть ли ключ
foo_bar__lock
. Если да, это значило, что в этот момент кто-то обновляет ключ
foo_bar
, и в доступе отказывали. Это был костыль поверх костыля, но мы очень
гордились.
Полнота. Другой пример, о котором писал в комментариях. Когда-то в Wargaming мы выкатили новый проект. Главная страница была тяжелой, потому что собирала данные из десяти сервисов, плюс было несколько языков. Всю страницу завернули в кэш, но не учли, что в ключе не было текущей локали. Вышло так, что первым посетителем был кто-то из Чехии. Декоратор посчитал страницу для чешского языка и положил в кэш. В течение часа посетители видели страницу на чешском. Кнопку для экстренного сброса кэша никто не предусмотрел. И кстати — такая кнопка нужна, и она тоже требует затрат.
Резюмируя: лучший кэш — это его отсутствие. Если у вас нет кэшей в проекте — искренне поздравляю. Это сильно лучше той ситуации, когда кто-то его затянет.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter