Configurar connection pooling para bases de datos y HTTP
Configura connection pooling para PostgreSQL, MySQL, Redis y clientes HTTP en Python, JavaScript y Java. Reduce latencia y evita agotamiento de conexiones.
Visión General
Abrir una nueva conexión de base de datos o HTTP para cada petición es costoso. El handshake TCP, la negociación TLS y la autenticación en la base de datos suman latencia y consumen CPU en ambos lados. El connection pooling mantiene un conjunto reutilizable de conexiones ya establecidas: tu código toma una, ejecuta una consulta o llamada a API, y la devuelve. Obtenés menos latencia, mayor throughput y menos errores de “demasiadas conexiones” bajo carga pico. Vi este patrón salvar servicios que se caían en load tests porque cada petición abría una conexión nueva.
Esta receta recorre los pools de base de datos para PostgreSQL, MySQL y Redis, y después hace lo mismo con el pooling de clientes HTTP en Python, JavaScript y Java. Cada ejemplo incluye la configuración del pool, una consulta o petición real, y el camino de retorno para que las conexiones no se fuguen.
Cuándo Usar
Usá connection pooling cuando tu aplicación abre una conexión nueva por petición y el throughput es bajo, cuando estás recibiendo errores de “demasiadas conexiones” bajo carga, cuando hacés llamadas frecuentes a APIs externas y querés reutilizar TCP, o cuando necesitás limitar la concurrencia para proteger una base de datos o servicio remoto.
Cuándo Evitar
Evitalo cuando un script de corta duración ejecuta una sola consulta y sale, porque el overhead del pool no vale la pena. También evitalo si tu driver o cliente HTTP ya maneja conexiones persistentes de forma transparente, o si corrés en una plataforma serverless con límites estrictos de vida de conexiones.
Solución
Python
import psycopg2
from psycopg2 import pool
import requests
from requests.adapters import HTTPAdapter
# Pool de conexiones PostgreSQL
pg_pool = psycopg2.pool.ThreadedConnectionPool(
minconn=5,
maxconn=20,
host="localhost",
database="app",
user="app",
password="secret"
)
def get_user(user_id: int):
conn = pg_pool.getconn()
try:
with conn.cursor() as cur:
cur.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return cur.fetchone()
finally:
pg_pool.putconn(conn)
# Pool de conexiones HTTP
session = requests.Session()
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=20)
session.mount("https://", adapter)
session.mount("http://", adapter)
resp = session.get("https://api.example.com/data")
JavaScript
const { Pool } = require('pg');
const axios = require('axios');
// Pool de conexiones PostgreSQL
const pgPool = new Pool({
host: 'localhost',
database: 'app',
user: 'app',
password: 'secret',
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
async function getUser(userId) {
const client = await pgPool.connect();
try {
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
return result.rows[0];
} finally {
client.release();
}
}
// Cliente HTTP con keep-alive
const http = require('http');
const https = require('https');
const httpAgent = new http.Agent({ keepAlive: true, maxSockets: 20 });
const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 20 });
const api = axios.create({ httpAgent, httpsAgent });
api.get('https://api.example.com/data')
.then(res => console.log(res.data));
Java
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.*;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import java.util.concurrent.TimeUnit;
// HikariCP — el estándar de oro para pooling en JVM
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost/app");
config.setUsername("app");
config.setPassword("secret");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(2000);
config.setIdleTimeout(30000);
config.addDataSourceProperty("cachePrepStmts", "true");
HikariDataSource ds = new HikariDataSource(config);
int userId = 1;
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
ps.setInt(1, userId);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
// Cliente HTTP con pool acotado
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(20, 5, TimeUnit.MINUTES))
.connectTimeout(5, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
httpClient.newCall(request).execute();
Explicación
Un connection pool es una cola acotada de conexiones TCP ya establecidas. Cuando tu código pide una, el pool entrega una conexión inactiva en lugar de abrir un nuevo socket. Cuando termina el trabajo, la conexión vuelve al pool en lugar de cerrarse, así la mayoría de las operaciones se salta el costo del handshake y la autenticación.
Una vez debugueé un servicio donde la latencia p99 saltaba de 5ms a 80ms todas las mañanas a las 9am. La causa era un pool con minconn=0: los primeros usuarios del día pagaban el costo completo de TCP+TLS+auth en cada petición hasta que el pool se calentaba. Ajustar minconn=5 lo resolvió al instante.
Hay algunas palancas importantes. El tamaño mínimo mantiene conexiones precalentadas para que la primera petición no sea lenta. El tamaño máximo es el techo que evita que el pool sature la base de datos o el servidor remoto. El connection timeout decide cuánto espera el llamador antes de rendirse. El idle timeout cierra conexiones que llevan inactivas demasiado tiempo. El max lifetime obliga al pool a reemplazar conexiones viejas antes de que se pongan stale, lo cual también ayuda cuando rotás credenciales de base de datos.
Para clientes HTTP, keep-alive reutiliza la conexión TCP subyacente entre peticiones al mismo host,
ahorrando el handshake TLS en cada llamada. Si la base de datos sigue siendo el cuello de botella después
de ajustar el pool, los siguientes pasos suelen ser consultas y esquema. Consultá
database indexing y query optimization.
Variantes
Para PostgreSQL, las opciones comunes son psycopg2.pool en Python, pg en Node.js y HikariCP en
la JVM. Los tres ofrecen pools threaded o async, y la clave es ajustar el tamaño del pool a tu
concurrencia real en lugar de copiar los defaults.
MySQL usa mysql-connector-python, mysql2 o HikariCP. Las dos settings a vigilar son
wait_timeout del lado del servidor y max_connections, porque un pool que abre demasiadas
conexiones va a chocar con el límite del servidor y empezar a fallar.
Redis es lo suficientemente rápido como para que muchos salteen el pooling, pero redis-py,
ioredis y Lettuce incluyen connection pools por una buena razón. En alta concurrencia, una sola
conexión compartida se vuelve un cuello de botella, y un pool chico (5 a 10 conexiones) mantiene
el throughput estable.
Para clientes HTTP, el enfoque varía según el lenguaje. En Python, requests.Session con
HTTPAdapter te permite setear pool_maxsize por host. En Node.js, axios combinado con
http.Agent te da maxSockets para controlar conexiones paralelas. En Java, ConnectionPool
de OkHttp y Apache HttpClient incluyen límites por ruta listos para usar.
Mejores Prácticas
Dimensioná el pool según tu concurrencia real, no la cantidad de núcleos. Empezá con la cantidad de
workers concurrentes más un pequeño margen y ajustá desde ahí. Siempre liberá conexiones en un bloque
finally para que vuelvan al pool incluso si hay una excepción. Mantené connectionTimeout menor que el
timeout total de la petición de tu aplicación para fallar rápido en lugar de quedarse colgado. Monitoreá
conexiones activas, inactivas, en espera y totales; si se encolan, el pool es chico o la base de datos
está saturada. Activá el cache de prepared statements a nivel de pool cuando esté disponible, como
cachePrepStmts de HikariCP. Usá TLS para conexiones de base de datos y HTTP entre servicios, pero
mantené un idle timeout razonable para cerrar sockets viejos. Para una estrategia general de rendimiento,
consultá la guía de optimización de
performance.
Monitoreo de la salud del pool
La mayoría de las librerías de pool exponen métricas que podés scrapear y alertar. HikariCP expone
conteos de activas, inactivas y en espera vía JMX o su HikariPoolMXBean. En Python, psycopg2.pool
no expone métricas directamente, así que llevá el conteo de borrows y returns en un wrapper. En Node.js,
pg.Pool expone pool.totalCount, pool.idleCount y pool.waitingCount. Configurá alertas sobre
threads en espera que crecen con el tiempo, porque eso significa que el pool es chico o la base de
datos está lenta. Detectá leaks comparando conexiones activas antes y después de un batch de
peticiones: si las activas siguen subiendo, algún código se olvidó de devolver la conexión.
Dimensionamiento en cloud y serverless
En entornos contenerizados, dimensioná el pool al límite de concurrencia del contenedor, no a la cantidad de núcleos del host. Un pod de Kubernetes con 2 núcleos y 4 peticiones concurrentes necesita un pool de 5 o 6, no de 20. Lo aprendi por las malas despues de un deployment donde cada pod abria 20 conexiones a una base de datos que solo podia manejar 100 en total. En plataformas serverless como AWS Lambda o Cloud Run, los pools son complicados porque cada instancia es de corta duración y comparte la base de datos con muchas otras instancias. Considerá un proxy como PgBouncer o RDS Proxy delante de la base de datos para agrupar y compartir conexiones entre muchas instancias.
Errores Comunes
El más grande es no liberar conexiones: una que no se devuelve eventualmente vacía el pool y bloquea
todas las peticiones. Un pool size de 1 serializa todo el acceso a base de datos y mata el throughput. Un
pool demasiado grande puede saturar el límite max_connections de la base de datos y gastar memoria.
Ignorar idle timeouts produce conexiones stale y fallos silenciosos. Desactivar HTTP keep-alive desperdicia
milisegundos y CPU reabriendo TLS en cada petición externa. Compartir un mismo pool entre bases de datos
no relacionadas acopla tráfico y hace imposible ajustarlo; usá un pool por base de datos y por instancia
de aplicación.
Resumen
Connection pooling mantiene un conjunto reutilizable de conexiones TCP para que tu código tome una,
ejecute una consulta o llamada a API, y la devuelva. El tamaño del pool, el connection timeout, el
idle timeout y el max lifetime son las cuatro palancas que importan. Dimensioná el pool según la
concurrencia real, no los núcleos de CPU. Siempre liberá conexiones en un bloque finally. Mantené
connectionTimeout menor que el timeout de tu petición para fallar rápido. Para HTTP, activá
keep-alive y acotá maxSockets a tu concurrencia. Cuando la base de datos sigue siendo el cuello de
botella después de ajustar el pool, pasá a database indexing y
query optimization.
See Also
Para el lado JVM, la documentación de HikariCP es la
referencia a la que siempre vuelvo. Si estás en Python, el
módulo pool de psycopg2 explica las opciones de pool
threaded y async. Los desarrolladores de Node.js deberían leer la
API Pool de node-postgres, que expone totalCount,
idleCount y waitingCount. Para pooling HTTP en Java, el
ConnectionPool de OkHttp
está bien documentado. Del lado HTTP en Node.js, la
documentación de axios cubre el keep-alive basado en agents
en detalle.
Preguntas frecuentes
¿Cuál es el tamaño óptimo del pool?
Para cargas OLTP, empezá con (núcleos * 2) + discos_efectivos. En entornos cloud o contenerizados, igualá
el tamaño del pool a la concurrencia de la aplicación, no a los núcleos de CPU. Monitoreá métricas de
waiting y aumentá solo si las conexiones empiezan a encolarse.
¿Debo usar un pool o varios?
Un pool por base de datos por instancia de aplicación es lo usual. Varios pools a la misma base de datos fragmentan recursos y reducen eficiencia. En microservicios, dale a cada servicio su propio pool.
¿Cómo manejo el agotamiento del pool?
Mantené connectionTimeout corto para que las peticiones fallen rápido en lugar de colgarse
indefinidamente. Agregá circuit breakers o reintentos con backoff. Monitoreá la saturación del pool y
escalá la base de datos o los workers antes de que el agotamiento sea crítico.
¿Y el pooling de conexiones HTTP?
HTTP keep-alive y pools acotados permiten reutilizar conexiones TLS con el mismo host. Paga cuando tu
servicio llama repetidamente a las mismas pocas APIs de bajada. Configurá pool_maxsize o maxSockets
suficiente para tu concurrencia, pero no tanto que agotes los puertos efímeros.
¿Cómo monitoreo la salud del pool?
Seguí cuatro métricas: conexiones activas, inactivas, threads en espera y totales. La mayoría de las
librerías de pool las exponen vía JMX (HikariCP), pool.status() (psycopg2) o pgPool.totalCount
(node-pg). Configurá alertas sobre threads en espera: si ese número crece, el pool es chico o la
base de datos está lenta. También vigilá los leaks: si las conexiones activas siguen subiendo sin
volver a inactivas, algún código se olvidó de llamar putconn o release.
Recursos Relacionados
Optimización de Rendimiento SQL
Una guía práctica para optimizar consultas SQL: estrategias de indexación, reescritura de queries, análisis de EXPLAIN plans y anti-patrones comunes a evitar.
GuideGuía de Optimización de Performance Web
manual detallado para optimizar el rendimiento de aplicaciones web con mejores Core Web Vitals y experiencia de usuario.
RecipeOptimizar consultas con índices de base de datos
Cómo crear, analizar y mantener índices para acelerar las consultas y evitar los errores de indexación más comunes.
RecipeOptimizar Queries Lentas de Base de Datos
Cómo identificar, analizar y corregir queries SQL lentos usando EXPLAIN, refactoring de queries y técnicas de optimización específicas por base de datos.
PatternPatrón Cache-Aside
Carga datos en el caché bajo demanda desde el almacenamiento principal. Un patrón de caché que da a la aplicación control total sobre qué y cuándo cachear.
RecipePatrones de Cache de Redis para Aplicaciones de Alto
Como implementar patrones cache-aside, write-through y write-behind con Redis para reducir carga de base de datos y mejorar tiempos de respuesta