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:
- Handshake TCP — estabelecer a conexão de rede em si (3-way handshake)
- Negociação SSL/TLS (se estiver usando conexão criptografada)
- Autenticação — enviar usuário/senha e o banco validar as credenciais
- 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:
- Pega emprestada uma conexão já existente do pool
- Usa essa conexão para executar a query
- 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:
- Testam se uma conexão ainda está "viva" antes de entregá-la para uso
- Fecham conexões ociosas há muito tempo, liberando recursos
- Tentam reconectar automaticamente em caso de queda de rede ou reinício do banco
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:
- Node.js:
pg-pool(embutido no driverpg), Prisma, TypeORM, Knex - Python: SQLAlchemy (pool embutido), psycopg2's
SimpleConnectionPool - Java: HikariCP (o mais popular atualmente), Apache Commons DBCP
- Proxies externos: PgBouncer e PgPool-II (para PostgreSQL) — úteis quando você tem múltiplas instâncias da aplicação, cada uma com seu próprio pool, e quer um controle centralizado de conexões no nível de infraestrutura
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.