Erro too many connections ou PostgreSQL consumindo RAM com conexões ociosas? Configure pool de conexões com PgBouncer ou ProxySQL passo a passo.
Para que serve
Aplicações em PHP, Node.js ou com muitos workers abrem centenas de conexões ao banco, a maioria ociosa. No PostgreSQL, cada conexão é um processo que consome memória e CPU de gerenciamento; no MySQL, conexões demais levam ao erro Too many connections e à disputa por recursos. Um pool de conexões fica no meio do caminho: recebe milhares de conexões dos clientes e as atende com poucas conexões reais ao banco. Este artigo configura o PgBouncer para PostgreSQL e o ProxySQL para MySQL/MariaDB.
Pré-requisitos
- Acesso root à máquina onde o pool vai rodar. O mais simples é rodar no mesmo servidor/VM do banco ou no servidor da aplicação.
- Permissão para mudar a string de conexão da aplicação (nova porta).
- Debian 12/13 ou Ubuntu 24.04. Para PgBouncer recente (1.21 ou mais novo, com suporte a prepared statements no modo transação), use o repositório PGDG se a versão da distribuição for antiga.
Parte A: pool de conexões PgBouncer para PostgreSQL
1. Instale
apt install pgbouncer
pgbouncer --version
2. Gere o arquivo de usuários
O PgBouncer precisa validar a senha do cliente. Com senhas SCRAM (padrão do PostgreSQL 14 em diante), copie o hash do banco para o userlist.txt:
sudo -u postgres psql -At -c "SELECT format('\"%s\" \"%s\"', rolname, rolpassword)
FROM pg_authid WHERE rolname IN ('app','pgb_admin');" > /etc/pgbouncer/userlist.txt
chown postgres:postgres /etc/pgbouncer/userlist.txt
chmod 640 /etc/pgbouncer/userlist.txt
app é o usuário da aplicação e pgb_admin um usuário criado só para administrar o PgBouncer (CREATE ROLE pgb_admin LOGIN PASSWORD '...';). Se a senha mudar no banco, gere o arquivo de novo. Para muitos usuários, a alternativa é auth_query, que consulta o banco a cada login.
3. Configure o /etc/pgbouncer/pgbouncer.ini
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
admin_users = pgb_admin
pool_mode = transaction
max_client_conn = 2000
default_pool_size = 20
reserve_pool_size = 5
max_prepared_statements = 200
server_idle_timeout = 600
pool_mode = transaction: a conexão real volta ao pool ao fim de cada transação. É o modo que mais economiza.default_pool_size: conexões reais por par usuário/banco. Comece com 2 a 4 vezes o número de núcleos disponíveis ao banco.max_prepared_statements: permite prepared statements de protocolo no modo transação (PgBouncer 1.21+; a partir do 1.24 já vem ligado com 200).- Se a aplicação estiver em outra VM, troque
listen_addrpelo IP privado e libere a porta 6432 só para ela.
systemctl restart pgbouncer
systemctl status pgbouncer
4. Aponte a aplicação
Troque a porta de 5432 para 6432 na string de conexão. Exemplo: postgresql://app:senha@127.0.0.1:6432/appdb.
SET fora de transação, LISTEN/NOTIFY, advisory locks de sessão, tabelas temporárias que atravessam transações e WITH HOLD cursors. Se a aplicação usar isso, crie um segundo banco no [databases] com pool_mode=session para essa parte, ou conecte-a direto ao PostgreSQL. Migrações de schema (ORMs) também devem conectar direto.5. Como saber se o PgBouncer funcionou
psql -h 127.0.0.1 -p 6432 -U pgb_admin pgbouncer -c "SHOW POOLS;"
psql -h 127.0.0.1 -p 6432 -U pgb_admin pgbouncer -c "SHOW STATS;"
Em SHOW POOLS, cl_active são clientes conectados, sv_active/sv_idle as conexões reais e cl_waiting os clientes esperando conexão. cl_waiting sempre acima de zero indica pool pequeno ou consultas lentas. No PostgreSQL, SELECT count(*) FROM pg_stat_activity; deve mostrar bem menos conexões que antes.
Parte B: ProxySQL para MySQL e MariaDB
O ProxySQL faz pool de conexões e, além disso, roteia consultas: escrita no primário e leitura nas réplicas (veja replicação MySQL/MariaDB com GTID).
1. Instale e troque a senha de administração
Baixe o pacote .deb da versão estável na página de releases do projeto (github.com/sysown/proxysql) ou use o repositório oficial, e instale com apt install ./proxysql_*.deb. A administração é feita por SQL na porta 6032:
systemctl enable --now proxysql
mysql -u admin -padmin -h 127.0.0.1 -P 6032 --prompt='Admin> '
UPDATE global_variables SET variable_value='admin:TroqueEstaSenha'
WHERE variable_name='admin-admin_credentials';
LOAD ADMIN VARIABLES TO RUNTIME; SAVE ADMIN VARIABLES TO DISK;
admin/admin só aceita conexões locais, mas troque-a mesmo assim e nunca exponha as portas 6032/6033 na internet.2. Cadastre servidores, usuário de monitoramento e usuário da aplicação
No MySQL primário, crie CREATE USER 'monitor'@'10.0.0.%' IDENTIFIED BY '...'; GRANT USAGE, REPLICATION CLIENT ON *.* TO 'monitor'@'10.0.0.%';. No admin do ProxySQL:
UPDATE global_variables SET variable_value='monitor' WHERE variable_name='mysql-monitor_username';
UPDATE global_variables SET variable_value='SenhaMonitor' WHERE variable_name='mysql-monitor_password';
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES
(10,'10.0.0.11',3306), (20,'10.0.0.12',3306);
INSERT INTO mysql_replication_hostgroups (writer_hostgroup, reader_hostgroup, comment)
VALUES (10, 20, 'cluster1');
INSERT INTO mysql_users (username, password, default_hostgroup)
VALUES ('app','SenhaDaApp',10);
INSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup, apply)
VALUES (1,1,'^SELECT.*FOR UPDATE',10,1), (2,1,'^SELECT',20,1);
LOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK;
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;
LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;
O ProxySQL usa o read_only de cada servidor para decidir quem é escritor (hostgroup 10) e quem é leitor (20). Se você não tiver réplica, cadastre só o servidor no hostgroup 10 e não crie as regras de consulta.
3. Aponte a aplicação e verifique
A aplicação conecta na porta 6033 do ProxySQL com o usuário app. Confira:
SELECT hostgroup, srv_host, status, ConnUsed, ConnFree, Queries
FROM stats_mysql_connection_pool;
SELECT hostgroup, digest_text, count_star
FROM stats_mysql_query_digest ORDER BY count_star DESC LIMIT 10;
Os servidores devem aparecer como ONLINE e as leituras devem cair no hostgroup 20.
Problemas comuns
PgBouncer: "password authentication failed" ou "wrong password type"
O hash no userlist.txt está desatualizado ou é MD5 enquanto o auth_type é SCRAM. Gere o arquivo novamente e confirme password_encryption = scram-sha-256 no PostgreSQL.
PgBouncer: "prepared statement does not exist"
Versão antiga do PgBouncer em modo transação. Atualize para 1.21+ e defina max_prepared_statements, ou desative prepared statements no driver.
ProxySQL: usuário não autentica no MySQL 8.4
O padrão do MySQL 8.4 é caching_sha2_password. Use uma versão recente do ProxySQL (2.6 ou mais nova tem suporte) e confira se a senha em mysql_users é a mesma do banco.
Perguntas frequentes
Basta aumentar max_connections em vez de usar pool?
Funciona até certo ponto, mas cada conexão consome memória e o desempenho cai com centenas de conexões ativas disputando CPU. O pool resolve a causa.
Onde rodar o PgBouncer: no banco ou na aplicação?
Ambos funcionam. No servidor do banco, todas as aplicações compartilham o mesmo pool; no da aplicação, você economiza viagens de rede para abrir conexões.
O ProxySQL serve para PostgreSQL?
Versões recentes (3.x) trazem suporte inicial a PostgreSQL, mas para PostgreSQL o PgBouncer continua sendo a escolha mais simples e madura.
Leitura complementar
- PgBouncer: configuração
- PgBouncer: recursos e limitações por modo de pool
- ProxySQL: Read/Write Split
Precisa de ajuda?
Se o número de conexões estiver esgotando memória do servidor físico, abra um ticket com a saída de free -h e o número de conexões ativas do banco.
