28 апр. 2025 г.·7 мин чтения

Правила кеширования React Query для операторских экранов по бизнес-риску

Правила кеширования React Query должны соответствовать бизнес-риску на операторских экранах, чтобы команды настраивали `staleTime`, polling и обновление при фокусе без лишних сюрпризов.

Правила кеширования React Query для операторских экранов по бизнес-риску

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

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

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

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

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

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

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

Разделите данные по уровню риска

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

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

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

Иногда ответ — деньги. Иногда — накопившаяся очередь, пропущенное движение товара или проблема с безопасностью. Такие поля заслуживают агрессивного обновления. Цветовая тема или метка последнего обновления — нет.

Задайте допуск по свежести для каждой группы простыми словами. Данные для действий могут обновляться каждые 5–15 секунд. Поддерживающие данные обычно нормальны при 30–60 секундах. Фоновые данные часто можно оставлять свежими 10 минут и дольше.

Позже именно это даст вам staleTime и политику refetch. Число должно идти от влияния на бизнес, а не от догадок.

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

Сохраняйте одно и то же правило для похожих запросов. Если все бейджи с предупреждениями обновляются каждые 10 секунд, кроме одного, который ждёт пять минут, операторы перестают доверять всему экрану. Предсказуемость почти так же важна, как и скорость.

Определите, что значит «достаточно свежо»

«Достаточно свежо» должно означать конкретное число. Если значение может быть устаревшим на 30 секунд и никто не примет неверное решение, так и скажите: 30 секунд. Если позднее обновление может отправить работу не тому человеку, пропустить предупреждение или показать неверный остаток, скажите: 5 секунд или меньше.

Именно здесь команды часто ошибаются. Они принимают поведение по умолчанию, а потом спорят об этом, когда операторы говорят, что экран запаздывает. Лучше задать простой вопрос: что произойдёт, если это значение устарело на 5 секунд, и что произойдёт, если оно устарело на 2 минуты?

Ответ зависит от поля. Живая очередь на панели поддержки может требовать обновления каждые 10 секунд, потому что сотрудники действуют по ней сразу. Сводка по смене может быть устаревшей на 10 минут и всё равно оставаться нормальной, потому что никто не использует её для решений в режиме секунды за секундой.

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

Простая памятка помогает продукту, поддержке и инженерам говорить на одном языке:

  • Статус аварии или инцидента: устаревание 0–5 секунд
  • Активная очередь, остатки или показатели ёмкости: 10–30 секунд
  • Списки задач, которые операторы часто открывают заново: 30–60 секунд
  • Отчёты и сводки: 5–15 минут

Это не магические числа. Это просто понятная отправная точка. Сотрудники поддержки обычно оценивают их быстрее, чем инженеры. Они скажут: «Если этому числу минутa, мы всё ещё можем работать» или «Если этот статус опаздывает на 20 секунд, мы звоним не тому клиенту».

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

Настраивайте параметры запросов шаг за шагом

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

Начните с риск-групп

Начните с staleTime. Задайте один прямой вопрос: если этому значению 30 секунд, что может пойти не так?

Обычно достаточно простого деления:

  • Критичные данные о статусе: staleTime от 0 до 5 секунд
  • Активные рабочие очереди: staleTime примерно 5–15 секунд
  • Контекстные панели и сводки: staleTime примерно 30–120 секунд
  • Справочные данные вроде подписей или списков: staleTime в минутах или часах

Когда staleTime стал понятным, решите, должен ли экран обновляться, когда вкладка снова получает фокус. Включайте refetchOnWindowFocus для запросов, на которые операторы полагаются после переключения между вкладками, например для живых очередей или счётчиков проблем. Для медленных данных его лучше выключить. Иначе каждое переключение вкладки создаёт шум, спиннеры и лишние запросы.

Используйте refetchInterval только для данных, которые люди действительно отслеживают в процессе изменения. Списки задач, инцидентов, заказов или сообщений часто нужно опрашивать. Карточке профиля клиента это обычно не нужно. Если polling включён у каждого запроса, экран кажется беспокойным, а бэкенд делает лишнюю работу почти без пользы.

