Risvegliare i database senza connessioni morte
Le app pubblicate su Mythex, e i loro database, vanno in pausa quando nessuno le usa. Quando un'app si risvegliava poteva trattenere la prima richiesta per 10-12 secondi, perché cercava di riutilizzare connessioni al database morte mentre tutto era in pausa. Ora diamo alle app Spring Boot impostazioni del database sicure anche dopo una pausa, e diciamo all'agente di costruire le app di qualsiasi stack allo stesso modo. Sulla piattaforma di hosting, la prima richiesta dopo un risveglio è passata da 12,7 secondi a 2,3 secondi.
Perché app e database vanno in pausa
Un'app pubblicata su Mythex gira su una macchina che si sospende dopo pochi minuti di inattività, e il suo database è un Postgres serverless che va in pausa dopo cinque minuti di inattività e si risveglia alla richiesta successiva. È ciò che mantiene economica un'app tranquilla: paghi per ciò che il database ha effettivamente usato, e un'app in pausa non consuma nulla in calcolo. Abbiamo scritto del lato fatturazione in come vengono fatturati i database in base a ciò che usano davvero.
Il problema è che le due parti vanno in pausa separatamente, e non concordano su cosa sia una connessione.
Il sintomo: una prima visita lenta
La maggior parte delle app server mantiene un pool di connessioni: una manciata di connessioni aperte al database tenute pronte, così ogni richiesta non paga il costo di aprirne una nuova. È il design giusto per un'app sempre attiva.
Ecco cosa succede con la pausa:
- L'app è in esecuzione, con alcune connessioni inattive nel suo pool.
- Nessuno visita il sito. La macchina dell'app si sospende, congelata, con il pool intatto.
- Il database va in pausa e chiude la sua estremità di ogni connessione.
- Arriva un visitatore. L'app si risveglia esattamente com'era, ancora in possesso di connessioni che il database non conosce più.
- Il pool consegna una di quelle connessioni. Deve scoprire che è morta, provandola e aspettando, prima di scartarla.
In un'app Spring Boot, la validazione di quelle connessioni morte bloccava la prima richiesta per circa 10 secondi, prima di fallire con «Connection is not available». Per il visitatore il sito sembrava semplicemente bloccato, e poi magari mostrava un errore.
Perché le app Spring ne uscivano peggio
Spring Boot è un framework Java molto diffuso e, per impostazione predefinita, usa un pool di connessioni chiamato HikariCP. Spring può configurare quel pool tramite impostazioni, incluso quante connessioni inattive mantenere.
Ma Mythex fornisce il database di ogni app come un singolo DATABASE_URL nella forma postgresql://user:pass@host/db. Spring non sa leggere direttamente quel formato. Quindi le app Spring costruite su Mythex creavano il proprio pool nel codice, analizzando quell'URL, e un pool costruito a mano ignora tutte le impostazioni spring.datasource.* di Spring. Anche se qualcuno avesse conosciuto le impostazioni giuste del pool, non c'era un punto in cui avrebbero avuto effetto.
La soluzione, parte 1: impostazioni che Spring può usare
Quando un progetto ha un database, ora riceve anche variabili d'ambiente che Spring legge nativamente:
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAMEeSPRING_DATASOURCE_PASSWORD, convertite daDATABASE_URL(con SSL obbligatorio a meno che l'URL non dica diversamente).- Impostazioni del pool che sopravvivono alla pausa:
| Impostazione | Valore | Perché |
|---|---|---|
| Connessioni inattive minime | 0 | Non tenere nulla aperto tra una richiesta e l'altra, così non c'è nulla che possa scadere |
| Timeout di inattività | 30 secondi | Chiudere presto le connessioni non usate |
| Durata massima | 2 minuti | Ritirare ogni connessione prima che possa invecchiare |
| Timeout di connessione | 5 secondi | Non bloccare una richiesta a lungo se il database risponde lentamente |
Senza connessioni inattive trattenute, un'app risvegliata apre una connessione nuova invece di attraversare quelle morte.
Con queste variabili presenti, Spring configura il pool da solo, e le impostazioni si applicano. Ciò che il progetto ha impostato da sé prevale sempre; noi riempiamo solo ciò che manca. Le app non Spring ignorano queste variabili.
Il dettaglio che quasi la rendeva inefficace
Spring associa le variabili d'ambiente alle sue impostazioni con una regola facile da sbagliare. L'impostazione minimum-idle va scritta come SPRING_DATASOURCE_HIKARI_MINIMUMIDLE: trattini eliminati, parole attaccate. Scritta come SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE, che sembra più naturale, viene associata a un'impostazione diversa e inesistente (minimum.idle) e viene ignorata in silenzio.
È il genere di errore che supera ogni revisione e non fa nulla in produzione. Quindi un test verifica sia che i valori siano impostati, sia che nessuna variabile usi la forma con i trattini bassi.
La soluzione, parte 2: tutti gli altri stack
Spring non è l'unico framework con un pool, e non possiamo preconfigurare ogni libreria in ogni linguaggio. Quindi l'agente di Mythex, l'IA che scrive la tua app, ora riceve un'istruzione permanente per qualsiasi stack: le app pubblicate e i loro database vanno in pausa quando sono inattivi, quindi una connessione tenuta aperta può essere morta alla richiesta successiva. Dovrebbe:
- non tenere aperte connessioni inattive al database, oppure controllarne ciascuna prima dell'uso,
- riprovare una volta in caso di errore di connessione, e
- usare le impostazioni del database che la piattaforma fornisce nell'ambiente, invece di costruire un proprio pool analizzando
DATABASE_URL.
L'ultimo punto chiude il cerchio con la parte 1: un'app costruita sulle impostazioni fornite riceve automaticamente la configurazione sicura per la pausa.
Il risultato
Misurato sulla piattaforma di hosting su cui gestiamo le app pubblicate, la prima richiesta dopo un risveglio è passata da 12,7 secondi a 2,3 secondi.
Cosa ci siamo portati a casa
- Ogni livello che va in pausa deve concordare su cosa sopravvive. Un'app congelata e un database in pausa hanno fatto ciascuno una scelta ragionevole. Insieme, l'app si risvegliava con uno stato che non era più vero.
- I default pensati per server sempre accesi non vanno bene per app che scalano a zero. Tenere le connessioni calde è una buona idea finché tutto ciò che le circonda è freddo.
- Dai ai framework i loro dati nel loro formato. Un unico URL era semplice per noi, ma costringeva ogni app Spring a costruire a mano un pool che ignorava la propria configurazione.
- Verifica che un'impostazione sia scritta come il framework la legge. Un valore corretto con il nome sbagliato non fa nulla, in silenzio.
Se non conosci ancora come funzionano i database su Mythex, vedi la documentazione sui database e la nostra spiegazione su cos'è Postgres.