Pooling de Conexiones a Base de Datos: Guía Práctica
Guía práctica sobre pooling de conexiones de base de datos: dimensionar pools, manejar timeouts de inactividad, detectar fugas, y configurar HikariCP, PgBouncer y pools nativos en la nube para máximo throughput.
Descripción General
Las conexiones de base de datos son costosas de crear. Cada conexión requiere un handshake TCP, negociación TLS cuando el cifrado está activo, autenticación, asignación de memoria y un fork de proceso backend en el servidor. En un PostgreSQL típico eso cuesta desde unos pocos hasta decenas de milisegundos — multiplicado por cada consulta, destruye el rendimiento bajo carga. El pooling de conexiones reutiliza conexiones establecidas, reduciendo ese overhead a casi cero mientras previene el agotamiento de conexiones.
Esta guía cubre dimensionamiento, configuración, monitoreo y troubleshooting de pools tanto a nivel de aplicación (HikariCP, SQLAlchemy, node-postgres) como proxies de middleware (PgBouncer, ProxySQL).
Cuándo Usar
- Tu aplicación abre más de 10 conexiones concurrentes a base de datos
- Ves errores de
too many connectionsbajo carga - El tiempo de establecimiento de conexión excede el 5% del tiempo total de consulta
- Tu servidor de base de datos tiene cientos o miles de conexiones inactivas
- Ejecutas una arquitectura de microservicios donde cada servicio se conecta a bases compartidas
- Quieres limitar el uso de recursos de base de datos por aplicación o usuario
Conceptos Clave
| Concepto | Descripción |
|---|---|
| Pool | Una colección de conexiones de base de datos reutilizables |
| Tamaño Mínimo del Pool | Conexiones mantenidas listas incluso cuando están inactivas |
| Tamaño Máximo del Pool | Límite superior de conexiones que el pool creará |
| Timeout de Conexión | Tiempo de espera para obtener una conexión disponible del pool |
| Timeout de Inactividad | Cuánto tiempo una conexión inactiva permanece abierta antes de cerrarse |
| Detección de Fugas | Identificar código que adquiere conexiones sin liberarlas |
El Problema de Conexión
Sin pooling, cada petición paga el costo completo de conexión; con pooling, el handshake ocurre una vez y la conexión queda caliente para reutilizarse.
Implementación de Pooling de Conexiones Paso a Paso
1. Dimensiona tu Pool Correctamente
La configuración más importante es el tamaño del pool. Demasiado pequeño = peticiones bloqueadas. Demasiado grande = memoria desperdiciada y contención en base de datos.
Fórmula para Tamaño Óptimo de Pool
El punto de partida clásico viene de la wiki de PostgreSQL:
conexiones = ((núcleos * 2) + discos_efectivos)
Para PostgreSQL en un servidor de 16 núcleos con SSD:
conexiones = (16 * 2) + 1 = 33 conexiones para throughput máximo
Tómalo como heurística, no como regla — discos_efectivos vale 1 con SSD/NVMe y aproximadamente la cantidad de discos en almacenamiento mecánico. Valida contra métricas de latencia reales antes de dejarlo fijo.
Ejemplo Trabajado
Digamos que tu PostgreSQL maneja cómodamente 300 conexiones (por debajo de su max_connections de 500, dejando espacio para admin y replicación). Corres 8 instancias del servicio API más 2 workers de batch:
presupuesto = 300 conexiones
instancias API: 8 × maxPoolSize 30 = 240
workers batch: 2 × maxPoolSize 15 = 30
total: 270 de 300 — cabe, con un margen pequeño
Si la misma base tuviera que servir 20 instancias, la cuenta deja de funcionar: 20 × 30 = 600 > 300. Ahí es cuando pones PgBouncer delante y achicas cada pool de app a 3-5 — el proxy embute 600 conexiones cliente sobre ~25 conexiones reales al servidor en modo transaction.
Dimensionamiento de Pool por Servicio
| Escenario | Tamaño Máximo del Pool | Razonamiento |
|---|---|---|
| Servicio pequeño (2 instancias) | 10-15 | Compartir un límite pequeño de conexiones de base de datos |
| Servicio mediano (5 instancias) | 5-10 | Tamaño de pool × instancias ≤ límite de base de datos |
| Servicio grande (20+ instancias) | 3-5 | Muchas instancias, pools pequeños, usar PgBouncer |
| Worker de batch | 2-5 | Pocas operaciones concurrentes, conexiones largas |
| API en tiempo real | 10-20 | Muchas peticiones cortas, respuesta rápida |
# Ejemplo: Configuración de HikariCP (Java)
spring:
datasource:
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 300000 # 5 minutos
max-lifetime: 1200000 # 20 minutos
connection-timeout: 30000 # 30 segundos
leak-detection-threshold: 60000 # 60 segundos
pool-name: OrderServicePool
# Ejemplo: Configuración de pool de SQLAlchemy (Python)
from sqlalchemy import create_engine
engine = create_engine(
"postgresql://user:pass@localhost/db",
pool_size=10, # Conexiones mínimas mantenidas
max_overflow=5, # Conexiones extra más allá de pool_size
pool_timeout=30, # Segundos esperando conexión disponible
pool_recycle=1800, # Reciclar conexiones después de 30 minutos
pool_pre_ping=True, # Verificar salud de conexión antes de usar
echo=False
)
// Ejemplo: Pool de node-postgres (Node.js)
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
database: 'app',
user: 'app_user',
password: process.env.DB_PASSWORD,
max: 20, // Máximo de conexiones
idleTimeoutMillis: 300000, // Cerrar conexiones inactivas después de 5 min
connectionTimeoutMillis: 10000, // Timeout adquiriendo conexión
allowExitOnIdle: true // Permitir salida del proceso cuando pool inactivo
});
2. Configura el Comportamiento del Pool
Ajusta cómo el pool gestiona conexiones:
| Configuración | Qué Controla | Valor Recomendado |
|---|---|---|
| minIdle | Conexiones mantenidas calientes | 20-50% de maxPoolSize |
| maxLifetime | Edad máxima de conexión | 15-30 minutos (menor que timeout de DB) |
| idleTimeout | Cuánto tiempo permanecen abiertas conexiones inactivas | 5-10 minutos |
| connectionTimeout | Tiempo esperando conexión disponible | 10-30 segundos |
| validationTimeout | Timeout de health check | 2-5 segundos |
| leakDetectionThreshold | Advertir si conexión se mantiene demasiado | 30-60 segundos |
// Ejemplo: Configuración avanzada de HikariCP
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://db:5432/app");
config.setUsername("app");
config.setPassword(System.getenv("DB_PASSWORD"));
// Dimensionamiento de pool
config.setMinimumIdle(5);
config.setMaximumPoolSize(20);
// Timeouts
config.setConnectionTimeout(30000); // 30s espera máxima
config.setIdleTimeout(600000); // 10m cierre inactivo
config.setMaxLifetime(1800000); // 30m edad máxima
config.setValidationTimeout(5000); // 5s health check
// Detección de fugas
config.setLeakDetectionThreshold(60000); // 60s umbral de advertencia
// Rendimiento
config.setAutoCommit(false); // Usar transacciones explícitas
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config);
Por Qué Importan Estos Settings
- minIdle: Previene latencia de creación de conexión durante picos de tráfico
- maxLifetime: Previene conexiones obsoletas y trabaja alrededor de firewalls que eliminan TCP inactivo
- idleTimeout: Cierra conexiones no usadas para liberar recursos de base de datos
- connectionTimeout: Falla rápido en lugar de quedar colgado indefinidamente
- leakDetectionThreshold: Atrapa código que olvida cerrar conexiones
3. Usa Pooling de Conexiones en Middleware
Cuando tienes muchas instancias de aplicación, usa un proxy de pool de conexiones:
# Ejemplo: Configuración de PgBouncer
[databases]
app_db = host=primary.db port=5432 dbname=app
[pgbouncer]
listen_port = 6432
listen_addr = 0.0.0.0
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
# Modos de pool:
# session = conexión fijada hasta que el cliente se desconecta (default, más seguro)
# transaction = conexión devuelta al pool después de cada transacción (mejor compartir)
# statement = conexión devuelta después de cada statement (más agresivo)
pool_mode = transaction
# Límites de conexiones
max_client_conn = 10000
default_pool_size = 25
min_pool_size = 10
reserve_pool_size = 5
reserve_pool_timeout = 3
# Timeouts
server_idle_timeout = 600
server_lifetime = 3600
server_connect_timeout = 15
# Ejemplo: Configuración de ProxySQL
mysql_servers =
(
{ hostgroup_id=1, hostname="primary.db", port=3306, weight=1 },
{ hostgroup_id=2, hostname="replica1.db", port=3306, weight=1 },
{ hostgroup_id=2, hostname="replica2.db", port=3306, weight=1 }
)
mysql_query_rules =
(
{ rule_id=1, active=1, match_pattern="^SELECT", destination_hostgroup=2, apply=1 },
{ rule_id=2, active=1, match_pattern="^SELECT.*FOR UPDATE", destination_hostgroup=1, apply=1 }
)
Modos de Pool Explicados
| Modo | Comportamiento | Mejor Para |
|---|---|---|
| Session | Conexión retenida por toda la sesión del cliente | Prepared statements, variables de sesión |
| Transaction | Conexión devuelta después de COMMIT/ROLLBACK | La mayoría de aplicaciones web (recomendado) |
| Statement | Conexión devuelta después de cada statement | Stateless, consultas simples (raramente usado) |
Dos detalles operativos que conviene saber antes de producción: PgBouncer autentica contra auth_file (un userlist.txt con usuarios y hashes SCRAM — genéralo desde pg_authid en vez de escribirlo a mano), y termina TLS él mismo, así que ponlo en el mismo host o segmento de red que la app y restringe listen_addr acorde. Exponer PgBouncer en 0.0.0.0 sin regla de firewall es una forma común de publicar tu base de datos entera en internet por accidente.
4. Detecta y Corrige Fugas de Conexión
Las fugas de conexión son el problema de producción más común relacionado con pools:
// MAL: La conexión nunca se cierra si hay excepción
public User getUser(String id) {
Connection conn = dataSource.getConnection();
ResultSet rs = conn.prepareStatement("SELECT * FROM users WHERE id = ?")
.executeQuery();
// Si hay excepción aquí, la conexión nunca se retorna!
return mapUser(rs);
// Falta: conn.close()
}
// BIEN: Try-with-resources (Java)
public User getUser(String id) {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
ps.setString(1, id);
try (ResultSet rs = ps.executeQuery()) {
return mapUser(rs);
}
} // Auto-cerrado
}
# MAL: La conexión no se libera en excepción
def get_user(user_id):
conn = engine.connect()
result = conn.execute("SELECT * FROM users WHERE id = %s", user_id)
user = result.fetchone()
return user # Conexión nunca cerrada!
# BIEN: Context manager (Python/SQLAlchemy)
def get_user(user_id):
with engine.connect() as conn:
result = conn.execute(text("SELECT * FROM users WHERE id = :id"), {"id": user_id})
return result.fetchone()
# Conexión auto-devuelta al pool
Estrategias de Detección de Fugas
| Enfoque | Cómo | Cuándo |
|---|---|---|
| Logging de pool | leakDetectionThreshold de HikariCP | Desarrollo y staging |
| Wrapping de conexión | Rastrear trazas de pila de adquisición/liberación | Depurar fugas específicas |
| Tracing APM | Métricas de conexión de Datadog, New Relic | Monitoreo en producción |
| Basado en timeout | Cerrar conexiones fugadas después de N minutos | Red de seguridad en producción |
Atrapando la Fuga en un Stack Trace
Con leakDetectionThreshold habilitado, HikariCP registra el stack trace del código que tomó la conexión y nunca la devolvió. La traza apunta al sitio de adquisición, no a donde debió cerrarse — busca el primer frame de tu propio código y sigue el camino de llamada hasta encontrar el close() o bloque with faltante. Un infractor clásico es un helper que devuelve un ResultSet o Stream manteniendo la conexión abierta después de que el método retorna: la corrección es consumir los resultados dentro del scope gestionado, no fuera.
5. Monitorea la Salud del Pool
Rastrea métricas de pool para detectar problemas antes de que causen caídas:
# Ejemplo: Métricas Prometheus para HikariCP
# Estas se exponen automáticamente vía Micrometer en Spring Boot
# Métricas clave:
# hikaricp_connections_active - Conexiones actualmente en uso
# hikaricp_connections_idle - Conexiones disponibles en pool
# hikaricp_connections_pending - Hilos esperando conexión
# hikaricp_connections_timeout_total - Eventos de timeout
# hikaricp_connections_usage_seconds - Histograma de uso de conexión
# Ejemplo: Monitoreo de pool personalizado (Python)
from prometheus_client import Gauge, Counter
pool_active = Gauge('db_pool_connections_active', 'Conexiones activas')
pool_idle = Gauge('db_pool_connections_idle', 'Conexiones inactivas')
pool_waiters = Gauge('db_pool_waiters', 'Hilos esperando conexión')
pool_timeouts = Counter('db_pool_timeouts_total', 'Timeouts de conexión')
def monitor_pool(pool):
pool_active.set(pool.size())
pool_idle.set(pool.maxsize - pool.size())
# Alertar si waiters > 0 o active == max por > 30s
Grafica tres series por servicio: conexiones activas, waiters pendientes y latencia de adquisición. Waiters por encima de cero con activas en el máximo significa que el pool es el cuello de botella; activas en el máximo con cero waiters significa que está justo en capacidad — déjalo quieto. Combínalo con el conteo propio de pg_stat_activity de la base para ver ambos lados del cuello de botella.
Revisa También el Lado de la Base
Las métricas del pool muestran la vista del cliente. La base de datos ve la otra mitad — ejecuta esto cuando los números no cuadren:
-- Conexiones activas vs inactivas ahora mismo
SELECT state, COUNT(*)
FROM pg_stat_activity
WHERE datname = 'app'
GROUP BY state;
-- Sentencias que más tiempo acumulado consumen
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
Para una prueba rápida de saturación, pgbench -c 50 -T 30 mydb simula 50 clientes concurrentes durante 30 segundos. Si satura sin problema mientras tu app se traba, el cuello de botella es el pool, no la base de datos.
Alertas Críticas
| Alerta | Umbral | Significado |
|---|---|---|
| Agotamiento de pool | Active == Max por > 60s | Todas las conexiones en uso, nuevas peticiones bloqueadas |
| Tiempo de espera alto | Espera promedio > 1s | Pool muy pequeño o consultas muy lentas |
| Tasa de timeout | > 1% de peticiones | Agotamiento severo de pool |
| Fuga detectada | Cualquier advertencia de fuga | Código no cerrando conexiones |
| Edad de conexión | Edad promedio > maxLifetime | Conexiones no rotando correctamente |
Troubleshooting de Problemas Comunes del Pool
| Síntoma | Causa probable | Corrección |
|---|---|---|
connection reset en horas valle | maxLifetime mayor que el timeout de inactividad del firewall/DB | Baja maxLifetime por debajo del timeout; habilita pre-ping |
| Picos de latencia bajo carga | Pool subdimensionado para la concurrencia | Sube maxPoolSize gradualmente observando la CPU de la DB |
too many connections en la DB | La suma de pools de instancias excede max_connections | Achica los pools de app o pon PgBouncer delante |
| El pool se agota solo durante jobs batch | Batch y OLTP comparten un pool | Separa en dos pools con presupuestos propios |
| Hilos esperando para siempre | connectionTimeout ausente o enorme | Ponlo en 10-30s para que las peticiones fallen rápido |
6. Pooling en Serverless y Cold Starts
Lambda, Cloud Run y otros runtimes de funciones rompen el modelo clásico de pooling: las instancias aparecen y desaparecen a demanda, así que los pools por instancia se multiplican impredeciblemente. Cincuenta invocaciones concurrentes con un pool de 10 conexiones cada una pueden tumbar una base de datos en segundos.
La mitigación estándar es poner un proxy delante — RDS Proxy en AWS, PgBouncer en una VM pequeña, o el pooler gestionado de servicios como Supabase — y apuntar las funciones a él en vez de a la base. Si tu runtime mantiene entornos calientes entre invocaciones, un pool global de inicialización perezosa también ayuda: la primera invocación paga el costo de conexión, las siguientes la reutilizan.
// Reutiliza el pool entre invocaciones calientes de Lambda
// (la inicialización a nivel de módulo corre una vez por entorno)
const { Pool } = require('pg');
let pool;
function getPool() {
if (!pool) {
pool = new Pool({
connectionString: process.env.DATABASE_URL, // apunta al proxy
max: 2, // pequeño: el pooling real lo hace el proxy
idleTimeoutMillis: 10000,
allowExitOnIdle: true,
});
}
return pool;
}
Mantén el pool por función deliberadamente pequeño. El proxy es el componente que condensa cientos de conexiones efímeras en un conjunto estable de conexiones al servidor.
7. Ajusta PgBouncer Bajo Carga
Los valores por defecto de PgBouncer son conservadores. Tres parámetros importan más en producción:
pool_mode = transactionda la mejor compartición, pero resetea el estado de sesión entre transacciones. Los prepared statements, comandosSET, advisory locks a nivel de sesión yLISTEN/NOTIFYno sobreviven al límite de transacción — deja esas cargas en modo session o en una conexión directa.default_pool_sizees por par usuario+base. Conpool_size = 25y 4 usuarios pegándole a 2 bases, PgBouncer puede abrir hasta 200 conexiones al servidor. Multiplica antes de desplegar.reserve_pool_sizeyreserve_pool_timeoutabsorben ráfagas: pasado el timeout, los clientes en espera toman conexiones del pool de reserva en vez de recibir error.max_prepared_statements(PgBouncer 1.21+) hace que los prepared statements con nombre funcionen en modo transaction — el pooler los rastrea entre backends. En versiones anteriores, las cargas con prepared statements siguen necesitando modo session o una conexión directa.
PgBouncer expone su propia consola de administración (psql -h 127.0.0.1 -p 6432 pgbouncer). SHOW POOLS muestra esperas de cliente/servidor por pool; SHOW STATS reporta el tiempo de espera promedio — los dos números que quieres en tu dashboard junto a las métricas del paso 5.
8. Haz Pooling También en los Clientes HTTP
Las bases de datos no son el único recurso en pool. Cada llamada HTTP saliente de tu app paga un handshake TCP+TLS a menos que el cliente reutilice conexiones — y los pools keep-alive sin límite causan el mismo problema de agotamiento contra APIs downstream y load balancers.
// node: agente keep-alive con un pool de sockets acotado
const http = require('http');
const agent = new http.Agent({
keepAlive: true,
maxSockets: 50, // límite de sockets concurrentes por host
maxFreeSockets: 10, // cuántos sockets inactivos mantener calientes
timeout: 60000,
});
# requests/httpx: una Session reutiliza su pool de conexiones
import httpx
client = httpx.Client(
limits=httpx.Limits(max_connections=50, max_keepalive_connections=10),
timeout=30.0,
)
Las reglas se trasladan directamente: acota el pool (maxSockets/max_connections), mantén un pequeño conjunto caliente, y reutiliza el objeto cliente entre peticiones en vez de crear uno por llamada. Un requests.get() suelto en un bucle es el equivalente HTTP de abrir una conexión de base de datos por consulta.
9. Sobrevive a los Deploys y Restarts
Los rolling deploys causan una tormenta de conexiones predecible: cada instancia nueva abre sus minIdle conexiones de golpe mientras las instancias viejas aún retienen las suyas. Con 10 instancias × minimum-idle: 10, un restart rodante duplica brevemente la demanda de conexiones. Dos defensas: mantén minimum-idle bajo (5 o menos) para que el warm-up sea barato, y dimensiona maxPoolSize asumiendo que existe el doble de tu flota durante la ventana de deploy. Si tus deploys se solapan mucho, cuenta las flotas vieja y nueva por separado en el presupuesto del paso 1.
Lo que funciona
- Dimensiona pools basado en capacidad de base de datos, no deseo de aplicación. Tu base de datos tiene un límite duro de conexiones. Suma todos los maxPoolSizes y asegúrate de que quepan.
- Usa pooling a nivel de transacción (PgBouncer) para apps web. El pooling a nivel de sesión desperdicia conexiones durante el tiempo inactivo de petición HTTP.
- Siempre usa try-with-resources o context managers. Nunca confíes en llamadas manuales a close().
- Configura maxLifetime menor que el timeout de inactividad de base de datos. Previene errores de “connection reset” de firewalls o configuraciones de base de datos.
- Habilita verificación de conexión (pre-ping). Verifica que las conexiones estén vivas antes de entregarlas al código de aplicación.
- Usa pools separados para diferentes cargas de trabajo. Trabajos batch y APIs en tiempo real no deberían compartir un pool.
Errores Comunes
- Pools sobredimensionados. Un pool de 100 conexiones por instancia × 20 instancias = 2000 conexiones. La mayoría de servidores PostgreSQL luchan más allá de 500.
- Sin timeout de conexión. Timeouts por defecto de 30s+ causan fallos en cascada durante cortes.
- Mantener conexiones durante peticiones HTTP. Si tu llamada a API toma 5s y mantienes una conexión DB todo ese tiempo, necesitas 5× más conexiones.
- No manejar agotamiento de pool. Cuando el pool está lleno, tu aplicación debería degradarse elegantemente, no quedarse colgada indefinidamente.
- Un pool para todo. Trabajos batch que mantienen conexiones por minutos privan a peticiones API en tiempo real.
Variantes
- Pool de aplicación (HikariCP, SQLAlchemy, node-postgres): vive dentro de cada instancia. Es lo más simple de configurar, pero cada instancia tiene su propio presupuesto de conexiones — sirve hasta unas 5-10 instancias.
- Pool de middleware (PgBouncer, ProxySQL, pgpool): proxy entre las apps y la base que condensa conexiones cliente en un conjunto fijo de conexiones al servidor. La opción correcta cuando el número de instancias supera lo que
instancias × pool_sizepuede sostener. - Pool gestionado en la nube (RDS Proxy, pooling integrado de Cloud SQL): la misma idea operada por el proveedor, normalmente con integración IAM y manejo automático de failover.
- Pooling serverless: los runtimes efímeros (Lambda, Cloud Run) no pueden mantener pools estables; necesitan un proxy o un pool global reutilizado en caliente — cubierto en el paso 6. Ver también el patrón de pooling de conexiones DB serverless para una versión más reducida de este escenario.
Conclusión
El pooling de conexiones es de esos arreglos que parecen aburridos hasta el día en que salvan un lanzamiento. Dimensiona bien con la fórmula, acota los timeouts para que los fallos salgan rápido, y vigila los dos números que de verdad dicen algo: waiters y tiempo de espera. Con eso hecho, las tormentas de conexiones dejan de ser un problema de las 3 de la mañana.
Ver También
- Recipe de pooling de conexiones para bases de datos y clientes HTTP — configuraciones listas para copiar por lenguaje.
- Guía completa de tuning de PostgreSQL — qué ajustar del lado del servidor una vez que el pooling está en marcha.
- Configs ejecutables del repositorio complementario — los ejemplos de PgBouncer, HikariCP, SQLAlchemy y el pool de Lambda como archivos descargables.
Lecturas Adicionales
- Documentación de HikariCP — la referencia para la fórmula de dimensionamiento y el análisis “About Pool Sizing”.
- Documentación de PgBouncer — modos de pool, pools de reserva y los comandos de la consola admin usados arriba.
- Wiki de PostgreSQL — Number of database connections — fuente de la heurística
((núcleos × 2) + discos).
Preguntas frecuentes
¿Con qué tamaño de pool empiezo?
Empieza con 10. Monitorea las conexiones activas bajo carga pico; si se acercan al máximo de forma sostenida, sube gradualmente. Nunca dejes que pool_size × instancias supere lo que la base puede manejar — ver el ejemplo trabajado del paso 1.
¿PgBouncer o un pool en la aplicación?
Ambos. El pool en la app resuelve la eficiencia por instancia; PgBouncer resuelve la compartición entre instancias. Pasadas ~5 instancias, un proxy delante deja de ser opcional.
¿Por qué aparecen errores de "connection reset" de noche?
Casi siempre es un desfase de timeouts: la base o un firewall cierran conexiones inactivas mientras tu pool las sigue considerando vivas. Baja maxLifetime por debajo del timeout de inactividad y habilita pool_pre_ping/testOnBorrow para que las conexiones muertas se descarten antes de usarse.
¿Cómo hago pooling desde funciones serverless?
Apunta las funciones a un proxy (RDS Proxy, PgBouncer) y conserva una variable global de pool para reutilizar entre invocaciones calientes. Los cold starts siguen pagando el costo de conexión — es inevitable — pero el proxy lo absorbe.
¿Puede pool_size × instancias superar max_connections?
Puede, y te va a morder durante un pico de tráfico cuando todas las instancias llenen sus pools a la vez. Presupuesta siempre suma(maxPoolSize entre instancias) < max_connections − margen, o pon un proxy delante y trata el pool de la app como limitador de concurrencia, no como el pool real.
Recursos Relacionados
Réplicas de Lectura: Escala Lecturas Sin Tocar tu App
Guía de producción sobre réplicas de lectura: configuración de replicación streaming, enrutamiento de lectura/escritura (app, ORM y proxy), manejo del lag, monitoreo, failover y ajuste fino en PostgreSQL, MySQL y réplicas gestionadas en la nube.
RecipeEstrategias de Caché: Navegador, CDN, Redis y Base de Datos
Implementa caché efectiva en cada capa del stack: caché de navegador, edge de CDN, caché de aplicación con Redis y caché de consultas de base de datos — con estrategias de invalidación que mantienen los datos frescos.
GuideMétricas y Dashboards: Guía de Prometheus y Grafana
Guía práctica sobre métricas y dashboards: instrumentación de aplicaciones, elección de tipos de métricas, control de cardinalidad, construcción de dashboards útiles y creación de pipelines de alertas con Prometheus, Grafana y Datadog.
GuideGuía Completa de Sharding de Bases de Datos
Domina el sharding: shard keys, estrategias de partición, consistent hashing, consultas cross-shard, resharding, Vitess y Citus.