gabrel.com.br
~/blog/post

Pools de Conexões de Banco de Dados: O Que São e Por Que Sua Aplicação Precisa Deles

Se você já trabalhou com uma aplicação que acessa banco de dados, provavelmente já ouviu falar em "connection pool" (pool de conexões). Mas o que isso realmente significa, e por que é considerado uma prática essencial em qualquer aplicação séria? Vamos destrinchar isso com exemplos práticos.

O Problema: Conexões São Caras

Toda vez que sua aplicação precisa falar com o banco de dados, ela precisa de uma conexão — um canal de comunicação estabelecido entre a aplicação e o servidor do banco. Criar essa conexão do zero não é uma operação instantânea. Ela envolve várias etapas:

  1. Handshake TCP — estabelecer a conexão de rede em si (3-way handshake)
  2. Negociação SSL/TLS (se estiver usando conexão criptografada)
  3. Autenticação — enviar usuário/senha e o banco validar as credenciais
  4. Alocação de recursos no servidor — o banco reserva memória e processos/threads para atender aquela conexão

Cada uma dessas etapas leva tempo — geralmente de alguns milissegundos a dezenas de milissegundos. Isso pode parecer pouco, mas imagine uma aplicação web recebendo 1000 requisições por segundo, e cada requisição abrindo e fechando sua própria conexão com o banco. O overhead se acumula rapidamente e vira um gargalo de performance.

Exemplo prático do problema

Imagine uma API simples em Node.js, sem pool, usando uma conexão nova a cada requisição:

const { Client } = require('pg');

app.get('/usuarios/:id', async (req, res) => {
  const client = new Client({
    host: 'localhost',
    database: 'minha_app',
    user: 'admin',
    password: 'senha123',
  });

  await client.connect(); // abre conexão nova — custo de latência aqui
  const result = await client.query(
    'SELECT * FROM usuarios WHERE id = $1',
    [req.params.id]
  );
  await client.end(); // fecha a conexão — recurso descartado

  res.json(result.rows[0]);
});

Nesse exemplo, cada requisição paga o "imposto" de abrir e fechar uma conexão. Sob carga, isso é extremamente ineficiente e pode até derrubar o banco de dados, que tem um limite máximo de conexões simultâneas (no PostgreSQL, por padrão, esse limite costuma ser 100).

A Solução: Pool de Conexões

Um pool de conexões é, na prática, um "estoque" de conexões já abertas e prontas para uso, gerenciado pela própria aplicação (ou por um proxy dedicado). Em vez de abrir e fechar uma conexão a cada operação, a aplicação:

  1. Pega emprestada uma conexão já existente do pool
  2. Usa essa conexão para executar a query
  3. Devolve a conexão ao pool (em vez de fechá-la)

Se todas as conexões do pool estiverem em uso no momento, a requisição espera na fila até uma ficar disponível.

Exemplo prático da solução

Usando o mesmo cenário anterior, mas agora com um pool (pg.Pool, do próprio driver pg):

const { Pool } = require('pg');

// O pool é criado UMA VEZ, na inicialização da aplicação
const pool = new Pool({
  host: 'localhost',
  database: 'minha_app',
  user: 'admin',
  password: 'senha123',
  min: 5,          // mantém no mínimo 5 conexões abertas
  max: 20,         // no máximo 20 conexões simultâneas
  idleTimeoutMillis: 30000, // fecha conexões ociosas após 30s
});

app.get('/usuarios/:id', async (req, res) => {
  // pega uma conexão do pool (reutilizada, sem overhead de abrir do zero)
  const result = await pool.query(
    'SELECT * FROM usuarios WHERE id = $1',
    [req.params.id]
  );
  // a conexão volta automaticamente para o pool depois da query

  res.json(result.rows[0]);
});

Repare que a diferença é sutil no código, mas enorme na prática: não há mais client.connect() nem client.end() a cada requisição. O pool cuida de todo o ciclo de vida das conexões nos bastidores.

Por Que Isso Importa? Os Principais Benefícios

1. Performance

Reutilizar conexões elimina o overhead de handshake, autenticação e alocação de recursos a cada operação. Isso reduz drasticamente a latência das requisições, especialmente em aplicações com alto volume de tráfego.

2. Controle de Recursos

Bancos de dados têm limites de conexões simultâneas. Sem um pool, uma aplicação sob picos de tráfego pode facilmente estourar esse limite e causar erros de "too many connections", ou pior, derrubar o banco inteiro. O pool impõe um teto controlado (max) e enfileira requisições excedentes em vez de sobrecarregar o servidor.

3. Gerenciamento Automático de Conexões Problemáticas

Pools bem implementados também:

4. Previsibilidade sob Carga

Em vez de um comportamento caótico onde a aplicação abre quantas conexões "achar necessário", o pool cria um comportamento previsível e configurável, essencial para ambientes de produção.

Configurações Comuns de um Pool

Ao configurar um pool, alguns parâmetros aparecem quase universalmente entre diferentes tecnologias:

Parâmetro O que faz
min Número mínimo de conexões mantidas abertas, mesmo ociosas
max Número máximo de conexões simultâneas permitidas
idleTimeout Tempo até fechar uma conexão que está ociosa
connectionTimeout Tempo máximo de espera por uma conexão disponível antes de dar erro
acquireTimeout Similar ao anterior — tempo de espera na fila do pool

Como escolher o max ideal?

Não existe uma fórmula mágica, mas uma boa referência é a fórmula usada pelo HikariCP (pool famoso do ecossistema Java):

conexões = ((núcleos_de_cpu * 2) + número_de_discos_efetivos)

Na prática, a maioria das aplicações web funciona bem com pools entre 10 e 20 conexões por instância da aplicação — mas isso deve ser ajustado com testes de carga reais, considerando também quantas instâncias da aplicação estarão rodando simultaneamente (cada uma com seu próprio pool).

Ferramentas Populares de Pooling

Dependendo da stack, existem diferentes formas de implementar pooling:

Quando usar um proxy externo como o PgBouncer?

Se você tem, por exemplo, 10 instâncias da sua API rodando (em containers, servidores diferentes, etc.), cada uma com um pool de 20 conexões, isso já representa até 200 conexões simultâneas no banco — mesmo que cada aplicação individualmente esteja "comportada". Um proxy como o PgBouncer fica entre as aplicações e o banco, multiplexando essas conexões de forma muito mais eficiente, permitindo suportar milhares de conexões "lógicas" da aplicação usando um número muito menor de conexões reais no banco.

Resumo

Sem pool Com pool
Nova conexão a cada requisição Conexões reutilizadas
Alta latência por overhead de conexão Latência reduzida
Risco de esgotar conexões do banco Limite controlado e previsível
Sem gerenciamento de conexões mortas Validação e limpeza automáticas

Pools de conexões não são um detalhe de otimização opcional — são uma prática fundamental para qualquer aplicação que acesse banco de dados em produção. A boa notícia é que a maioria dos frameworks e ORMs modernos já vem com pooling embutido e configurado com valores razoáveis por padrão, bastando ajustar min, max e timeouts de acordo com a carga real da sua aplicação.

← voltar ao blog