Garbage collection

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

В основе работы лежит концепция «неактивных запросов». Пока query используется хотя бы одним observer’ом, данные считаются активными и не подлежат удалению. После размонтирования всех компонентов запрос переходит в состояние inactive и начинает ожидать очистки.


Жизненный цикл query

Каждый query внутри Query Cache проходит несколько стадий:

  1. Создание query
  2. Выполнение запроса
  3. Хранение данных в кеше
  4. Использование несколькими компонентами
  5. Переход в inactive
  6. Garbage collection
  7. Полное удаление из памяти

Пример:

const query = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers
})

После первого выполнения:

  • query сохраняется в Query Cache
  • данные становятся доступными всем компонентам
  • запускается управление жизненным циклом

Если компонент размонтируется:

return isVisible ? <Users /> : null

query становится inactive.

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


Что считается неиспользуемым query

Query считается неиспользуемым, если:

  • отсутствуют активные observers
  • ни один компонент не использует useQuery с этим ключом
  • query не участвует в refetch
  • query не удерживается вручную

Пример:

function App() {
    const [page, setPage] = useState('users')

    return (
        <>
            {page === 'users' && <UsersPage />}
            {page === 'posts' && <PostsPage />}
        </>
    )
}

При переключении страницы:

setPage('posts')

query ['users'] перестает использоваться и становится inactive.


cacheTime и gcTime

В TanStack Query v5 параметр cacheTime был переименован в gcTime.

Старый вариант:

cacheTime: 300000

Новый вариант:

gcTime: 300000

Оба значения означают:

сколько миллисекунд inactive query хранится в кеше перед удалением


Принцип работы gcTime

Пример:

const query = useQuery({
    queryKey: ['profile'],
    queryFn: fetchProfile,
    gcTime: 10000
})

Сценарий работы:

Компонент смонтирован

<Profile />

Query активен.

Компонент размонтирован

null

Query становится inactive.

Запускается таймер

TanStack Query запускает внутренний timer:

10 секунд

Если query снова используется

<Profile />

до истечения таймера:

  • garbage collection отменяется
  • данные остаются в кеше
  • сетевой запрос может не выполняться

Если query не используется

через 10 секунд:

  • query удаляется из Query Cache
  • память освобождается
  • все metadata уничтожаются

Значение gcTime по умолчанию

По умолчанию:

gcTime: 5 * 60 * 1000

То есть:

5 минут

Пример эквивалентной настройки:

const queryClient = new QueryClient({
    defaultOptions: {
        queries: {
            gcTime: 300000
        }
    }
})

Разница между staleTime и gcTime

Очень распространенная ошибка — путать staleTime и gcTime.

staleTime

Определяет:

как долго данные считаются свежими

gcTime

Определяет:

как долго inactive query хранится в кеше


Сравнение staleTime и gcTime

staleTime = freshness

staleTime: 60000

Данные считаются свежими 1 минуту.

gcTime = lifetime

gcTime: 300000

Inactive query живет 5 минут.


Визуальный пример

useQuery({
    queryKey: ['settings'],
    queryFn: fetchSettings,
    staleTime: 60000,
    gcTime: 300000
})

Сценарий:

0 сек

Запрос выполнен.

30 сек

Данные свежие.

70 сек

Данные stale, но все еще в кеше.

Компонент размонтирован

Query inactive.

Через 5 минут

Garbage collection удаляет query.


stale query не означает удаление

Даже если query stale:

isStale === true

данные продолжают храниться в памяти.

Garbage collection зависит только от:

  • активности query
  • gcTime

Активные query не удаляются

Даже если gcTime очень маленький:

gcTime: 1000

query не удалится, пока используется компонентом.

Пример:

function Dashboard() {
    useQuery({
        queryKey: ['stats'],
        queryFn: fetchStats,
        gcTime: 1000
    })

    return <div>Dashboard</div>
}

Пока компонент смонтирован:

  • query active
  • garbage collection невозможен

Немедленное удаление query

Можно указать:

gcTime: 0

Пример:

useQuery({
    queryKey: ['temp'],
    queryFn: fetchTempData,
    gcTime: 0
})

После размонтирования:

  • query сразу удаляется
  • кеш не сохраняется
  • повторный mount вызывает новый fetch

Использование gcTime для временных данных

Полезно для:

  • временных экранов
  • одноразовых модальных окон
  • transient data
  • ephemeral state

Пример:

useQuery({
    queryKey: ['wizard-step'],
    queryFn: fetchWizardData,
    gcTime: 0
})

Бесконечное хранение query

Можно полностью отключить garbage collection:

gcTime: Infinity

Пример:

useQuery({
    queryKey: ['countries'],
    queryFn: fetchCountries,
    gcTime: Infinity
})

Такой query:

  • никогда автоматически не удаляется
  • остается в кеше постоянно
  • доступен мгновенно

Когда использовать Infinity

Подходит для:

  • справочников
  • стран
  • языков
  • конфигурации
  • редко изменяемых данных

Пример:

useQuery({
    queryKey: ['app-config'],
    queryFn: fetchConfig,
    staleTime: Infinity,
    gcTime: Infinity
})

Опасности gcTime: Infinity

Проблемы:

  • рост памяти
  • утечки кеша
  • накопление больших payload
  • устаревшие данные
  • деградация производительности

Особенно опасно для:

  • infinite queries
  • больших списков
  • изображений
  • аналитики
  • streaming data

Garbage collection и Query Cache

Все query хранятся внутри QueryCache.

const queryClient = new QueryClient()

Внутри:

QueryCache
 ├── ['users']
 ├── ['posts']
 ├── ['profile']
 └── ...

Garbage collection удаляет записи именно из QueryCache.


