Antecedentes
Por algum tempo, o aplicativo Cal.com poderia sofrer com longos tempos de carregamento em frio variando de 7 segundos a 30 segundos, em casos extremos. Esse comportamento não ocorria sempre devido à natureza dos arranques em frio, mas estava acontecendo com frequência suficiente para se tornar um problema que precisávamos resolver o mais rápido possível.
Para aqueles que não estão familiarizados com arranques em frio, há muitos materiais disponíveis que descrevem o problema de forma geral, por isso não vamos cobrir isso. O que esperamos fazer com este blog é explicar a causa raiz exata que estava afetando o nosso sistema em particular, que foi induzida por arranques em frio em um ambiente sem servidor.
Resultados e Experimentos
Para começar, vamos olhar o seguinte log da Vercel:

Para a função /api/trpc/[trpc]
, vemos uma Duração de Arranque Frio de 298ms, mas uma Duração de Execução de 7.57s. Isso é um pouco estranho, certo? O arranque frio em si não é o problema, mas sim o que está sendo carregado no arranque frio que está causando a Duração de Execução a aumentar de forma significativa.
Então, por onde começamos a encontrar a causa raiz disso?
Hipótese 1: Tamanho do pacote da função
Após ver o Tamanho da Função de 33.1 MB, a nossa equipe, a comunidade e o pessoal da Vercel e Prisma mergulharam diretamente e começaram análises de pacotes e registro de compilação para tentar descobrir quais pacotes estavam sobrecarregando nossas funções. Embora esse Tamanho da Função esteja abaixo do limite de 50MB, acreditávamos que ainda poderia ser, no mínimo, parcialmente o culpado.
Identificamos algumas grandes dependências sendo carregadas como parte da nossa recuperação de sessão, que puderam ser reduzidas (PR) e carregámos de forma assíncrona as dependências da nossa loja de aplicativos (PR).
A performance melhorou em cada requisição após essas atualizações, mas não foi a redução de múltiplos segundos que estávamos esperando. Então, o que mais poderia ser? Será que o Prisma é lento para conectar em arranques em frio na nossa configuração sem servidor?
Hipótese 2: Prisma
Já tínhamos o Prisma Data Proxy funcionando em produção para lidar com o nosso agrupamento de conexões, então estávamos céticos quanto ao fato de ser esse o problema, mas mesmo assim, queríamos fazer nossa devida diligência para ter certeza disso.
Em um ramo experimental, criamos um endpoint de teste segregado das nossas rotas e páginas de API em uma rota de aplicativo separada (usando o novo App Router do Next.js) que apenas contava o número de usuários no sistema. A performance desta rota em arranques em frio podia chegar a 1 segundo, o que nos mostrou que o Prisma pode ter um pequeno atraso em arranques em frio, mas, novamente, não era os 7-15 segundos que estávamos observando em média.
Hipótese 3: _app e _document
Com a análise do pacote que foi feita, estava claro que o nosso aplicativo carrega muitas dependências. Isso despertou a ideia de reduzir o que era carregado a cada requisição de API. Sabendo que as rotas da API no Next.js carregam o arquivo _app.tsx, decidimos implementar um PageWrapper que contém todas as dependências necessárias para rotas que não são de API (PR).
Novamente, isso é uma boa melhoria em termos de otimização de dependências, mas ainda temos nossas longas esperas.
Causa Raiz
Depois de enviar vários PRs específicos de performance que ajudaram na organização do código, velocidade e tamanho do pacote, ainda não tínhamos encontrado a solução mágica. Era hora de voltar ao básico e pensar em termos de gargalos, já que muitos problemas de performance estão relacionados a gargalos, certo?
Usando esse raciocínio, foi feito um teste para dividir o mais simples roteador tRPC em sua própria rota de API. Para nós, esse era nosso roteador público. Após a implantação, o endpoint /api/trpc/viewer.public.i18n
agora era /api/trpc/public/i18n
. Observamos uma redução de 15 segundos para 2 segundos nos tempos de arranque em frio para esta rota. Isso parecia indicar que nosso único roteador tRPC era de fato o gargalo.
De forma simples, tínhamos muitos roteadores tRPC indo para o roteador principal de visualização exposto na rota /api/trpc/[trpc]
. Isso significava que a primeira chamada a essa função, seja para carregar termos i18n ou para obter a agenda de um usuário, precisava importar todos os 20 roteadores e todas as suas dependências.
Visto aqui no que foi, em última análise, o PR que resolveu a maioria dos nossos longos problemas de arranque em frio, havia uma suspeita de que o roteador de slots era muito grande e era a raiz dos problemas. Infelizmente, após dividir o roteador de slots em sua própria rota de API Next.js, ainda estávamos vendo tamanhos de pacotes grandes e longos tempos de arranque em frio. Por esses motivos, esse PR foi deixado intocado enquanto outras hipóteses foram exploradas.
Após a descoberta de que a rota i18n carregava em 2 segundos nos arranques em frio, o PR mencionado foi então aprimorado para que todos os roteadores tRPC tivessem sua própria rota de API. Quando divididos, a Vercel cria funções separadas por rota.
Então, por que dividir os roteadores tRPC em suas próprias rotas de API Next.js resolve o problema? A resposta simples, no nosso caso, é que mesmo que uma página chame 5 diferentes rotas tRPC para carregar dados, elas serão executadas concorrentemente e, assim, o efeito do carregamento de dependências para todos os roteadores não se compõe. Agora ainda podemos ver tempos de arranque em frio de 2-3 segundos em qualquer rota tRPC dada, mas uma vez que são chamadas concorrentemente pelo navegador, o tempo total de espera pode ser 3 segundos, não 7-15 segundos (ou 30 segundos em casos extremos).
Reconhecemos que ainda há mais trabalho a ser feito para reduzir ainda mais essas velocidades, mas as reduções até agora têm sido significativas.
Aqui está um problema para promover mudanças potenciais para tRPC em ambientes sem servidor.
Resultados Chave

Agradecimentos
Um grande agradecimento e obrigado a todos que contribuíram para resolver este problema. Desde a redação de scripts personalizados que ajudaram a determinar quais dependências estavam sobrecarregando nosso sistema até a abertura de PRs, realmente apreciamos o apoio e o esforço dedicados a resolver esses problemas. Esperamos que este blog e os PRs possam fornecer um pouco de insight sobre os experimentos que realizamos e a causa raiz que eventualmente encontramos.
Agradecimentos especiais a Julius Marminge do tRPC.io, Guillermo Rauch, Malte Ubl e Jimmy Lai da Vercel e Lucas Smith. 🙏
O que vem a seguir
Embora esta tenha sido uma grande conquista, ainda estamos trabalhando arduamente para reduzir ainda mais os arranques em frio, mas também melhorar a experiência de auto-hospedagem sem uma arquitetura sem servidor. Fiquem atentos!