Главное Авторские колонки Пресс-релизы Промо Вакансии Вопросы
109 0 В избр. Сохранено
Авторизуйтесь
Вход с паролем

База обновилась, дашборд — нет: как один кэш два часа показывал вчерашние данные

База уже содержала свежие данные, SQL возвращал правильную цифру, но дашборд ещё два часа показывал старую. Я прошёл весь путь метрики от источника до экрана и обнаружил несколько независимых кэшей, каждый из которых работал правильно, но вместе они задерживали данные.
Мнение автора может не совпадать с мнением редакции

Аннотация

В базе данные обновлялись вовремя, SQL возвращал правильные значения, ETL завершался без ошибок, но руководители ещё несколько часов видели старые цифры на дашборде. Я разобрал цепочку от базы до BI и нашёл причину в нескольких слоях кэширования. Ни один из них отдельно не был сломан.

Проблема выглядела почти нелепо. В 10:05 база уже содержала свежие данные. В 10:07 контрольный SQL-запрос возвращал правильную выручку. В 10:10 витрина тоже была обновлена. А на дашборде до полудня продолжала жить вчерашняя цифра.

Сначала подозрение естественно упало на ETL. Потом — на задержку реплики. Затем на сам BI. Но каждый отдельный компонент работал так, как был настроен.

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

Сначала пришлось разделить свежесть данных и свежесть интерфейса

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

Между базой и пользователем у нас было несколько слоёв: основная БД, read replica, аналитическая витрина, BI-сервис, внутренний кэш запросов и уже затем браузер пользователя. Каждый слой имел собственную логику обновления.

Я начал с простой временной шкалы одного показателя.

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

Формально максимальная задержка должна была составлять около пяти-шести минут.

Фактически получалось до двух часов.

Это уже означало, что проблема находится не в скорости загрузки, а где-то после неё.

Для разбора я стал фиксировать четыре момента:

  • когда значение появилось в исходной базе;
  • когда оно появилось в аналитической витрине;
  • когда BI впервые начал возвращать новое значение;
  • когда пользователь реально увидел его на экране.

Это сразу отделило backend freshness от visual freshness.

И оказалось, что они живут почти независимо.

Первый кэш нашёлся внутри BI

Для проверки я взял конкретный показатель — дневную выручку.

В 10:00 база содержала условные 8,4 млн рублей. В 10:05 после очередной загрузки витрина тоже показывала 8,4 млн. Прямой запрос к витрине возвращал новое значение.

Но BI продолжал показывать 7,9 млн.

Я открыл сам запрос, который лежал под визуализацией, и выполнил его напрямую.

Результат был правильным.

Тогда стало понятно: старое значение живёт уже не в данных, а выше.

У BI был включён query cache с TTL 60 минут. Если пользователь запрашивал тот же набор данных в пределах часа, система могла вернуть сохранённый результат вместо повторного выполнения SQL.

Сам механизм вполне разумный.

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

Проблема была в другом: TTL никак не был связан с обновлением витрины.

Получалось:

витрина обновилась в 10:05;

кэш BI был создан в 09:58;

он жил до 10:58.

В течение почти часа BI совершенно корректно отдавал старый результат.

Кэш не сломался.

Он сделал ровно то, что ему разрешили.

После отключения кэша проблема почему-то осталась

На этом месте я уже был уверен, что всё найдено.

Уменьшили TTL до десяти минут, очистили кэш, обновили страницу.

Цифра стала правильной.

На следующий день ситуация повторилась, только уже у части пользователей.

Это была самая неприятная часть расследования. Когда причина вроде найдена, но симптом возвращается в другой форме.

Я начал сравнивать ответы для разных пользователей.

Один человек видел новое значение.

Другой — старое.

Третий после полной перезагрузки страницы получал новое.

Так обнаружился второй слой — кэширование уже на уровне фронтенда и браузера.

Дашборд загружал данные через API. Ответ API кэшировался промежуточным прокси примерно на 30 минут. При этом разные URL для некоторых фильтров считались разными ключами кэша.

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

Один ключ был создан после обновления витрины.

Другой — до.

Именно поэтому проблема выглядела случайной.

Один показатель в итоге имел четыре разных возраста

После этого я решил посчитать не только значение метрики, но и её возраст на каждом уровне.

Получилась довольно показательная картина.

Допустим, в 11:20 пользователь открывает отчёт.

Исходная база обновилась в 11:19.

Реплика — в 11:19:15.

Витрина — в 11:15.

BI-кэш создан в 10:52.

API-кэш создан в 11:03.

Самая свежая система содержит данные возрастом одну минуту.

Самый старый слой — почти полчаса.

При этом пользователь видит только итоговую цифру и никак не может понять, откуда она приехала.

Вот здесь я впервые сформулировал для себя полезное правило: у дашборда должна быть не только метрика, но и timestamp свежести.

Не когда пользователь открыл страницу.

А когда были получены данные, из которых рассчитан показатель.

Если этого нет, отличить старое значение от свежего практически невозможно.

Пример, где старый кэш мог привести к реальному неправильному решению

Сам по себе лаг на час кажется неприятным, но не катастрофическим.

Проблема начинается, когда на основе дашборда принимается действие.

