Connection pooling: porque é que a base cai sem ter tido um pico
Uma ligação ao Postgres não é um objeto: é um processo do lado do servidor, com a sua memória. Daí o preço, e daí a conta que quase ninguém faz antes de a base começar a recusar trabalho.
E o erro não aparece onde se espera. A base não fica lenta — fica a dizer que já tem clientes suficientes, enquanto o gráfico de carga dela está no chão.
1. Duzentos utilizadores não são duzentas ligações
Um pedido que corre uma consulta de 5 ms ocupa a ligação durante 5 ms. Em cem pedidos por segundo, meia ligação está ocupada em permanência. É por isso que um pool de dez aguenta um tráfego que parece exigir centenas: as ligações são reutilizadas, e o que varia é quanto tempo cada pedido as retém.
A consequência é que o número que importa não é «quantos utilizadores», mas quanto tempo cada consulta segura a ligação. Uma consulta de 2 segundos estraga o pool de uma forma que mil consultas de 2 ms não estragam.
2. A conta que ninguém faz
O Postgres vem de origem com max_connections = 100, e esse número é partilhado por tudo o que se liga. A conta é esta:
instâncias da aplicação × tamanho do pool
+ a sessão de psql que está aberta para ver uma coisa
+ o trabalho de migração que corre no arranque
+ a cópia de segurança noturna
+ o que mais estiver nesse servidorUm contentor com um pool de dez usa dez. Três contentores usam trinta. Acrescente-se outra aplicação na mesma base e os cem acabam sem ninguém ter tido um pico de tráfego. O limite não se atinge por sucesso; atinge-se por multiplicação.
3. O sintoma, que engana
Há dois, e são opostos. O primeiro é honesto: too many clients already, e sabe-se logo o que é. O segundo é o mau — os pedidos ficam em fila à espera de uma ligação do pool e expiram ali, sem nunca chegarem à base.
Nesse caso a aplicação está lenta, os registos mostram timeouts de base de dados, e a base está ociosa. Quem for ver a base não encontra nada, porque o problema não está lá — está na fila à porta. É um dos casos em que os cinco comandos por esta ordem levam ao sítio errado se não se souber disto.
4. Os três números que se configuram
Máximo de ligações do pool. Por instância, não no total. A regra de partida:
max_connectionsa dividir pelo número de instâncias, com folga para o psql e para as cópias.Tempo de espera por uma ligação. Se não houver, o pedido fica pendurado em vez de falhar. Falhar depressa é melhor do que uma fila que cresce.
Tempo até fechar uma ligação ociosa. O que evita manter trinta ligações abertas às quatro da manhã por causa de um pico das três.
Aumentar o pool é a reação errada quase sempre. Se a fila cresce porque as consultas são lentas, um pool maior dá mais consultas lentas em simultâneo — e aí o problema passa a ser da base. É quando se vai ler o plano em vez de adivinhar.
5. Onde o pool deixa de funcionar
Em ambientes onde cada pedido arranca o seu próprio processo — serverless, funções à beira — não há pool que sirva, porque cada invocação criaria o seu. Cem invocações em paralelo são cem pools, e a base cai de imediato.
A saída é um intermediário (o PgBouncer e afins) em modo de transação, com uma consequência que convém saber antes: nesse modo não há sessão. Instruções preparadas, tabelas temporárias e SET deixam de se poder contar com, porque a ligação de trás muda entre transações.
6. E aqui?
Este site usa o adaptador do Prisma sobre o pg, sem opções de pool definidas — leva os valores por omissão. Um contentor, um pool, uma base que não tem mais nada a usá-la.
Está bem assim, e vale a pena ser exacto quanto ao motivo: está bem pela escala, não por cuidado nenhum. No dia em que houver dois contentores, ou outra aplicação na mesma base, é esta a conta a fazer primeiro — e é mais barato fazê-la agora, enquanto a resposta é óbvia, do que às duas da manhã com o site em baixo.
O resumo
O pool existe porque uma ligação é um processo. O limite esgota-se por multiplicação de instâncias, não por tráfego. E quando os pedidos expiram com a base ociosa, o problema é a fila à porta — não a base.