Acordar Bases de Dados Sem Ligações Mortas
As apps publicadas no Mythex, e as suas bases de dados, entram em suspensão quando ninguém as usa. Quando uma app acordava, podia manter o primeiro pedido 10–12 segundos, porque tentava reutilizar ligações à base de dados que tinham morrido enquanto tudo dormia. Agora damos às apps Spring Boot definições de base de dados seguras depois de uma suspensão, e dizemos ao agente para construir apps de qualquer stack da mesma forma. Na plataforma de alojamento, o primeiro pedido após um despertar passou de 12,7 segundos para 2,3 segundos.
Porque é que as apps e as bases de dados entram em suspensão
Uma app publicada no Mythex corre numa máquina que suspende depois de alguns minutos inativa, e a sua base de dados é um Postgres serverless que adormece após cinco minutos inativa e acorda com o pedido seguinte. É isso que mantém barata uma app tranquila: pagas pelo que a base de dados realmente usou, e uma app adormecida não gasta nada em computação. Escrevemos sobre o lado da faturação em cobrar bases de dados pelo que realmente usam.
O senão é que as duas entram em suspensão separadamente, e não concordam quanto ao que é uma ligação.
O sintoma: uma primeira visita lenta
A maioria dos servidores de apps mantém um pool de ligações: um punhado de ligações abertas à base de dados, mantidas prontas para que cada pedido não pague o custo de abrir uma nova. É o desenho certo para uma app que está sempre ligada.
Eis o que acontece com a suspensão:
- A app está a correr, com algumas ligações inativas no pool.
- Ninguém visita. A máquina da app suspende — congelada, com o pool intacto.
- A base de dados adormece e deixa cair a sua ponta de cada ligação.
- Chega um visitante. A app acorda exatamente como estava, ainda com ligações que a base de dados já não conhece.
- O pool entrega uma dessas ligações. Tem de descobrir que está morta — tentando-a e esperando — antes de desistir dela.
Numa app Spring Boot, validar essas ligações mortas bloqueava o primeiro pedido durante cerca de 10 segundos antes de falhar com "Connection is not available". Para o visitante, o site simplesmente ficava pendurado e depois mostrava talvez um erro.
Porque é que as apps Spring foram as mais afetadas
Spring Boot é uma framework Java popular, e usa por defeito um pool de ligações chamado HikariCP. O Spring consegue configurar esse pool a partir de definições, incluindo quantas ligações inativas manter.
Mas o Mythex dá a cada app a sua base de dados como uma única DATABASE_URL no formato postgresql://user:pass@host/db. O Spring não consegue ler esse formato diretamente. Assim, as apps Spring construídas no Mythex criavam o seu próprio pool em código, fazendo o parsing desse URL — e um pool construído à mão ignora todas as definições spring.datasource.* do Spring. Mesmo que alguém soubesse as definições certas do pool, não havia onde elas tivessem efeito.
A correção, parte 1: definições que o Spring consegue usar
Quando um projeto tem uma base de dados, agora também lhe damos variáveis de ambiente que o Spring lê nativamente:
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAMEeSPRING_DATASOURCE_PASSWORD, convertidas a partir deDATABASE_URL(com SSL obrigatório, a menos que o URL indique outra coisa).- Definições do pool que sobrevivem à suspensão:
| Definição | Valor | Porquê |
|---|---|---|
| Mínimo de ligações inativas | 0 | Não mantém nada aberto entre pedidos, por isso não há nada que fique desatualizado |
| Tempo limite de inatividade | 30 segundos | Fecha cedo as ligações não usadas |
| Tempo de vida máximo | 2 minutos | Retira cada ligação antes de poder envelhecer |
| Tempo limite de ligação | 5 segundos | Não deixa um pedido pendurado muito tempo se a base de dados demorar a responder |
Sem ligações inativas mantidas, uma app acordada abre uma ligação nova em vez de percorrer as mortas.
Com estas variáveis presentes, o Spring configura o próprio pool, e as definições aplicam-se. O que o projeto tiver definido por si tem sempre prioridade; nós só preenchemos o que falta. As apps que não são Spring ignoram estas variáveis.
O detalhe que quase não fazia nada
O Spring mapeia variáveis de ambiente para as suas definições segundo uma regra fácil de errar. A definição minimum-idle tem de ser escrita como SPRING_DATASOURCE_HIKARI_MINIMUMIDLE — sem hífenes, palavras juntas. Escrevê-la como SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE, que parece mais natural, mapeia para uma definição diferente e inexistente (minimum.idle) e é ignorada em silêncio.
Este é o tipo de erro que passa em todas as revisões e não faz nada em produção. Por isso, um teste verifica tanto que os valores estão definidos como que nenhuma variável usa a forma com underscores.
A correção, parte 2: todas as outras stacks
O Spring não é a única framework com um pool, e não conseguimos pré-configurar todas as bibliotecas em todas as linguagens. Por isso, o agente do Mythex — a IA que escreve a tua app — recebe agora uma instrução permanente para qualquer stack: as apps publicadas e as suas bases de dados entram em suspensão quando estão inativas, por isso uma ligação mantida aberta pode estar morta no pedido seguinte. Deve:
- não manter ligações inativas à base de dados abertas, ou verificar cada uma antes de a usar,
- tentar de novo uma vez em caso de erro de ligação, e
- usar as definições de base de dados que a plataforma fornece no ambiente, em vez de construir o seu próprio pool a fazer parsing de
DATABASE_URL.
Este último ponto fecha o ciclo com a parte 1: uma app construída com as definições fornecidas apanha automaticamente a configuração segura para suspensão.
O resultado
Medido na plataforma de alojamento onde corremos as apps publicadas, o primeiro pedido após um despertar passou de 12,7 segundos para 2,3 segundos.
O que tirámos disto
- Cada camada que entra em suspensão precisa de concordar sobre o que sobrevive. Uma app congelada e uma base de dados a adormecer fizeram, cada uma, uma escolha razoável. Juntas, a app acordou a segurar um estado que já não era verdadeiro.
- Predefinições pensadas para servidores sempre ligados não servem apps que escalam para zero. Manter ligações quentes é boa ideia até tudo à volta delas arrefecer.
- Dá às frameworks os seus dados no formato delas. Um único URL era simples para nós, mas obrigava cada app Spring a construir à mão um pool que ignorava a sua própria configuração.
- Testa se uma definição está escrita da forma como a framework a lê. Um valor correto com o nome errado não faz nada, em silêncio.
Se és novo em como as bases de dados funcionam no Mythex, vê a documentação das bases de dados e o nosso guia sobre o que é o Postgres.