Como a Zepto reduziu a memória dos pods Next.js de ~5 GB para ~1,2 GB ao conter um timer do react-query
Aprofundamento CEVIU
Aprofundamento
O problema da Zepto não era um vazamento clássico. No SSR, o react-query v3 cria uma Query para cada useQuery renderizado e dispara na hora um timer de expiração de cache (Query.scheduleGc), de 2 a 5 minutos. No navegador isso faz sentido, porque o timer é cancelado quando um componente assina a query. No servidor a assinatura acontece dentro de useEffect, que não roda durante o SSR. O timer corre até o fim, e o QueryClient que ele limparia já foi descartado.
O estrago vem do AsyncLocalStorage. O Node copia o store ativo para cada timer, e o Next.js renderiza cada requisição dentro de um store que referencia request, response e spans do OpenTelemetry. Timer pendente é raiz de GC, então cada requisição finalizada ficava presa até o disparo. A correção foi um cache de servidor que monta todas as queries com cacheTime: Infinity, o que impede o timer de começar. Os pods caíram de cerca de 4,8 GiB para cerca de 1,2 GiB. Na v4 e na v5 o padrão no servidor já é infinito, mas um gcTime finito explícito nos hooks reabre a armadilha. A issue TanStack/query#11320 estava sem correção mergeada em novembro de 2026.
Por que isso importa
Para quem opera Next.js em produção, a lição vale além do react-query: qualquer setTimeout criado durante uma requisição segura essa requisição até disparar. Bibliotecas feitas para o navegador fazem isso o tempo todo, com polling, debounce e retries. Se você usa gcTime próprio nos hooks, atualizar para a v5 não resolve sozinho. E se o autoscaling depende de memória, a correção pode reduzir réplicas e subir latência, então acompanhe esses números depois.
Perguntas frequentes
Por que o react-query mantinha requisições na memória no servidor?
Cada query renderizada no SSR iniciava um timer de cache de 2 a 5 minutos. O Node associa o contexto de AsyncLocalStorage da requisição a esse timer, e timers pendentes são raízes de GC. Assim a requisição inteira, com response e buffers, ficava viva até o timer disparar.
Atualizar para o react-query v5 resolve o problema?
Só em parte. Na v4 e na v5 o padrão de cacheTime/gcTime no servidor é Infinity. Mas se os hooks passam um gcTime finito explícito, o timer real volta a rodar no SSR. Nesse caso é preciso um guard no QueryClient do servidor.
Como a Zepto corrigiu?
Criou um cache para o QueryClient do servidor que monta toda query com cacheTime: Infinity, então o timer nunca começa. O valor não é desfeito pelos hooks, porque o react-query mantém o maior entre o atual e o novo. O navegador continua com o cache padrão.
Por que o autoscaling foi afetado?
Os pods escalavam por CPU e memória, e o vazamento fazia a memória cruzar o limite primeiro. Sem ele, só a CPU disparava o escalonamento, raramente atingida num servidor Node. A frota encolheu e a latência subiu, então a Zepto passou a usar CPU como sinal principal e manteve um limite de memória mais alto como segurança.
Fontes
- rohitpotato.xyzfonte original
- Categoria
- CEVIU Web Dev
- Publicado
- 12 de novembro de 2026
- Editoria
- CEVIU Web Dev

