Soluzioni

Impresa

Modelli

Sviluppatore

Risorse

Prezzo

Da

Keith Williams

15 mag 2023

Cal.com Risoluzione Cold Start

Contesto

Per un po' di tempo, l'app Cal.com poteva soffrire di lunghi tempi di caricamento a freddo che variavano da 7 secondi a 30 secondi, nei casi estremi. Questo comportamento non si verificava sempre a causa della natura dei caricamenti a freddo, ma si verificava abbastanza frequentemente da diventare un problema che dovevamo affrontare il prima possibile.

Per coloro che non sono familiari con i caricamenti a freddo, ci sono molti materiali là fuori che descrivono il problema in generale quindi non tratteremo questo. Ciò che speriamo di fare con questo blog è spiegare la causa principale che stava influenzando il nostro sistema in particolare, che era indotta dai caricamenti a freddo in un ambiente serverless.

Risultati e Esperimenti

Per iniziare, diamo un'occhiata al seguente log di Vercel:

Per la funzione /api/trpc/[trpc], vediamo una Durata di Avvio a Freddo di 298 ms ma una Durata di Esecuzione di 7,57 s. Questo è un po' strano, giusto? L'avvio a freddo stesso non è il problema ma piuttosto, è ciò che si carica all'avvio a freddo che sta causando l'inflazione della Durata di Esecuzione

Quindi da dove iniziamo a trovare la causa principale di questo?

Ipotesi 1: Dimensione del pacchetto della funzionalità

Dopo aver visto la Dimensione della Funzione di 33,1 MB, il nostro team, la comunità e i ragazzi di Vercel e Prisma sono entrati immediatamente e hanno iniziato analisi dei pacchetti e logging della compilazione per cercare di capire quali pacchetti appesantivano le nostre funzioni. Anche se questa Dimensione della Funzione è sotto il limite di 50MB, credevamo potesse essere almeno in parte la colpevole.

Abbiamo identificato alcune grosse dipendenze che venivano caricate come parte del recupero della sessione che sono state ridotte (PR) e abbiamo caricato pigramente le dipendenze del nostro app store (PR).

Le prestazioni sono migliorate ad ogni richiesta dopo questi aggiornamenti ma non è stata la riduzione di più secondi che speravamo. Allora cos'altro potrebbe essere? È Prisma lento a connettersi all'avvio a freddo nel nostro setup serverless?

Ipotesi 2: Prisma

Avevamo già Prisma Data Proxy in esecuzione in produzione per gestire il nostro pooling di connessioni quindi eravamo scettici che fosse il problema, ma nonostante ciò, volevamo fare il nostro dovere per assicurarci di questo.

In un ramo sperimentale, abbiamo avviato un endpoint di prova segregato dalle nostre rotte API e pagine in una separate app route (utilizzando il nuovo App Router di Next.js) che otteneva solo il conteggio degli utenti nel sistema. Le prestazioni di questa rotta sugli avvii a freddo potevano arrivare fino a 1 secondo, il che ci mostrava che Prisma può avere un leggero ritardo sugli avvii a freddo, ma ancora, non erano i 7-15 secondi che vedevamo in media.

Ipotesi 3: _app e _document

Dall'analisi dei pacchetti che era stata effettuata, era chiaro che la nostra app carica molte dipendenze. Questo ha innescato l'idea di ridurre ciò che veniva caricato ad ogni richiesta API. Sapendo che le rotte API in Next.js caricano il file _app.tsx, abbiamo deciso di implementare un PageWrapper che contiene tutte le dipendenze necessarie per le rotte non API (PR).

Ancora una volta, questo è un bel miglioramento in termini di ottimizzazione delle dipendenze ma avevamo ancora i nostri tempi di attesa lunghi.

Cause Fondamentali

Dopo aver spedito carichi di PR specifici per le prestazioni che hanno aiutato con l'organizzazione del codice, la velocità e la dimensione del pacchetto, non avevamo ancora trovato il colpo d'oro. Era il momento di tornare al cerchio completo e pensare in termini di colli di bottiglia poiché molti problemi di prestazioni sono legati a colli di bottiglia, giusto?

Utilizzando questo modo di pensare, è stato eseguito un test per dividere il router tRPC più semplice in una propria rotta API. Per noi, quello era il nostro router pubblico. Dopo il deployment, l'endpoint /api/trpc/viewer.public.i18n era ora /api/trpc/public/i18n. Abbiamo visto una diminuzione da 15 secondi a 2 secondi nei tempi di avvio a freddo per questa rotta. Questo sembrava sottolineare il fatto che il nostro singolo router tRPC era effettivamente il collo di bottiglia.

In poche parole, avevamo troppi router tRPC che andavano nel principale router viewer esposto sulla rotta /api/trpc/[trpc]. Questo significava che la prima chiamata a questa funzione, che fosse per caricare i termini i18n o per ottenere il programma di un utente, doveva importare tutti e 20 i router e tutte le loro dipendenze. 

Visto qui in quello che è stato in ultima analisi il PR che ha risolto la maggior parte dei nostri problemi di lungo avvio a freddo, c'era il sospetto che il router dei slot fosse troppo grande e fosse la causa dei problemi. Sfortunatamente, dopo aver diviso il router dei slot in una propria rotta API di Next.js, stavamo ancora vedendo grandi dimensioni del pacchetto e lunghi tempi di avvio a freddo. Per questi motivi, questo PR è rimasto intoccato mentre venivano perseguite altre ipotesi.

Dopo la scoperta della rotta i18n che si caricava in 2 secondi all'avvio a freddo, il PR sopra menzionato è stato quindi ampliato in modo che tutti i router tRPC avessero la propria rotta API. Quando vengono divisi, Vercel crea funzioni separate per ogni rotta.

Quindi perché dividere i router tRPC nelle proprie rotte API di Next.js risolve il problema? La risposta semplice, per il nostro caso, è che anche se una pagina chiama 5 diverse rotte tRPC per caricare i dati, esse verranno eseguite in parallelo e quindi l'effetto del caricamento delle dipendenze per tutti i router non è compresso. Possiamo ancora vedere tempi di avvio a freddo di 2-3 secondi su qualsiasi data tRPC ma poiché vengono chiamati in parallelo dal browser, il tempo di attesa totale può essere di 3 secondi, non 7-15 secondi (o 30 secondi nei casi estremi).

Riconosciamo che c'è ancora molto lavoro da fare per ridurre ulteriormente queste velocità, ma le riduzioni finora sono state significative.

Ecco un problema per promuovere potenziali cambiamenti per tRPC in ambienti serverless.

Risultati Chiave

Differences in Endpoint Performance

Riconoscimenti

Un grande riconoscimento e ringraziamento a tutti coloro che hanno contribuito a risolvere questo problema. Dalla scrittura di script personalizzati che hanno aiutato a determinare quali dipendenze stavano appesantendo il nostro sistema all'apertura di PR, apprezziamo davvero il supporto e l'impegno profuso nella risoluzione di questi problemi. Speriamo che questo blog e i PR possano fornire un po' di insight sugli esperimenti che abbiamo svolto e sulla causa principale che abbiamo infine trovato. 

Un sentito ringraziamento a Julius Marminge da tRPC.io, Guillermo Rauch, Malte Ubl e Jimmy Lai da Vercel e Lucas Smith. 🙏

Cosa c'è dopo

Sebbene questo sia stato un grande traguardo, stiamo ancora lavorando duramente per ridurre ulteriormente i tempi di avvio a freddo, ma anche per migliorare l'esperienza di self-hosting senza un'architettura serverless. Rimanete sintonizzati!