Небольшая заметка о кэшировании. Для начала тезис:

Кэш — это лекарство, которое подчас опасней болезни. Пользоваться им нужно аккуратно, а еще лучше — не попадать в ситуацию, когда он нужен.

Добавление кэша в проект несет много проблем. Например, на сколько его устанавливать? Если данные обновились, как его сбросить? Кажется, что ответить просто: давай на час, плюс добавим хук в 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 мы выкатили новый проект. Главная страница была тяжелой, потому что собирала данные из десяти сервисов, плюс было несколько языков. Всю страницу завернули в кэш, но не учли, что в ключе не было текущей локали. Вышло так, что первым посетителем был кто-то из Чехии. Декоратор посчитал страницу для чешского языка и положил в кэш. В течение часа посетители видели страницу на чешском. Кнопку для экстренного сброса кэша никто не предусмотрел. И кстати — такая кнопка нужна, и она тоже требует затрат.

Резюмируя: лучший кэш — это его отсутствие. Если у вас нет кэшей в проекте — искренне поздравляю. Это сильно лучше той ситуации, когда кто-то его затянет.