Configurar connection pooling para bases de datos y clientes HTTP
Cómo configurar connection pooling para bases de datos y clientes HTTP para mejorar rendimiento y confiabilidad
Visión General
Abrir una nueva conexión de base de datos o HTTP para cada petición es costoso. El connection pooling mantiene un conjunto reutilizable de conexiones ya establecidas, reduciendo drásticamente la latencia y previniendo el agotamiento de recursos bajo carga. La mayoría de los incidentes en producción relacionados con “demasiadas conexiones” se resuelven con una configuración adecuada del pool.
Esta receta cubre connection pooling de base de datos con PostgreSQL, MySQL y Redis, más pooling de clientes HTTP para llamadas a APIs externas.
Cuándo Usar
Usa este recurso cuando:
- Tu aplicación abre una conexión nueva por petición y el throughput es bajo
- Recibes errores de “demasiadas conexiones” bajo carga
- Haces llamadas HTTP frecuentes a APIs externas y quieres reutilizar conexiones TCP
- Necesitas ajustar los límites de concurrencia para un servicio web o worker
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 httpAgent = new (require('http').Agent)({ keepAlive: true, maxSockets: 20 });
const httpsAgent = new (require('https').Agent)({ keepAlive: true, maxSockets: 20 });
const api = axios.create({ httpAgent, httpsAgent });
Java
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.*;
// 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);
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 pooling (Java 11+)
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
Explicación
El connection pooling funciona manteniendo una cola acotada de conexiones TCP ya establecidas. Cuando tu código solicita una conexión, el pool entrega una inactiva en lugar de abrir un nuevo socket. Cuando la operación termina, la conexión vuelve al pool en lugar de cerrarse.
Parámetros clave del pool:
- min connections: Conexiones precalentadas listas al inicio
- max connections: Tope máximo para proteger la base de datos o servidor remoto
- connection timeout: Cuánto esperar por una conexión disponible antes de fallar
- idle timeout: Cuánto mantener una conexión inactiva abierta antes de cerrarla
Para clientes HTTP, keep-alive reutiliza la conexión TCP subyacente entre múltiples peticiones al mismo host, eliminando el overhead del handshake TLS en cada llamada.
Variantes
| Tecnología | Enfoque | Notas |
|---|---|---|
| PostgreSQL | psycopg2.pool / pg / HikariCP | ThreadedConnectionPool para threads, AsyncConnectionPool para asyncio |
| MySQL | mysql-connector-python / mysql2 / HikariCP | Mismos conceptos de pool; cuidado con wait_timeout del servidor |
| Redis | redis-py connection pool / ioredis / Lettuce | Redis es rápido, pero el pool sigue siendo importante en alta concurrencia |
| HTTP (Python) | requests Session + HTTPAdapter | pool_maxsize controla conexiones por host |
| HTTP (Node) | axios + http.Agent | maxSockets controla conexiones paralelas |
| HTTP (Java) | Apache HttpClient / OkHttp | Connection managers integrados con límites por ruta |
Mejores Prácticas
- Ajusta
max pool sizeaproximadamente al número de workers concurrentes (threads, procesos o concurrencia del event loop) - Siempre usa
release()oputconn()en un bloquefinallypara evitar fugas - Configura
connectionTimeoutmenor que el timeout total de la petición de tu aplicación - Monitorea métricas del pool: activas, inactivas, en espera y totales
- Usa cache de prepared statements a nivel de pool cuando esté disponible (ej. HikariCP
cachePrepStmts)
Errores Comunes
- No liberar conexiones — siempre devuélvelas al pool, incluso ante excepciones
- Pool size = 1 — serializa todo el acceso a base de datos y mata el throughput
- Pool demasiado grande — puede saturar la base de datos con límites de
max_connections - Ignorar idle timeouts — conexiones stale causan fallos silenciosos o sockets semiabiertos
- Sin HTTP keep-alive — reabrir TLS en cada petición externa desperdicia milisegundos
Preguntas Frecuentes
¿Cuál es el tamaño óptimo del pool?
Un buen punto de partida es (núcleos * 2) + discos_efectivos para cargas OLTP. Para bases de datos en la nube, iguala el tamaño del pool a la concurrencia de la aplicación, no a los núcleos de CPU. Monitorea métricas de waiting y aumenta solo si las conexiones se encolan.
¿Debo usar un pool o varios?
Un pool por base de datos por instancia de aplicación es el estándar. Crear múltiples pools a la misma base de datos fragmenta recursos y reduce eficiencia. Para microservicios, cada servicio gestiona su propio pool.
¿Cómo manejo el agotamiento del pool?
Configura un connectionTimeout razonable para que las peticiones fallen rápido en lugar de colgarse indefinidamente. Agrega circuit breakers o reintentos con backoff. Monitorea la saturación del pool y escala la base de datos o los workers antes de que el agotamiento sea crítico.
Recursos Relacionados
SQL Performance Tuning — Indexes, Queries, and Explain Plans
A practical guide to optimizing SQL queries: indexing strategies, query rewriting, EXPLAIN plan analysis, and common anti-patterns to avoid.
GuideWeb Performance Optimization Guide
A comprehensive guide to optimizing web application performance for better Core Web Vitals and user experience.
RecipeImplement CDN Edge Caching
Configure content delivery networks with edge caching rules, cache invalidation, and geographic optimization for static and dynamic content.
RecipeDebounce and Throttle
How to implement debounce and throttle patterns to control function execution frequency for search inputs, scroll handlers, and API calls.
PatternCache-Aside Pattern
Load data into the cache on demand from the backing store. A caching pattern that gives the application full control over what and when to cache.