Despertar bases de datos sin conexiones muertas
Las apps publicadas en Mythex, y sus bases de datos, se duermen cuando nadie las usa. Cuando una app despertaba, podía retener su primera petición entre 10 y 12 segundos, porque intentaba reutilizar conexiones a la base de datos que se habían muerto mientras todo dormía. Ahora damos a las apps de Spring Boot una configuración de base de datos segura tras el reposo, y pedimos al agente que construya apps de cualquier stack de la misma manera. En la plataforma de hosting, la primera petición tras un despertar pasó de 12,7 segundos a 2,3 segundos.
Por qué se duermen las apps y las bases de datos
Una app publicada de Mythex se ejecuta en una máquina que se suspende tras unos minutos sin actividad, y su base de datos es un Postgres sin servidor que se duerme tras cinco minutos inactivos y despierta con la siguiente consulta. Eso es lo que mantiene barata una app tranquila: pagas por lo que la base de datos realmente usó, y una app dormida no gasta nada en cómputo. Escribimos sobre el lado de la facturación en cobrar las bases de datos por lo que realmente usan.
El problema es que los dos se duermen por separado, y no se ponen de acuerdo sobre lo que es una conexión.
El síntoma: una primera visita lenta
La mayoría de las apps de servidor mantienen un pool de conexiones: un puñado de conexiones abiertas a la base de datos, listas para usarse, de modo que cada petición no tenga que pagar el coste de abrir una nueva. Es el diseño correcto para una app que está siempre encendida.
Esto es lo que pasa con el reposo:
- La app está en ejecución, con unas cuantas conexiones inactivas en su pool.
- Nadie la visita. La máquina de la app se suspende, congelada, con su pool intacto.
- La base de datos se duerme y cierra su extremo de cada conexión.
- Llega un visitante. La app despierta exactamente como estaba, todavía con conexiones que la base de datos ya no conoce.
- El pool entrega una de esas conexiones. Tiene que averiguar que está muerta, probándola y esperando, antes de descartarla.
En una app de Spring Boot, validar esas conexiones muertas bloqueaba la primera petición unos 10 segundos antes de fallar con «Connection is not available». Para el visitante, la web simplemente se colgaba y quizá mostraba un error después.
Por qué las apps de Spring eran las más afectadas
Spring Boot es un framework popular de Java, y por defecto usa un pool de conexiones llamado HikariCP. Spring puede configurar ese pool desde ajustes, incluido cuántas conexiones inactivas mantener.
Pero Mythex entrega a cada app su base de datos como una única DATABASE_URL con la forma postgresql://usuario:clave@host/bd. Spring no puede leer ese formato directamente. Así que las apps de Spring construidas en Mythex creaban su propio pool en código, analizando esa URL, y un pool construido a mano ignora todos los ajustes spring.datasource.* de Spring. Aunque alguien conociera los ajustes correctos del pool, no había ningún sitio donde se aplicaran.
La solución, parte 1: ajustes que Spring puede usar
Cuando un proyecto tiene una base de datos, ahora también recibe variables de entorno que Spring lee de forma nativa:
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAMEySPRING_DATASOURCE_PASSWORD, convertidas desdeDATABASE_URL(con SSL obligatorio salvo que la URL indique lo contrario).- Ajustes del pool que sobreviven al reposo:
| Ajuste | Valor | Por qué |
|---|---|---|
| Conexiones inactivas mínimas | 0 | No mantener nada abierto entre peticiones, así que no hay nada que caduque |
| Tiempo de inactividad | 30 segundos | Cerrar pronto las conexiones sin usar |
| Vida máxima | 2 minutos | Retirar cada conexión antes de que envejezca |
| Tiempo de espera de conexión | 5 segundos | No bloquear una petición mucho tiempo si la base de datos tarda en responder |
Sin conexiones inactivas retenidas, una app que despierta abre una conexión nueva en lugar de abrirse paso entre conexiones muertas.
Con estas variables presentes, Spring configura el pool por sí mismo y los ajustes se aplican. Lo que el proyecto haya definido por su cuenta siempre prevalece; nosotros solo completamos lo que falta. Las apps que no son de Spring ignoran estas variables.
El detalle que casi la dejó sin efecto
Spring asigna las variables de entorno a sus ajustes con una regla fácil de equivocar. El ajuste minimum-idle debe escribirse como SPRING_DATASOURCE_HIKARI_MINIMUMIDLE: sin guiones y con las palabras pegadas. Escribirlo como SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE, que parece más natural, se asigna a otro ajuste que no existe (minimum.idle) y se ignora sin avisar.
Ese es el tipo de error que pasa todas las revisiones y no hace nada en producción. Por eso una prueba comprueba tanto que los valores estén definidos como que ninguna variable use la forma separada por guiones bajos.
La solución, parte 2: todos los demás stacks
Spring no es el único framework con un pool, y no podemos preconfigurar todas las bibliotecas de todos los lenguajes. Por eso el agente de Mythex, la IA que escribe tu app, recibe ahora una instrucción permanente para cualquier stack: las apps publicadas y sus bases de datos se duermen cuando están inactivas, así que una conexión mantenida abierta puede estar muerta en la siguiente petición. Debe:
- no mantener abiertas conexiones inactivas a la base de datos, o comprobar cada una antes de usarla,
- reintentar una vez ante un error de conexión, y
- usar los ajustes de base de datos que la plataforma proporciona en el entorno, en lugar de construir su propio pool analizando
DATABASE_URL.
Ese último punto cierra el círculo con la parte 1: una app construida con los ajustes proporcionados recoge automáticamente la configuración segura frente al reposo.
El resultado
Medido en la plataforma de hosting donde ejecutamos las apps publicadas, la primera petición tras un despertar pasó de 12,7 segundos a 2,3 segundos.
Lo que sacamos de todo esto
- Cada capa que se duerme tiene que estar de acuerdo en lo que sobrevive. Una app congelada y una base de datos dormida tomaron cada una una decisión razonable. Juntas, la app despertó con un estado que ya no era cierto.
- Los valores por defecto pensados para servidores siempre encendidos no encajan con apps que escalan a cero. Mantener las conexiones calientes es buena idea hasta que todo lo que las rodea se enfría.
- Entrega a los frameworks sus datos en su propio formato. Una sola URL nos resultaba sencilla, pero obligaba a cada app de Spring a construir a mano un pool que ignoraba su propia configuración.
- Comprueba que un ajuste esté escrito como el framework lo lee. Un valor correcto con el nombre equivocado no hace nada, y sin avisar.
Si eres nuevo en cómo funcionan las bases de datos en Mythex, consulta la documentación de bases de datos y nuestra explicación sobre qué es Postgres.