Сбор метрик производительности позволяет анализировать поведение клиентского кеша, скорость выполнения запросов, частоту повторных обращений к серверу, объём сетевой активности и реакцию интерфейса на изменения состояния данных. В крупных приложениях TanStack Query становится центральным механизмом взаимодействия с API, поэтому деградация производительности напрямую влияет на UX, нагрузку на backend и потребление памяти.
Метрики необходимы для решения нескольких задач:
Наиболее базовая метрика — длительность выполнения
queryFn.
const start = performance.now()
const data = await fetch('/api/users')
const end = performance.now()
console.log(`Запрос занял ${end - start}ms`)
В TanStack Query подобные измерения обычно внедряются централизованно внутри HTTP-клиента.
Позволяет определить:
Пример счётчика:
let requestCounter = 0
async function api(url) {
requestCounter++
return fetch(url)
}
Одна из важнейших метрик.
Высокий показатель cache hit означает:
Низкий показатель указывает на:
Анализируется:
Позволяет обнаружить:
TanStack Query предоставляет доступ к внутреннему кешу через
QueryClient.
const queryClient = new QueryClient()
Через queryCache можно подписываться на изменения
состояния запросов.
const unsubscribe = queryClient
.getQueryCache()
.subscribe((event) => {
console.log(event)
})
События содержат:
Пример анализа состояния:
queryClient
.getQueryCache()
.subscribe((event) => {
const query = event.query
console.log({
key: query.queryKey,
status: query.state.status,
fetchStatus: query.state.fetchStatus,
dataUpdatedAt: query.state.dataUpdatedAt,
errorUpdatedAt: query.state.errorUpdatedAt
})
})
Наиболее правильный подход — измерение внутри API-слоя.
async function request(url, options) {
const start = performance.now()
const response = await fetch(url, options)
const end = performance.now()
console.log({
url,
duration: end - start
})
return response.json()
}
useQuery({
queryKey: ['users'],
queryFn: () => request('/api/users')
})
Retry могут существенно увеличивать нагрузку.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
retry: 3
})
Для анализа retry полезно фиксировать:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError(error) {
console.log(error)
}
}
}
})
Чрезмерные ререндеры — распространённая проблема.
Причины:
Для анализа ререндеров используется React Profiler.
<Profiler
id="UsersPage"
onRen der={(id, phase, duration) => {
console.log({
id,
phase,
duration
})
}}
>
<UsersPage />
</Profiler>
TanStack Query выполняет фоновые обновления автоматически.
Важно измерять:
queryClient
.getQueryCache()
.subscribe((event) => {
const state = event.query.state
console.log(state.fetchStatus)
})
Возможные значения:
idlefetchingpausedНеправильно подобранный staleTime приводит либо к
устаревшим данным, либо к чрезмерному количеству запросов.
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
staleTime: 1000 * 60 * 5
})
При сборе метрик оценивают:
gcTime влияет на объём памяти.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
gcTime: 1000 * 60 * 30
})
Слишком большое значение приводит к:
const queries = queryClient
.getQueryCache()
.findAll()
console.log(queries.length)
TanStack Query не предоставляет размер памяти напрямую, но можно вычислять приблизительный объём.
const size = JSON.stringify(
queryClient.getQueryData(['users'])
).length
Чрезмерный invalidate — частая причина проблем производительности.
queryClient.invalidateQueries({
queryKey: ['users']
})
Полезно логировать:
const originalInvalidate =
queryClient.invalidateQueries.bind(queryClient)
queryClient.invalidateQueries = async (...args) => {
console.log('invalidateQueries', args)
return originalInvalidate(...args)
}
Infinite Query требуют отдельного мониторинга.
Проблемы:
const data = queryClient.getQueryData(['feed'])
console.log(data.pages.length)
Prefetch может незаметно генерировать большое количество запросов.
queryClient.prefetchQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Важно измерять:
Browser Performance API подходит для глубокой диагностики.
performance.mark('users-fetch-start')
await fetch('/api/users')
performance.mark('users-fetch-end')
performance.measure(
'users-fetch',
'users-fetch-start',
'users-fetch-end'
)
const entries =
performance.getEntriesByName('users-fetch')
console.log(entries)
Метрики часто отправляются в:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError(error) {
Sentry.captureException(error)
}
}
}
})
async function request(url) {
const start = performance.now()
const response = await fetch(url)
const duration =
performance.now() - start
if (duration > 2000) {
Sentry.captureMessage(
`Slow request: ${url}`
)
}
return response.json()
}
OpenTelemetry позволяет строить полноценный distributed tracing.
Обычно собираются:
const tracer = trace.getTracer('frontend')
const span = tracer.startSpan('users-query')
span.end()
При SSR и hydration важно анализировать:
const start = performance.now()
hydrate(queryClient, dehydratedState)
const end = performance.now()
console.log(end - start)
TanStack Query Devtools позволяют анализировать:
Крупные приложения часто используют собственный metrics layer.
class QueryMetrics {
requests = 0
errors = 0
cacheHits = 0
cacheMisses = 0
logRequest() {
this.requests++
}
logError() {
this.errors++
}
logCacheHit() {
this.cacheHits++
}
logCacheMiss() {
this.cacheMisses++
}
getStats() {
return {
requests: this.requests,
errors: this.errors,
cacheHits: this.cacheHits,
cacheMisses: this.cacheMisses
}
}
}
const metrics = new QueryMetrics()
async function request(url) {
metrics.logRequest()
try {
const response = await fetch(url)
return response.json()
} catch (error) {
metrics.logError()
throw error
}
}
Иногда требуется бизнес-аналитика.
Например:
Даже быстрые запросы могут вызывать лаги интерфейса.
Причины:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
select(data) {
const start = performance.now()
const result = heavyTransform(data)
const end = performance.now()
console.log(end - start)
return result
}
})
Полезно отслеживать:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
networkMode: 'offlineFirst'
})
Большое количество observers увеличивает нагрузку.
queryClient
.getQueryCache()
.findAll()
.forEach((query) => {
console.log(
query.getObserversCount()
)
})
Иногда несколько компонентов инициируют одинаковые query с разными ключами.
Плохой пример:
['user', 1]
['users', 1]
['profile', 1]
Фактически это могут быть одинаковые данные, но TanStack Query создаст разные кеш-записи.
Большие объёмы данных могут тормозить:
const start = performance.now()
JSON.stringify(data)
const end = performance.now()
console.log(end - start)
TanStack Query влияет на:
Особенно заметно это при:
Плохая архитектура:
const user = useQuery(...)
const posts = useQuery(...)
const comments = useQuery(...)
Если запросы зависят друг от друга, возникают последовательные задержки.
Сбор метрик сам по себе не должен создавать нагрузку.
Обычно применяются:
function shouldSample(rate = 0.1) {
return Math.random() < rate
}
const queue = []
function pushMetric(metric) {
queue.push(metric)
}
setInterval(() => {
if (!queue.length) {
return
}
sendMetrics(queue)
queue.length = 0
}, 5000)
Может приводить к:
Плохой пример:
onSuccess(data) {
heavyAnalytics(data)
}
Тяжёлая аналитика блокирует главный поток.
JSON размером в несколько мегабайт способен существенно замедлить приложение.
Отправка каждой метрики отдельным запросом создаёт дополнительную нагрузку.
Обычно используется следующая схема:
const metrics = {
requests: 0,
errors: 0,
slowRequests: 0
}
async function api(url) {
metrics.requests++
const start = performance.now()
try {
const response = await fetch(url)
return await response.json()
} catch (error) {
metrics.errors++
throw error
} finally {
const duration =
performance.now() - start
if (duration > 1000) {
metrics.slowRequests++
}
}
}