TanStack Query активно взаимодействует с удалёнными API, фоновыми обновлениями, повторными запросами, кешированием и механизмами синхронизации состояния. При увеличении нагрузки или усложнении архитектуры появляется необходимость отслеживать:
Интеграция с системами мониторинга позволяет превращать внутренние события TanStack Query в телеметрию приложения.
В типичном приложении мониторинг строится из нескольких уровней:
| Уровень | Назначение |
|---|---|
| Логирование | Запись событий и ошибок |
| Error tracking | Централизованный сбор исключений |
| Performance monitoring | Метрики скорости |
| Tracing | Отслеживание цепочек запросов |
| Analytics | Поведенческий анализ |
| Metrics | Агрегация технических показателей |
TanStack Query предоставляет несколько точек интеграции:
queryFnmutationFnonErroronSuccessonSettledQueryCacheMutationCacheQueryCache позволяет перехватывать события всех запросов
приложения.
import {
QueryClient,
QueryCache
} from '@tanstack/react-query'
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
console.error('QUERY ERROR', {
key: query.queryKey,
error
})
},
onSuccess: (data, query) => {
console.log('QUERY SUCCESS', {
key: query.queryKey
})
},
onSettled: (data, error, query) => {
console.log('QUERY FINISHED', {
key: query.queryKey
})
}
})
})
Подобный уровень перехвата особенно полезен для:
Наиболее распространённый вариант — отправка ошибок запросов в Sentry.
import * as Sentry from '@sentry/react'
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
Sentry.captureException(error, {
tags: {
queryKey: JSON.stringify(query.queryKey)
}
})
}
})
})
Минимальная отправка исключения редко бывает достаточной. Намного полезнее передавать:
onError: (error, query) => {
Sentry.captureException(error, {
tags: {
type: 'tanstack-query',
queryKey: JSON.stringify(query.queryKey)
},
extra: {
state: query.state,
meta: query.meta
}
})
}
Ошибки мутаций обычно критичнее ошибок чтения.
import {
MutationCache,
QueryClient
} from '@tanstack/react-query'
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onError: (error, variables, context, mutation) => {
Sentry.captureException(error, {
tags: {
mutationKey: JSON.stringify(
mutation.options.mutationKey
)
},
extra: {
variables
}
})
}
})
})
Особенно важно отслеживать:
Datadog используется для инфраструктурного мониторинга и performance-аналитики.
function trackQueryDuration(queryKey, duration) {
window.DD_RUM?.addAction('query_duration', {
queryKey,
duration
})
}
Использование:
const fetchUsers = async () => {
const started = performance.now()
const response = await fetch('/api/users')
const finished = performance.now()
trackQueryDuration(
'users',
finished - started
)
return response.json()
}
Частые повторные запросы часто указывают на нестабильность API.
retry: (failureCount, error) => {
window.DD_RUM?.addError(error, {
retryCount: failureCount
})
return failureCount < 3
}
New Relic позволяет анализировать производительность frontend-приложений.
const fetchPosts = async () => {
const start = performance.now()
const response = await fetch('/api/posts')
const data = await response.json()
const duration = performance.now() - start
window.newrelic?.addPageAction(
'tanstack_query_request',
{
endpoint: '/api/posts',
duration
}
)
return data
}
OpenTelemetry становится стандартом observability-инфраструктуры.
import { trace } from '@opentelemetry/api'
const tracer = trace.getTracer('frontend')
async function fetchTodos() {
return tracer.startActiveSpan(
'fetch-todos',
async (span) => {
try {
const response = await fetch('/api/todos')
const data = await response.json()
span.setAttribute(
'http.status_code',
response.status
)
return data
} catch (error) {
span.recordException(error)
throw error
} finally {
span.end()
}
}
)
}
При использовании OpenTelemetry можно формировать единый distributed trace:
Browser
↓
Frontend Query
↓
Gateway API
↓
Microservice
↓
Database
Это позволяет находить:
TanStack Query поддерживает собственный логгер.
import { setLogger } from '@tanstack/react-query'
setLogger({
log: (...args) => {
console.log('[RQ LOG]', ...args)
},
warn: (...args) => {
console.warn('[RQ WARN]', ...args)
},
error: (...args) => {
console.error('[RQ ERROR]', ...args)
}
})
setLogger({
log: () => {},
warn: (message) => {
monitoringService.warn(message)
},
error: (error) => {
monitoringService.error(error)
}
})
Одним из главных показателей считается время выполнения запроса.
async function monitoredFetch(url) {
const started = performance.now()
const response = await fetch(url)
const duration = performance.now() - started
metrics.histogram(
'query.duration',
duration
)
return response.json()
}
Эффективность кеширования напрямую влияет на производительность.
const query = queryClient.getQueryData(['users'])
if (query) {
metrics.increment('cache.hit')
} else {
metrics.increment('cache.miss')
}
queryCache.subscribe((event) => {
if (event.type === 'added') {
metrics.increment('query.created')
}
if (event.type === 'removed') {
metrics.increment('query.removed')
}
})
Фоновые обновления могут создавать скрытую нагрузку.
queryCache.subscribe((event) => {
const query = event.query
if (!query) {
return
}
if (query.state.fetchStatus === 'fetching') {
metrics.increment('background.fetch')
}
})
Некорректная конфигурация staleTime часто приводит
к:
Мониторинг stale-state помогает выявлять подобные проблемы.
queryCache.subscribe((event) => {
const query = event.query
if (!query) {
return
}
const isStale = query.isStale()
monitoring.track('query_state', {
stale: isStale,
key: query.queryKey
})
})
TanStack Query поддерживает offline-first архитектуру.
queryCache.subscribe((event) => {
const query = event.query
if (
query?.state.fetchStatus === 'paused'
) {
monitoring.track('offline_query', {
key: query.queryKey
})
}
})
window.addEventListener('online', () => {
monitoring.track('network_restored')
})
window.addEventListener('offline', () => {
monitoring.track('network_lost')
})
Optimistic update — потенциальный источник рассинхронизации данных.
useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
monitoring.track('optimistic_started')
},
onError: () => {
monitoring.track('optimistic_reverted')
},
onSuccess: () => {
monitoring.track('optimistic_confirmed')
}
})
Большое количество запросов способно привести к росту памяти.
setInterval(() => {
const queries =
queryClient
.getQueryCache()
.getAll()
monitoring.gauge(
'query_cache_size',
queries.length
)
}, 10000)
Слишком большие значения gcTime увеличивают потребление
памяти.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 5
}
}
})
Grafana обычно используется вместе с:
metrics.increment('query.success')
metrics.increment('query.error')
metrics.histogram(
'query.duration',
duration
)
| Метрика | Назначение |
|---|---|
| query.duration | Время запросов |
| query.error.rate | Частота ошибок |
| query.retry.count | Повторные запросы |
| cache.hit.rate | Эффективность кеша |
| mutation.failure.rate | Ошибки мутаций |
| background.refetch.count | Фоновые обновления |
| optimistic.rollback.rate | Откаты optimistic update |
| offline.queue.size | Размер offline-очереди |
Prometheus обычно не взаимодействует напрямую с frontend, однако frontend может отправлять агрегированные данные через backend.
const telemetry = {
queryKey,
duration,
status,
retries
}
navigator.sendBeacon(
'/metrics',
JSON.stringify(telemetry)
)
Технические ошибки не всегда отражают реальное качество UX.
const started = performance.now()
const { data } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts
})
const duration = performance.now() - started
if (isLoading) {
metrics.increment('ui.loading')
}
Высокая частота загрузок часто говорит о:
staleTime;Correlation ID позволяет связывать frontend-ошибки с backend-логами.
async function api(url) {
const correlationId = crypto.randomUUID()
const response = await fetch(url, {
headers: {
'X-Correlation-ID': correlationId
}
})
return response.json()
}
При использовании Axios удобно внедрять мониторинг через interceptors.
import axios from 'axios'
const api = axios.create()
api.interceptors.request.use((config) => {
config.metadata = {
started: performance.now()
}
return config
})
api.interceptors.response.use(
(response) => {
const duration =
performance.now() -
response.config.metadata.started
monitoring.track('request_success', {
duration
})
return response
},
(error) => {
monitoring.track('request_error')
return Promise.reject(error)
}
)
Ошибки TanStack Query можно связывать с глобальной системой UI-ошибок.
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
throwOnError: true
})
Далее ошибка попадает в:
<ErrorBoundary>
<App />
</ErrorBoundary>
Внутри Error Boundary возможно:
В SSR-приложениях важно отслеживать:
const state = dehydrate(queryClient)
const size = JSON.stringify(state).length
monitoring.track('dehydrated_size', {
size
})
Слишком большой dehydrated state увеличивает:
Разные стратегии кеширования могут сравниваться через feature flags.
const staleTime =
featureFlags.newCaching
? 1000 * 60 * 5
: 0
Далее собираются метрики:
Системы мониторинга позволяют формировать автоматические алерты.
| Событие | Условие |
|---|---|
| Высокий error rate | > 5% |
| Долгие запросы | > 3 сек |
| Excessive retries | > 10 retries/min |
| Cache miss spike | Резкий рост |
| Mutation failures | Рост ошибок мутаций |
| Offline queue growth | Увеличение очереди |
onSuccess: () => {
console.log('success')
}
При высокой нагрузке подобное создаёт огромный поток логов.
Недопустимо отправлять:
Плохой вариант:
await monitoring.send()
Мониторинг не должен блокировать UI.
Слишком подробный tracing способен:
Типичная production-связка:
TanStack Query
↓
Axios interceptors
↓
OpenTelemetry
↓
Collector
↓
Grafana / Datadog / New Relic
Дополнительно:
TanStack Query
↓
Sentry
↓
Error Tracking
Подобная архитектура обеспечивает: