---
title: "Meio segundo de tela congelada: a main thread é o gargalo do seu produto de IA"
slug: main-thread-gargalo-produto-ia
date: 2026-10-30
category: "Arquitetura"
tags: ["ia", "llm", "web-workers", "performance", "typescript", "frontend"]
readTime: "3 min"
excerpt: "Rodei a mesma conta na main thread e num Web Worker: a tela congelou até 617 ms de um lado e ficou em 16,8 ms do outro, gastando a mesma CPU. Parsear stream, markdown e JSON de LLM no lugar errado custa experiência e, no fim, token."
url: https://eliseu.dev/blog/main-thread-gargalo-produto-ia
project: main-thread
---

Rodei cinco vezes a mesma conta na minha demo e a tela congelou entre 467 ms e 617 ms em todas. Quando a mesma conta foi para um Web Worker, o pior frame ficou em 16,8 ms. Nenhum modelo envolvido: o travamento era só JavaScript na main thread.

Em produto de IA isso aparece toda hora. A resposta do LLM chega em stream e alguém precisa trabalhar em cima dela: markdown que vira HTML, JSON de tool call, bloco de código para destacar. Esse trabalho cresce com a resposta e roda no mesmo lugar que pinta a tela e responde ao clique.

## O que a demo mede

É o segundo item da série Frontend com IA: [02-main-thread](https://github.com/EliseuSantos/frontend-ia-demos/tree/main/demos/02-main-thread), em TypeScript estrito. Ela tokeniza um texto de 27.719 caracteres 8.000 vezes, com o mesmo `workload.ts` importado pelos dois lados, e uma barrinha animada em cima para mostrar quem trava. [Este link roda a comparação ao abrir](https://eliseusantos.github.io/frontend-ia-demos/02-main-thread/?modo=comparar).

O lado do Worker manda o texto como buffer transferido:

```typescript
const encoded = new TextEncoder().encode(text);
const buffer = encoded.buffer;
worker.postMessage({ buffer, rounds }, [buffer]);
```

Antes do `postMessage` o buffer tinha 27.999 bytes. Depois, 0 na main thread. A memória mudou de dono sem cópia.

Rodei a página publicada cinco vezes no Chrome headless. Main thread: 490 a 629 ms de CPU, pior frame de 467 a 617 ms, uma long task por rodada. Worker: 500 a 590 ms de CPU, pior frame entre 16,7 e 16,8 ms, nenhuma long task. O número muda com a máquina (o card em /projects registra 333 ms numa medição anterior), mas o formato se repete.

## O Worker não acelerou nada

Repara na CPU: igual dos dois lados. O Worker só tira a conta do caminho de quem pinta o frame e trata o clique. Para o web.dev, qualquer tarefa acima de 50 ms é long task, e um INP bom fica em 200 ms ou menos. Meio segundo travado passa disso com folga.

Como Tech Lead, eu decidiria assim:

1. **Medir antes de mover.** A demo conta long tasks com um observer simples, que dá para levar para produção:

```typescript
this.observer.observe({ type: 'longtask', buffered: true });
```

2. **Parse pesado de resposta vai para Worker.** Markdown, highlight e JSON grande saem da main thread quando a medição mostra long task. A tela recebe pronto.
3. **Transferir em vez de copiar** quando o dado é grande.

## Onde isso vira plataforma de IA

Tela congelada parece resposta travada. A reação natural é clicar em gerar de novo ou recarregar a página, e cada nova geração é token pago. Um problema de JavaScript vira linha na conta do provedor.

O formato do stream também é decisão da plataforma. Se o cliente precisa reparsear o markdown inteiro a cada token, o trabalho cresce junto com a resposta. Eu pediria ao time de Engenharia de IA eventos com tipo (texto, tool call, fim de bloco) e JSON de tool call fechado antes de ir para a tela. Contrato de stream bem desenhado deixa o frontend de agentes de IA mais barato de rodar.

A pergunta que eu levaria para a mesa: quando a resposta mais longa do produto chega, quanto tempo a tela fica sem responder ao clique? Se ninguém mediu, a demo está aberta no [repositório frontend-ia-demos](https://github.com/EliseuSantos/frontend-ia-demos), e as outras demos da série estão em [eliseu.dev/projects](https://eliseu.dev/projects).

## Fontes

- [Código da demo 02-main-thread](https://github.com/EliseuSantos/frontend-ia-demos/tree/main/demos/02-main-thread), repositório [frontend-ia-demos](https://github.com/EliseuSantos/frontend-ia-demos)
- [Demo no ar, modo comparar](https://eliseusantos.github.io/frontend-ia-demos/02-main-thread/?modo=comparar)
- [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) e [Transferable objects](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects), MDN
- [Optimize long tasks](https://web.dev/articles/optimize-long-tasks) e [Interaction to Next Paint (INP)](https://web.dev/articles/inp), web.dev