Мониторинг кеша

TanStack Query предоставляет внутренний кеш как реактивную структуру данных, где каждое изменение состояния запроса, мутации или подписки отражается через события кеша. Мониторинг кеша в этой системе опирается на наблюдение за QueryCache, MutationCache и QueryClient, позволяя фиксировать жизненный цикл данных, диагностировать повторные запросы, отслеживать инвалидирование и анализировать поведение приложения в реальном времени.

Кеш в TanStack Query состоит из нескольких ключевых уровней:

  • QueryCache — хранит все query-объекты
  • MutationCache — хранит состояние мутаций
  • QueryClient — координирует доступ ко всем кешам и глобальным операциям
  • подписчики (listeners) — механизм реактивного наблюдения

Мониторинг кеша всегда начинается с понимания того, что любое изменение query (создание, обновление, инвалидирование) проходит через QueryCache, а любые мутации — через MutationCache.

Подписка на QueryCache

Основной механизм наблюдения за изменениями кеша — подписка на QueryCache.

import { QueryCache } from '@tanstack/query-core';

const queryCache = new QueryCache({
  onError: (error, query) => {
    console.log('Query error:', query.queryKey, error);
  },
  onSuccess: (data, query) => {
    console.log('Query success:', query.queryKey);
  },
});

Однако более низкоуровневый способ мониторинга — использование метода subscribe, который позволяет отслеживать все события изменения состояния query.

const unsubscribe = queryCache.subscribe((event) => {
  console.log('Cache event:', event.type);

  if (event.type === 'upd ated') {
    console.log('Updated query:', event.query.queryKey);
  }

  if (event.type === 'removed') {
    console.log('Removed query:', event.query.queryKey);
  }
});

События могут включать:

  • added — добавление нового запроса
  • updated — обновление состояния запроса
  • removed — удаление из кеша

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

Наблюдение через QueryClient

В реальных приложениях чаще используется QueryClient, который инкапсулирует кеши и предоставляет единый интерфейс доступа:

import { QueryClient } from '@tanstack/query-core';

const queryClient = new QueryClient();

const cache = queryClient.getQueryCache();

cache.subscribe((event) => {
  console.log('Global cache event:', event);
});

Через QueryClient можно также отслеживать мутации:

const mutationCache = queryClient.getMutationCache();

mutationCache.subscribe((event) => {
  console.log('Mutation event:', event.type);
});

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

Жизненный цикл query как объект наблюдения

Каждый query в кеше проходит через несколько состояний:

  • fetching
  • success
  • error
  • stale
  • inactive

Мониторинг этих переходов позволяет выявлять проблемы производительности и избыточные запросы.

cache.subscribe((event) => {
  const query = event.query;

  console.log({
    key: query.queryKey,
    state: query.state.status,
    fetchStatus: query.state.fetchStatus,
    updatedAt: query.state.dataUpdatedAt,
  });
});

Особенно важны поля:

  • status — логическое состояние данных
  • fetchStatus — активность запроса
  • dataUpdatedAt — время последнего обновления

Инвалидация и повторные запросы

Одним из ключевых аспектов мониторинга является отслеживание invalidateQueries.

queryClient.invalidateQueries({
  queryKey: ['users'],
});

С точки зрения кеша это вызывает цепочку событий:

  • пометка query как stale
  • возможный refetch
  • обновление состояния всех подписчиков

Мониторинг этих событий позволяет выявлять:

  • каскадные обновления
  • повторные лишние refetch
  • чрезмерную инвалидизацию

Логирование можно реализовать через глобальную подписку:

queryCache.subscribe((event) => {
  if (event.type === 'updated' && event.query.state.isInvalidated) {
    console.log('Invalidated query refetched:', event.query.queryKey);
  }
});

Отслеживание активности запросов

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

function getActiveQueries(cache) {
  return cache.getAll().filter(q => q.state.fetchStatus === 'fetching');
}

Это позволяет:

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

Также можно отслеживать долгоживущие запросы:

