JavaScript Avançado

Guia Completo de Estratégias de Connection Pooling e Query Optimization em Node.js

7 min de leitura

Guia Completo de Estratégias de Connection Pooling e Query Optimization em Node.js

Connection Pooling em Node.js Connection pooling é uma técnica fundamental para otimizar o uso de conexões com bancos de dados. Em vez de criar uma nova conexão a cada requisição (operação custosa), mantemos um conjunto de conexões reutilizáveis. Isso reduz latência, economiza recursos e melhora a throughput da aplicação. No Node.js, a maioria das bibliotecas de database já implementa pooling nativamente. Vamos explorar com PostgreSQL usando : Configuração Avançada A chave está em encontrar o equilíbrio entre (número máximo de conexões) e consumo de memória. Para aplicações de médio porte, 20-50 conexões costumam ser suficientes. Se sua aplicação está em produção com alta concorrência, monitore métricas: quantas conexões estão ativas vs ociosas. Use e para debug. Ativas: ${pool.totalCount}, Disponíveis: ${pool.availableCount} --- Query Optimization Otimizar queries é onde a maioria ganha performance exponencial. A diferença entre uma query ruim e uma boa pode ser de 100x ou mais. Foque em três pilares: índices, evitar N+1 queries, e prepared statements. Índices e

<h2>Connection Pooling em Node.js</h2>

<p>Connection pooling é uma técnica fundamental para otimizar o uso de conexões com bancos de dados. Em vez de criar uma nova conexão a cada requisição (operação custosa), mantemos um conjunto de conexões reutilizáveis. Isso reduz latência, economiza recursos e melhora a throughput da aplicação.</p>

<p>No Node.js, a maioria das bibliotecas de database já implementa pooling nativamente. Vamos explorar com PostgreSQL usando <code>pg</code>:</p>

<pre><code class="language-javascript">const { Pool } = require(&#039;pg&#039;);

const pool = new Pool({

user: &#039;usuario&#039;,

password: &#039;senha&#039;,

host: &#039;localhost&#039;,

port: 5432,

database: &#039;meu_banco&#039;,

max: 20, // máximo de conexões no pool

idleTimeoutMillis: 30000, // fecha conexões ociosas

connectionTimeoutMillis: 2000,

});

// Usar a conexão

pool.query(&#039;SELECT * FROM usuarios WHERE id = $1&#039;, [1], (err, res) =&gt; {

if (err) console.error(err);

else console.log(res.rows);

});

// Fechar o pool quando a app encerrar

pool.end();</code></pre>

<h3>Configuração Avançada</h3>

<p>A chave está em encontrar o equilíbrio entre <code>max</code> (número máximo de conexões) e consumo de memória. Para aplicações de médio porte, 20-50 conexões costumam ser suficientes. Se sua aplicação está em produção com alta concorrência, monitore métricas: quantas conexões estão ativas vs ociosas. Use <code>pool.totalCount</code> e <code>pool.availableCount</code> para debug.</p>

<pre><code class="language-javascript">setInterval(() =&gt; {

console.log(Ativas: ${pool.totalCount}, Disponíveis: ${pool.availableCount});

}, 5000);</code></pre>

<p>---</p>

<h2>Query Optimization</h2>

<p>Otimizar queries é onde a maioria ganha performance exponencial. A diferença entre uma query ruim e uma boa pode ser de 100x ou mais. Foque em três pilares: índices, evitar N+1 queries, e prepared statements.</p>

<h3>Índices e Explain</h3>

<p>Sempre use <code>EXPLAIN ANALYZE</code> antes de considerar uma query pronta para produção. Isso mostra o plano de execução e se está usando índices corretamente.</p>

<pre><code class="language-sql">EXPLAIN ANALYZE SELECT * FROM usuarios WHERE email = &#039;teste@example.com&#039;;

-- Criar índice se necessário

CREATE INDEX idx_usuarios_email ON usuarios(email);</code></pre>

<p>Em Node.js, pratique isso antes de fazer o deploy:</p>

<pre><code class="language-javascript">async function analisarQuery() {

const resultado = await pool.query(`

EXPLAIN ANALYZE

SELECT u.id, u.nome, COUNT(p.id) as posts

FROM usuarios u

LEFT JOIN posts p ON u.id = p.usuario_id

GROUP BY u.id

`);

console.log(resultado.rows);

}</code></pre>

<h3>Evitar o Problema N+1</h3>

<p>Este é o erro mais comum. Você faz uma query que retorna N linhas, depois faz N queries adicionais em um loop. Solução: use JOINs ou batch loading.</p>

<p><strong>❌ Errado — N+1 queries:</strong></p>

