Привет, Хабр. Меня зовут Любовь Вайгель. Я системный аналитик в СберЗдоровье — MedTech-компании №1 в России.

Во многих сценариях, сталкиваясь со снижением скорости ответа системы, компании приходят к необходимости внедрения кэша и довольно быстро реализуют кэширование на уровне технологий. Но правда в том, что кэширование, которое часто воспринимают как простой тактический приём для ускорения работы приложения, на практике может сильно изменить свойства системы: согласованность данных, поведение при сбоях, требования к актуальности и доступности информации. Поэтому для системного аналитика важно четко понимать, что мы кэшируем, зачем, на каких условиях и как это будет отражено в требованиях.

В этой статье я разберу пять неочевидных истин о кэшировании, которые часто остаются «за кадром», и покажу, как они влияют на работу системного аналитика.

1. Кэш — это распределённая система

Основная ошибка — воспринимать кэш как изолированный вспомогательный компонент. Как только в системе появляется кэш, у вас становится как минимум два источника данных:

  • основной источник истины — обычно база данных;

  •  кэш.

То есть система начинает вести себя как распределённая. В результате появляется необходимость решать вопросы согласованности данных, порядка операций и распространения изменений. Именно поэтому простая на первый взгляд задача, такая как инвалидация кэша, быстро превращается в нетривиальную.

Например, вопрос «как кэш узнаёт, что данные изменились?» — это не техническая мелочь, а архитектурное решение, ведь возможны разные варианты, в том числе:

  •  инвалидация по событию;

  •  обновление по расписанию;

  •  инвалидация по TTL;

  •  комбинация подходов.

Для системного аналитика это означает простую вещь: в требованиях должна быть зафиксирована стратегия согласования данных между источником истины и кэшем. Если этого нет, поведение системы останется неуправляемым, а проблемы всплывут уже в продакшене.

Исходя из этого, в требованиях важно зафиксировать: 

  •  какие данные считаются источником истины;

  •  когда и по какому событию кэш обновляется или инвалидируется;

  •  что происходит, если кэш и основной источник временно расходятся.

2. Кэш ускоряет чтение, но усложняет согласованность данных

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

Причем расхождение может привести к разным последствиям, например:

  •  пользователь видит устаревшие данные;

  •  разные пользователи получают разные версии одной и той же информации;

  •  система принимает решение на основе данных, которые уже изменились в источнике истины.

Важно отметить, что подобное расхождение не всегда критично — допустимость использования устаревших данных напрямую зависит от бизнес-контекста:

  •  для каталога товаров устаревшее описание или рейтинг на несколько минут обычно терпимы;

  • для цены, остатка на складе, финансового баланса или медицинских данных требования к актуальности должны быть намного строже.

Поэтому задача системного аналитика — не просто проработать кэширование, а перевести этот компромисс согласованности в измеримые требования:

  •  насколько свежими должны быть данные;

  •  какая задержка обновления допустима;

  •  в каких сценариях устаревшие данные недопустимы;

  •  что пользователь или система должны увидеть, если данные могут быть неактуальны.

На практике это чаще всего выражается через требования к TTL, допустимому возрасту данных и сценариям, где кэш использовать нельзя.

3. Отказ кэша может обрушить всю систему

Существует распространённый миф: кэш — это необязательный компонент, и если он откажет, система просто станет медленнее.

В реальности в высоконагруженных системах отказ кэша может привести к полному падению приложения. Подтверждением этому является классический сценарий — thundering herd problem, при котором кэш становится недоступным или очищается и, как следствие:

  •  множество параллельных запросов одновременно не находят данные в кэше;

  •  все они идут напрямую в основной источник данных;

  •  база данных получает лавинообразную нагрузку и тоже выходит из строя.

С технической стороны эту проблему часто решают с помощью паттерна Singleflight или аналогичных механизмов дедупликации запросов. Аналитику не обязательно выбирать конкретный механизм реализации, но важно зафиксировать требуемое поведение системы. Например:

  •  одновременные запросы к одним и тем же данным не должны мультиплицировать нагрузку на источник истины;

  •  при недоступности кэша система должна деградировать предсказуемо, а не создавать каскадный отказ;

  •  критичные пользовательские сценарии должны иметь описанное поведение при отказе кэша.