setInterval(() => {
  const active = getActiveQueries(queryClient.getQueryCache());

  active.forEach(query => {
    const duration = Date.now() - query.state.dataUpdatedAt;

    if (duration > 10000) {
      console.warn('Long running query:', query.queryKey);
    }
  });
}, 5000);

Мониторинг MutationCache

MutationCache отражает операции изменения данных на сервере. Его мониторинг критичен для контроля состояния side-effect операций.

mutationCache.subscribe((event) => {
  switch (event.type) {
    case 'updated':
      console.log('Mutation updated:', event.mutation.state.status);
      break;

    case 'added':
      console.log('Mutation started');
      break;

    case 'removed':
      console.log('Mutation finished');
      break;
  }
});

Состояния мутаций:

  • idle
  • pending
  • success
  • error

Мониторинг позволяет выявлять:

  • неудачные операции записи
  • повторные попытки
  • неконсистентность состояния UI

Интеграция с DevTools и внешним логированием

TanStack Query Devtools использует те же механизмы подписки на кеш, предоставляя визуальное отображение состояния всех query и mutation.

Однако в production-среде чаще используется кастомное логирование:

queryCache.subscribe((event) => {
  fetch('/logs', {
    method: 'POST',
    body: JSON.stringify({
      type: event.type,
      queryKey: event.query?.queryKey,
      state: event.query?.state,
    }),
  });
});

Такой подход позволяет строить:

  • централизованную аналитику запросов
  • трассировку производительности
  • аудит поведения кеша

Паттерны мониторинга кеша

Агрегация событий

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

const stats = {
  queries: 0,
  mutations: 0,
  errors: 0,
};

queryCache.subscribe((event) => {
  stats.queries++;

  if (event.query?.state?.status === 'error') {
    stats.errors++;
  }
});

Разделение по ключам

Мониторинг часто требует сегментации по queryKey:

function groupByKey(cache) {
  return cache.getAll().reduce((acc, query) => {
    const key = JSON.stringify(query.queryKey);
    acc[key] = (acc[key] || 0) + 1;
    return acc;
  }, {});
}

Отслеживание “шумных” запросов

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

const frequencyMap = new Map();

queryCache.subscribe((event) => {
  const key = JSON.stringify(event.query.queryKey);

  frequencyMap.se t(key, (frequencyMap.get(key) || 0) + 1);
});

Производственные аспекты мониторинга

Мониторинг кеша напрямую влияет на производительность. Основные ограничения:

  • частые подписки увеличивают нагрузку GC
  • глубокое логирование может замедлять UI
  • сериализация query state дорогая операция

Рекомендуется:

  • использовать throttle/delay для логов
  • фильтровать события по ключам
  • избегать подписок внутри React-компонентов без необходимости

Пример оптимизации:

let timeout;

queryCache.subscribe((event) => {
  clearTimeout(timeout);

  timeout = setTimeout(() => {
    console.log('Batch event processing');
  }, 200);
});

SSR и hydration мониторинг

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

queryClient.hydrate(dehydratedState);

queryCache.subscribe((event) => {
  if (event.type === 'added') {
    console.log('Hydrated query:', event.query.queryKey);
  }
});

Основные метрики:

  • количество гидратированных query
  • время до первого refetch
  • расхождения между сервером и клиентом

Persistence и мониторинг восстановления кеша

При использовании persistence слоя кеш может восстанавливаться из localStorage или IndexedDB.

queryClient.getQueryCache().subscribe((event) => {
  if (event.type === 'added') {
    console.log('Restored from persistence:', event.query.queryKey);
  }
});

Это позволяет отслеживать:

  • устаревшие данные после восстановления
  • пропущенные инвалидизации
  • состояние оффлайн-first стратегий

Практика построения системы наблюдения

Полноценный мониторинг кеша обычно включает несколько слоев:

  • низкоуровневые подписки QueryCache
  • агрегация событий
  • фильтрация по ключам и статусам
  • экспорт метрик в внешние системы

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