Честно обрабатывайте записи и ошибки

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

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

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

Простой пример операторского экрана

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

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

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

Для ожидающих задач используйте очень короткий staleTime и стабильный интервал polling. Во многих командах хорошо работают 3–5 секунд. Если очередь движется медленнее, может хватить 10 секунд.

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

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

История и заметки относятся к третьей группе. Агентам они нужны для контекста: что произошло раньше, что сказал клиент, что попробовал другой коллега. Этот контекст важен, но он редко меняется в следующую секунду.

Одна схема, которая подходит многим операторским экранам:

  • Ожидающие задачи: staleTime почти 0, обновление каждые 3–5 секунд
  • Счётчики: обновление после действий create, edit, assign и close
  • Журнал истории: кеш примерно на 5 минут
  • Внутренние заметки: кеш на 10–15 минут, если только сотрудники не правят их часто

Здесь настройки кеша перестают быть технической деталью и начинают соответствовать бизнес-риску. Держите срочную часть экрана свежей. А более медленный контекст — дешёвым и спокойным.

Выбирайте триггеры обновления осторожно

Экран может выглядеть спокойным и при этом быть неверным. Плохое поведение обновлений обычно начинается тогда, когда все триггеры оставлены включёнными по умолчанию, хотя экран и так делает polling каждые несколько секунд.

Когда вкладка получает фокус, задайте один практический вопрос: могло ли что-то важное измениться, пока оператор был не здесь? Если на странице показаны статусы диспетчеризации, остатки на складе или очереди предупреждений, обновление при фокусе часто имеет смысл. Если экран уже делает polling каждые 5 секунд, refetchOnWindowFocus может добавить нагрузку без особой пользы.

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

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

Для фоновых вкладок тоже нужны другие правила. Если экран не виден, полноценный polling обычно напрасно расходует ресурсы. Замедлите его или поставьте на паузу, если только пользователю не нужно сразу вернуться к актуальной очереди. Для многих операторских экранов polling каждые 2 секунды на переднем плане и каждые 30–60 секунд в фоне — более безопасный компромисс.

Одна ошибка создаёт много путаницы: команды одновременно включают polling, обновление при фокусе и обновление после reconnect для одного и того же горячего запроса. Потом пользователь возвращается на вкладку как раз в момент очередного polling, и приложение отправляет дублирующиеся запросы за одними и теми же данными. Это не делает данные свежее. Это просто усложняет понимание поведения.

Хорошо работает короткий набор правил:

  • Используйте обновление при фокусе для данных, которые могут сильно меняться, пока пользователь отсутствует
  • Используйте обновление после reconnect для данных, которые должны быстро восстановиться после сбоев
  • Добавляйте ручное обновление там, где операторы часто проверяют результат
  • Замедляйте или ставьте на паузу polling в фоновых вкладках
  • Не включайте все триггеры сразу для горячих запросов, если не можете объяснить зачем

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

Частые ошибки

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

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

Другие команды делают обратную ошибку. Они опрашивают низкорисковые данные весь день, потому что боятся пропустить изменения. Это тратит запросы, создаёт шум в логах и повышает вероятность лимитов. Если панель меняется несколько раз за смену, polling каждые 5 секунд — это не осторожность. Это пустая трата ресурсов.

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

Состояния загрузки тоже могут подорвать доверие. Некоторые команды прячут старые, но всё ещё полезные данные за полноэкранным спиннером при каждом refetch. Для операторского экрана это часто хуже, чем показать данные, которые отстают на 20 секунд, но с понятным индикатором обновления. Если таблица исчезает при каждом refetch, люди перестают ей доверять и начинают проверять другой инструмент.

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