Представим рекламную кампанию с дневным бюджетом 1 млн рублей.

К 14:00 фактические расходы уже достигли 920 тысяч, а конверсия резко просела.

Свежая витрина это показывает.

Но дашборд из-за кэша отображает данные на 13:00:

расходы 710 тысяч;

конверсия в норме;

остаток бюджета выглядит безопасным.

Менеджер смотрит на отчёт и не снижает ставки.

За следующий час расход уходит за дневной лимит.

Формально ни рекламный кабинет, ни база, ни BI не дали неверных данных.

Система просто показала правильные данные из прошлого.

Для операционного управления это почти то же самое, что неправильные данные.

Именно поэтому freshness должна быть частью качества аналитики, а не технической деталью инфраструктуры.

TTL сам по себе не решает проблему

После первого инцидента было большое желание просто поставить маленький TTL везде.

Например, одну минуту.

Это быстро приводит к другой крайности.

Если отчёт тяжёлый, BI начинает постоянно пересчитывать одни и те же запросы. Нагрузка на базу растёт. Пользователи получают более медленные страницы. Реплика может начать отставать уже из-за аналитического трафика.

То есть можно победить старые данные и одновременно создать новую проблему.

Нам пришлось разделить дашборды по требованиям к свежести.

Операционные отчёты, где решения принимаются в течение дня, получили короткий TTL.

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

Исторические отчёты вообще почти не требовали частого пересчёта.

Я стал смотреть на TTL как на бизнес-параметр.

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

Что я теперь проверяю для каждого кэша

  • что является ключом кэша;
  • сколько живёт значение;
  • что происходит после обновления источника;
  • есть ли принудительная инвалидизация;
  • может ли пользователь получить разные версии одной метрики;
  • виден ли timestamp данных;
  • что произойдёт при промахе кэша;
  • можно ли безопасно полностью отключить кэш на время диагностики.

Раньше я в основном смотрел на TTL.

Теперь понимаю, что этого мало.

Главный вопрос — кто и когда инвалидирует старое значение.

Самой слабой частью оказалась инвалидизация

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

У нас этого не происходило.

ETL завершался сам по себе.

BI ничего об этом не знал.

API-кэш тоже.

Каждый слой жил по таймеру.

Получалась полностью асинхронная система, где свежесть определялась совпадением нескольких независимых часов.

Мы изменили схему.

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

Не весь кэш целиком.

Только ключи, связанные с обновившимся набором данных.

Это оказалось важнее простого уменьшения TTL.

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

Кэш уже логически устарел.

Я добавил freshness SLA вместо абстрактного требования актуальности

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

Свежие — это сколько?

30 секунд?

5 минут?

Час?

Мы ввели простое требование для каждого класса отчётов.

Например:

операционный дашборд — не старше 10 минут;

маркетинговая сводка — не старше 30 минут;

финансовый отчёт — обновление раз в сутки после закрытия периода.

После этого уже можно измерять нарушение.

Если витрина обновилась в 10:00, а пользователь в 10:25 всё ещё видит старую версию при SLA 10 минут, это конкретный инцидент.

Без такой границы спор быстро превращается в субъективный.

Для контроля я добавил отдельную метрику:

Data Age = Current Time − Source Data Timestamp

Если Data Age превышает допустимый порог, отчёт считается устаревшим даже в том случае, если технически все сервисы доступны.

Ошибка оказалась не в кэше, а в архитектуре свежести

Сам по себе кэш в этой истории не был проблемой.

Без него тяжёлые отчёты работали бы хуже.

Проблемой было отсутствие общей модели свежести.

Каждый компонент отвечал только за свой участок.

ETL гарантировал обновление витрины.

BI гарантировал быстрый ответ.

API гарантировал снижение нагрузки.

Браузер старался не загружать лишнее.

Все локально оптимизировали систему.

А пользователь хотел одного: увидеть актуальную цифру.

Это хороший пример того, как локально правильные решения вместе создают неправильный результат.

Как я теперь разбираю подобные сбои

После этого кейса я перестал начинать диагностику с вопроса, где сломались данные.

Сначала строю путь конкретной метрики.

Источник.

Реплика.

Витрина.

BI.

API.

Браузер.

И на каждом этапе фиксирую не только значение, но и timestamp.

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

Если timestamp свежий, но цифра старая — нужно проверять сам расчёт.

Если и цифра, и timestamp старые — искать слой, который удерживает предыдущую версию.

Второй полезный приём — полностью отключить кэш на время проверки.

Не как постоянное решение.

А как диагностический эксперимент.

Если без кэша проблема исчезает, цепочка поиска резко сужается.

Вывод

В этом случае база обновлялась правильно.

ETL тоже.

SQL возвращал правильные данные.

BI не падал.

API отвечал без ошибок.

И всё равно пользователь видел устаревшую цифру.

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

Главный вывод для меня оказался довольно простым: актуальность аналитики нельзя проверять только на уровне базы.

Нужно проверять весь путь до экрана.

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

какие данные лежат под цифрой;

когда они были получены;

через какие кэши прошли до пользователя.

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

А иногда — ещё хуже: достаточно правдоподобной, чтобы на её основе приняли неправильное решение.

0
В избр. Сохранено
Авторизуйтесь
Вход с паролем