Skip to content

Banco lento

Pool de conexões: recicle com jitter

Todo pool de conexões descarta e recria conexões depois de um tempo de vida máximo — MaxConnLifetime no pgx, pool_recycle no SQLAlchemy, maxLifetimeSeconds no pg do Node. Se todas as conexões nasceram juntas no boot do app, todas expiram no mesmo segundo. Um pool pequeno pode travar nessa reconexão em massa e não se recuperar sozinho: o app fica respondendo db ping failed indefinidamente, mesmo com o banco saudável.

Foi exatamente isso que tirou três apps do ar por cerca de 21 horas em junho de 2026. O banco estava de pé o tempo todo; o que travou foi o pool do lado do app.

A correção é espalhar a expiração (jitter). Como fazer em cada biblioteca:

Go — pgx/v5 (pgxpool)

cfg, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
    return err
}
cfg.MaxConnLifetime = time.Hour
cfg.MaxConnLifetimeJitter = 30 * time.Minute // metade do tempo de vida

pool, err := pgxpool.NewWithConfig(ctx, cfg)

Atenção: pgxpool.New(ctx, dsn) não aceita jitter, porque a DSN não tem como expressar esse campo. Use ParseConfig + NewWithConfig como acima.

Se você usa github.com/guilhermebr/gox/postgres, atualize para a versão de setembro de 2026 ou mais nova — o jitter passa a ser aplicado sozinho.

Python — psycopg_pool e SQLAlchemy

# psycopg_pool: max_lifetime já aplica um jitter aleatório por conexão
pool = ConnectionPool(dsn, max_lifetime=3600)

# SQLAlchemy: recicle e valide antes de usar
engine = create_engine(dsn, pool_recycle=3600, pool_pre_ping=True)

O pool_pre_ping descarta conexões mortas antes de entregá-las ao seu código, o que evita a maior parte dos sintomas mesmo quando a expiração é sincronizada.

Node — pg (node-postgres)

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  maxLifetimeSeconds: 3600,
  idleTimeoutMillis: 30000,
});

A partir do pg 8.10 a expiração é avaliada por conexão, não em lote.

Sintoma clássico

  • O app responde healthz: db ping failed ... context deadline exceeded por minutos ou horas.
  • O banco está saudável e aceita conexões novas normalmente.
  • Um restart do app resolve na hora — e o problema volta algumas horas depois, sempre perto de uma marca redonda de tempo desde o último boot.

Se isso descreve o seu caso, o pool é o suspeito, não o banco.

Checklist rápido

  • Sua app usa connection pool? (pgxpool, psycopg_pool, pg.Pool — não abra uma conexão por request)
  • O tempo de vida das conexões tem jitter? (seção acima)
  • Você bateu o limite de conexões do plano? Veja Bancos → Limites
  • Os logs mostram timeout em alguma query específica?
  • Tem EXPLAIN ANALYZE da query suspeita?