stamatios
← Voltar ao feed
Pilha de rolos de filme despejada em um funil que alimenta um único olho, metáfora do Replay Vision da PostHog
Dev & Engenharia · IA & Modelos

PostHog cria rasterizador para modelos multimodais verem replays

resumo de ~3 min

Modelos não conseguiam ver replay

A maioria das gravações de sessão que o PostHog ingere não é assistida: fica armazenada, nunca é aberta e expira sem atenção. Em produto movimentado, são dezenas de milhares por dia, impossíveis de acompanhar só com pessoas. O time de Session Replay lançou filtros, ordenação, coleções e resumos para lidar com atenção limitada. O Replay Vision partiu de outra ideia: automatizar essa atenção. Modelos multimodais recentes são bons o suficiente para assistir a um vídeo e relatar o que o usuário fez e onde algo deu errado.

O primeiro obstáculo foi que os modelos não conseguiam realmente ver um replay. A gravação parece vídeo, mas não há vídeo no pipeline. O SDK captura, com rrweb, um DOM serializado e eventos de mudança: nó inserido, atributo alterado, mouse movido, página rolada. Ao apertar play, um navegador reconstrói o DOM e aplica as mutações, com estilos e layout reais.

Entregar isso a um LLM se mostrou infrutífero. É conteúdo verboso, muitos megabytes de JSON por gravação, e modelos recentes têm contexto limitado. Pior: o modelo não sabe o que está vendo. O fluxo rrweb é um changelog contra uma árvore; para saber o que a tela mostrava no segundo 42, seria preciso manter a árvore inteira em contexto, aplicar as mutações até ali e rodar layout e pintura. O modelo pode dizer que uma div ganhou a classe is-disabled, mas não que o botão de checkout ficou cinza e deslizou sob o banner de cookies, fato que só existe após o layout. Modelos multimodais como Gemini precisam de vídeo real. Faltava a versão rasterizada: um MP4 de pixels comprimidos.

Rasterizador: dois processos, um relógio

O primeiro componente foi um rasterizador: rrweb entra, MP4 sai. O serviço carregava o player em Chromium headless, reproduzia a gravação a 8x e capturava quadros via Chrome DevTools Protocol para o ffmpeg codificar em MP4.

Funcionou nos testes iniciais, mas com várias instâncias na mesma máquina surgiram vídeos que pulavam trechos, congelavam por segundos ou tinham durações diferentes para a mesma gravação. O serviço era muito sensível à contenção de CPU. Eram dois processos independentes: um renderizava, outro capturava. Se a renderização desacelerasse, a captura seguia no ritmo e o ffmpeg repetia o último quadro - congelamento. Se a captura passasse fome enquanto a renderização continuava, quadros se acumulavam e alguns eram descartados - salto. Era preciso determinismo.

Congelar o tempo

Outra forma de ver o problema é o tempo: captura e renderização esperam rodar em tempo real. O Chrome tem o modo HeadlessExperimental.beginFrame, que impede o compositor de produzir quadros sozinho. Ele espera um comando explícito via CDP, renderiza um quadro e retorna quando termina. O próximo quadro só vem depois que a captura pegou o anterior e o enviou ao ffmpeg.

Mas o player rrweb em JavaScript não espera o navegador. Se o requestAnimationFrame é chamado menos vezes, ele compensa avançando conforme o tempo passado desde o último tick. Era preciso fazer o player acreditar que rodava em tempo real e avançar exatamente um intervalo de quadro por tick. A solução foi congelar o tempo na máquina virtual JavaScript: Date.now, performance.now, requestAnimationFrame, setTimeout e setInterval são substituídos por versões que não seguem o relógio de parede. O tempo só anda quando beginFrame é chamado, por um intervalo de quadro. Assim o player não percebe se um tick levou 20 milissegundos ou dois segundos reais. Isso usa o puppeteer-capture, plugin do Puppeteer com os shims de tempo virtual e o loop de beginFrame.

Resultado

Com beginFrame controlando o compositor e o relógio virtual controlando o player, os dois modos de falha desaparecem. Se a renderização passa fome, o relógio espera o quadro. Se a captura passa fome, o renderizador espera o próximo beginFrame. O próximo quadro do vídeo é o próximo da gravação, e a contenção de CPU muda só a duração, não o resultado. A mesma gravação gera o mesmo vídeo.

A versão descrita está em produção desde março de 2026. Roda como centenas de jobs concorrentes em muitos pods e alimenta todo o Replay Vision e alguns recursos do Session Replay. Até a data do texto, havia rasterizado mais de 370 anos de gravações em cerca de 3,5 milhões de vídeos. O rasterizador foi o desbloqueio para colocar gravações diante de um modelo multimodal.