Кроме того, требования к надёжности должны явно учитывать сценарий отказа кэша:

  •  как система деградирует;

  •  что происходит с пользовательскими запросами;

  •  какие компоненты считаются критичными;

  •  какие метрики и алерты нужны, чтобы заметить проблему до массового инцидента.

4. Иногда устаревшие данные лучше, чем их отсутствие

Это контринтуитивная, но важная идея: в ряде сценариев доступность важнее абсолютной актуальности.

Хороший пример — пользовательские ленты, списки, каталоги. Если сервис профилей временно недоступен, пустые блоки вместо имён и аватаров создают ощущение «сломанной системы», даже если формально данные просто не загрузились. Поэтому в таких случаях лучше показать немного устаревшие данные, чем не показать ничего.

Для этого используют резервное использование устаревшего кэша в пределах допустимого окна:

cacheTtl — время, в течение которого данные считаются актуальными;

fallbackTtl — время, в течение которого устаревшие данные допустимы как резервный вариант.

Если основной источник недоступен, система продолжает работать, отдавая данные из кэша, пока их возраст не превысил fallbackTtl.

Для аналитика это напрямую связано с требованиями к доступности и отказоустойчивости: где мы готовы пожертвовать свежестью данных ради пользовательского опыта, а где — нет.

Исходя из этого, в требованиях важно зафиксировать: 

  •  можно ли показывать устаревшие данные;

  •  какой максимальный возраст таких данных допустим;

  •  нужно ли сообщать пользователю, что данные могут быть неактуальны;

  •  для каких сценариев fallback запрещён.

5. Кэшировать нужно не самые «дорогие», а подходящие данные

Часто можно услышать: «давайте кэшировать самые тяжёлые запросы». Стоимость получения данных действительно важна, но это не единственный критерий.

Хороший кандидат для кэширования обычно:

  •  часто читается;

  •  редко изменяется;

  •  дорог в получении, например из-за сложных запросов или внешних API;

  •  невелик по объёму;

  •  имеет понятные правила актуализации.

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

Поэтому в требованиях аналитик должен фиксировать не абстрактное «реализовать кэширование», а конкретный перечень данных или типов данных, которые подлежат кэшированию, и обоснование этого выбора.

Чек-лист требований к кэшированию для системного аналитика

Таким образом, перед тем как согласовывать кэширование, стоит ответить на ряд вопросов:

  •  Какие данные кэшируем?

  •  Где находится источник истины?

  •  Какой допустимый возраст данных?

  •  Что инвалидирует кэш?

  •  Что происходит при недоступности кэша?

  •  Можно ли отдавать устаревшие данные?

  •  Как система защищает источник истины от лавинообразной нагрузки?

  •  Какие метрики покажут, что кэш работает неправильно?

При этом ответы на эти и другие вопросы можно упаковать в полный и информативный набор требований. Например:

Требование к TTL. Система должна кэшировать данные профиля пользователя на 15 минут. По истечении TTL при первом запросе данные должны обновляться из основного хранилища.

Требование к инвалидации. При изменении пользователем аватара соответствующая запись в кэше должна быть немедленно инвалидирована.

Требование к отказоустойчивости. В случае недоступности основной базы данных система должна предоставлять данные профилей из кэша, если их возраст не превышает 60 минут.

Функциональное требование. В кэш должны помещаться следующие данные: профиль пользователя, список друзей, каталог товаров.

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

Такие формулировки позволяют избежать двусмысленностей и заранее определить поведение системы в критических сценариях.

Заключение

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

Именно поэтому без системного аналитика, который зафикисирует условия, ограничения и компромиссы, внедрить кэширование без ущерба всей системе в перспективе практически невозможно. При этом, для удобства работы с кэшем и учета всех ключевых аспектов, системные аналитики могут опираться на чек-лист, доступный по ссылке.

Если посмотреть на ваши текущие требования, какие из них стоило бы пересмотреть с учётом этих неочевидных истин?