<pre><code class="language-javascript">async function buscarUsuariosComPostsErrado() {

const usuarios = await pool.query(&#039;SELECT * FROM usuarios LIMIT 10&#039;);

for (let user of usuarios.rows) {

const posts = await pool.query(

&#039;SELECT * FROM posts WHERE usuario_id = $1&#039;,

[user.id]

); // 10 queries adicionais!

user.posts = posts.rows;

}

return usuarios.rows;

}</code></pre>

<p></p>

<p>Correto — Uma única query com JOIN:**</p>

<pre><code class="language-javascript">async function buscarUsuariosComPostsCerto() {

const resultado = await pool.query(`

SELECT

u.id, u.nome, u.email,

json_agg(json_build_object(&#039;id&#039;, p.id, &#039;titulo&#039;, p.titulo)) as posts

FROM usuarios u

LEFT JOIN posts p ON u.id = p.usuario_id

GROUP BY u.id

LIMIT 10

`);

return resultado.rows;

}</code></pre>

<p>Isso reduz 10+ queries para exatamente 1. Performance dramática.</p>

<h3>Prepared Statements e Parametrização</h3>

<p>Sempre use placeholders (<code>$1, $2</code>) ao invés de concatenar strings. Isso previne SQL injection e permite que o banco reutilize planos de execução compilados.</p>

<pre><code class="language-javascript"></code></pre>

<p>---</p>

<h2>Monitoramento e Boas Práticas</h2>

<p>Aplicações em produção precisam de observabilidade. Implemente métricas de performance de queries: tempo de execução, quantidade de linhas retornadas, e uso do pool.</p>

<pre><code class="language-javascript">const { performance } = require(&#039;perf_hooks&#039;);

async function queryComMonitoramento(sql, params) {

const inicio = performance.now();

try {

const resultado = await pool.query(sql, params);

const duracao = performance.now() - inicio;

console.log([QUERY] ${duracao.toFixed(2)}ms - ${sql.substring(0, 50)});

return resultado;

} catch (erro) {

console.error([ERRO] Query falhou após ${(performance.now() - inicio).toFixed(2)}ms);

throw erro;

}

}</code></pre>

<h3>Checklist de Otimização</h3>

<ul>

<li>Use índices nas colunas de filtro (WHERE, JOIN, ORDER BY)</li>

<li>Selecione apenas as colunas necessárias (evite SELECT *)</li>

<li>Aggregate dados no banco (GROUP BY, SUM) em vez de na aplicação</li>

<li>Cache resultados que mudam pouco frequentemente</li>

<li>Use transactions (<code>BEGIN/COMMIT</code>) para múltiplas operações relacionadas</li>

<li>Configure timeouts: <code>statement_timeout</code> no PostgreSQL para queries que travam</li>

</ul>

<p>---</p>

<h2>Conclusão</h2>

<p>Connection pooling e query optimization são competências essenciais para desenvolvedores Node.js que lidam com dados. Comece dominando EXPLAIN ANALYZE e eliminando N+1 queries—esses dois pontos podem resolver 80% dos problemas de performance. Em segundo lugar, configure seu pool conscientemente: nem muito pequeno (sufoca requisições) nem muito grande (desperdiça memória). Por fim, meça tudo: implemente logging e monitoramento desde o início, porque otimização sem métricas é apenas adivinhação.</p>

<p>---</p>

<h2>Referências</h2>

<ol>

<li><a href="https://www.postgresql.org/docs/current/sql-explain.html" target="_blank" rel="noopener noreferrer">PostgreSQL Documentation - EXPLAIN</a></li>

<li><a href="https://node-postgres.com/api/pool" target="_blank" rel="noopener noreferrer">node-postgres Pool Documentation</a></li>

<li><a href="https://use-the-index-luke.com/" target="_blank" rel="noopener noreferrer">High Performance Node.js - Use The Index, Luke</a></li>

<li><a href="https://blog.logrocket.com/node-js-database-performance/" target="_blank" rel="noopener noreferrer">Optimization Tips for Node.js + SQL - LogRocket</a></li>

<li><a href="https://www.postgresql.org/docs/current/runtime-config-query.html" target="_blank" rel="noopener noreferrer">PostgreSQL Query Tuning Official Guide</a></li>

</ol>

Comentários

Mais em JavaScript Avançado

Prototype Chain Avançado: Object.create, getPrototypeOf e Herança Real: Do Básico ao Avançado
Prototype Chain Avançado: Object.create, getPrototypeOf e Herança Real: Do Básico ao Avançado

Object.create: A Base da Herança Real é o método fundamental para criar heran...

Performance em React: memo, useMemo, useCallback e Profiler na Prática
Performance em React: memo, useMemo, useCallback e Profiler na Prática

Performance em React: memo, useMemo, useCallback e Profiler React é declarati...

APIs RESTful Avançadas com Express: Versionamento, Rate Limiting e Cache: Do Básico ao Avançado
APIs RESTful Avançadas com Express: Versionamento, Rate Limiting e Cache: Do Básico ao Avançado

Versionamento de APIs RESTful O versionamento é fundamental para manter compa...