Быстрая проверка на запах помогает:

  • Все панели обновляются с одной и той же скоростью
  • Низкорисковые виджеты создают большую часть сетевого трафика
  • Действия пользователя не обновляют ближайшие данные сразу
  • Refetch стирает экран вместо того, чтобы сохранять предыдущие данные
  • Никто не может сказать, когда панель менялась в последний раз

Большинство этих проблем не из-за React Query. Они из-за того, что ко всем запросам относятся так, будто у них одинаковая бизнес-цена.

Что проверить перед релизом

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

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

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

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

Показывайте возраст изменяющихся данных. Пометка вроде «Обновлено 12 секунд назад» убирает догадки. Если одна панель обновляется каждые 10 секунд, а другая — каждые 2 минуты, сделайте это тоже видимым.

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

Держите дорогие запросы в тишине, когда на них никто не смотрит. Если вкладка скрыта, drawer закрыт или панель деталей вне экрана, остановите polling или замедлите его. Нет смысла тратить запросы на данные, на которые никто не может повлиять.

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

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

Что делать дальше

Начните не с кода, а с небольшой таблицы рисков. Разложите все запросы одного операторского экрана по трём группам: низкий риск, неприятно при устаревании и дорого при устаревании. Затем добавьте по одному простому предложению к каждому запросу: что пойдёт не так, если этим данным 30 секунд, 2 минуты или 10 минут.

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

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

Хорошо работает простой процесс:

  • Составьте таблицу рисков до изменения любого параметра запроса
  • Сначала измените один загруженный экран, а не всё приложение
  • Добавьте короткую заметку рядом с каждым запросом, объясняющую, зачем существует его правило обновления
  • Пересматривайте эти заметки после инцидентов, всплесков обращений в поддержку или изменений в процессе

Держите эти заметки рядом с кодом. Короткого комментария вроде «Refetch каждые 15 секунд, потому что по этому счётчику операторы маршрутизируют срочные тикеты» обычно достаточно. Через шесть месяцев такая заметка может уберечь кого-то от «чистки» запроса до дефолта, который выглядит аккуратно, но мешает команде.

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

Если экран влияет на выручку, нагрузку on-call или объём обращений в поддержку, может помочь внешний обзор. Oleg Sotnikov на oleg.is работает как Fractional CTO и startup advisor, и такой разбор операторского потока часто дешевле, чем превращать каждый экран в live feed, который сжигает бюджет.

Правила кеша редко бывают универсальными. Они должны соответствовать цене ошибки.

Часто задаваемые вопросы

Почему стандартные настройки React Query плохо подходят для операторских экранов?

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

Как выбрать правильный staleTime?

Сначала сгруппируйте запросы по бизнес-риску. Если устаревшие данные могут привести к потере денег, пропуску работы или проблеме с комплаенсом, держите staleTime очень коротким. Если данные нужны только для контекста, дайте им более длинное окно кеша и меньше обновлений.

Какие данные нужно обновлять быстрее всего?

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

Нужно ли включать polling для каждого запроса?

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

Когда стоит включать refetchOnWindowFocus?

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

Что должно происходить после mutation вроде assign или close?

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

Стоит ли прятать старые данные за спиннером во время refetch?

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

Нужна ли операторским экранам метка last updated?

Да. Небольшая пометка вроде Updated 12 seconds ago показывает, свежий экран, отстает он или завис. Это убирает догадки и сокращает количество случайных ручных обновлений.

Как background tabs должны обрабатывать refetching?

Замедляйте polling или ставьте его на паузу, когда экран никто не смотрит. Многие команды держат быстрый интервал на переднем плане и гораздо более редкий в фоне. Это экономит запросы и не делает экран устаревшим, когда пользователь возвращается.

Когда вместо polling стоит использовать real-time events?

Используйте polling для большинства изменяющихся данных, а event-based updates добавляйте только там, где задержка мешает бизнесу. Если поле влияет на срочную маршрутизацию, выручку или работу дежурной команды, отправляйте обновления только для этого небольшого набора и оставляйте остальное на обычных правилах кеша. Так вы получите более свежие данные, не превращая весь продукт в live feed.