Как удаление влияет на повторный mount

Если query удален:

gcTime expired

то при повторном mount:

useQuery({
    queryKey: ['users']
})

TanStack Query:

  • создает новый query
  • выполняет новый fetch
  • повторно записывает данные в кеш

Поведение без удаления

Если query еще в кеше:

gcTime не истек

то:

useQuery({
    queryKey: ['users']
})

может:

  • мгновенно показать старые данные
  • запустить background refetch
  • избежать loading state

Garbage collection и background refetch

Если query inactive:

observer count = 0

background refetch обычно не выполняется.

Garbage collection спокойно удаляет такой query.


Поведение при refetchInterval

Особый случай:

useQuery({
    queryKey: ['notifications'],
    queryFn: fetchNotifications,
    refetchInterval: 5000
})

Если компонент размонтирован:

  • polling останавливается
  • query становится inactive
  • начинается gc timer

Влияние prefetchQuery

Prefetch создает query без observer’ов.

Пример:

queryClient.prefetchQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts
})

После prefetch:

  • query сразу inactive
  • запускается gc timer

Если query не используется:

gcTime expired

данные удаляются.


Удержание prefetched данных

Часто для prefetch увеличивают gcTime:

queryClient.prefetchQuery({
    queryKey: ['products'],
    queryFn: fetchProducts,
    gcTime: 1000 * 60 * 30
})

Это предотвращает быстрое удаление данных до перехода пользователя на страницу.


Garbage collection и SSR

При Server-Side Rendering query могут гидратироваться:

dehydrate(queryClient)

После hydration:

  • query появляются в клиентском кеше
  • lifecycle продолжается уже в браузере
  • gcTime начинает работать на клиенте

Garbage collection и persistence

При использовании persistence:

persistQueryClient(...)

query сохраняются:

  • localStorage
  • IndexedDB
  • AsyncStorage

Однако gcTime все равно продолжает работать.

Если query удален garbage collector’ом:

  • он исчезает и из persistence
  • либо помечается для удаления

Garbage collection и Devtools

В Devtools можно наблюдать:

  • active query
  • inactive query
  • countdown до удаления
  • cache lifetime

Inactive query обычно отображаются отдельно.


Пример полного жизненного цикла

function UsersPage() {
    const query = useQuery({
        queryKey: ['users'],
        queryFn: fetchUsers,
        staleTime: 60000,
        gcTime: 300000
    })

    return (
        <div>
            {query.data?.map(user => (
                <div key={user.id}>
                    {user.name}
                </div>
            ))}
        </div>
    )
}

Этап 1

Компонент монтируется.

Этап 2

Выполняется fetch.

Этап 3

Данные кешируются.

Этап 4

Компонент размонтируется.

Этап 5

Query inactive.

Этап 6

Запускается 5-минутный gc timer.

Этап 7

Если компонент не появился снова — query удаляется.


Ручное удаление query

Garbage collection — автоматический механизм, но query можно удалять вручную.


removeQueries

Пример:

queryClient.removeQueries({
    queryKey: ['users']
})

TanStack Query:

  • немедленно удаляет query
  • очищает кеш
  • уничтожает metadata

removeQueries vs invalidateQueries

invalidateQueries

queryClient.invalidateQueries({
    queryKey: ['users']
})
  • query остается в кеше
  • данные stale
  • возможен refetch

removeQueries

queryClient.removeQueries({
    queryKey: ['users']
})
  • query полностью удаляется
  • кеш исчезает
  • состояние уничтожается

resetQueries

Еще один механизм:

queryClient.resetQueries({
    queryKey: ['users']
})

Он:

  • сбрасывает состояние
  • сохраняет query
  • может запускать refetch

Это не garbage collection.


Memory pressure

В больших приложениях управление garbage collection становится критически важным.

Особенно при:

  • тысячах query
  • infinite scrolling
  • больших response payload
  • offline cache
  • analytics dashboards

Оптимизация памяти

Уменьшение gcTime

gcTime: 60000

Удаление временных query

gcTime: 0

Избирательный persistence

Не все query нужно сохранять.

Нормализация данных

Избегать огромных payload.


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

Слишком большой gcTime

gcTime: Infinity

для всех query.

Результат:

  • переполнение памяти
  • огромный Query Cache

Путаница staleTime и gcTime

Ошибка:

staleTime: 0

не удаляет query.

Данные просто становятся stale.


Слишком маленький gcTime

gcTime: 1000

может приводить к:

  • постоянным повторным fetch
  • потере UX
  • лишним network requests

Рекомендации по настройке

API-справочники

staleTime: Infinity
gcTime: Infinity

Пользовательские данные

staleTime: 60000
gcTime: 300000

Временные данные

gcTime: 0

Infinite queries

gcTime: 60000

для предотвращения переполнения памяти.


Garbage collection внутри архитектуры TanStack Query

Garbage collection тесно связан с:

  • QueryObservers
  • QueryCache
  • notifyManager
  • timeoutManager
  • focusManager
  • onlineManager

Когда observer count становится равным нулю:

observers.length === 0

TanStack Query:

  1. Помечает query как inactive
  2. Запускает gc timer
  3. Ожидает gcTime
  4. Удаляет query из QueryCache

Как TanStack Query экономит память

Библиотека не хранит query бесконечно.

После удаления уничтожаются:

  • data
  • error
  • status
  • timestamps
  • observers
  • retry metadata
  • fetch state

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


Garbage collection и производительность интерфейса

Правильно настроенный gcTime позволяет:

  • уменьшать потребление памяти
  • ускорять навигацию
  • сохранять responsive UI
  • избегать лишних fetch
  • поддерживать баланс между UX и memory usage

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