¿Qué es un connection pool?
Un connection pool es un caché de conexiones abiertas a la base de datos que la aplicación toma prestadas y devuelve. En lugar de abrir una conexión nueva para cada consulta, una solicitud solicita una, ejecuta sus sentencias y la devuelve. La siguiente solicitud reutiliza el mismo socket, que ya está autenticado y listo.
La razón por la que esto es importante es que una conexión a la base de datos no es barata. En una base de datos de prueba local puede parecer instantáneo, pero en producción cada nueva conexión tiene un coste de configuración fijo antes de realizar cualquier trabajo útil. Un pool paga ese coste una vez por conexión en lugar de una vez por solicitud, razón por la cual es la primera pieza de infraestructura que añade casi cualquier backend.
El pool es también un mecanismo de seguridad. Debido a que tiene un máximo estricto, no puede abrir accidentalmente diez mil conexiones durante un pico de tráfico. Las llamadas que llegan cuando el pool está lleno esperan en una cola, lo cual es lento pero manejable, en lugar de saturar la base de datos, lo cual sería crítico.
Un pool en pocas líneas
En Node, el driver pg incluye un pool listo para usar. Crearlo requiere una sola llamada al constructor, y cada consulta que ejecutes a través de él toma prestada y devuelve una conexión automáticamente.
import { Pool } from "pg";
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 30_000,
connectionTimeoutMillis: 5_000,
});
const { rows } = await pool.query("SELECT id, email FROM users WHERE id = $1", [id]);
Esa es la idea central. El pool abre conexiones de forma perezosa (lazy) a medida que surge la demanda, las mantiene abiertas entre consultas y las cierra cuando han estado inactivas demasiado tiempo. La aplicación nunca ve un socket; ve un método query.
Este mismo patrón existe en todos los ecosistemas. Java tiene HikariCP, Python tiene el pool de SQLAlchemy y el de asyncpg, Go tiene database/sql con SetMaxOpenConns, y cada ORM envuelve uno de estos. Los nombres varían, pero la configuración se reduce a los mismos conceptos: un máximo, un mínimo, un idle timeout y un checkout timeout.
Por qué las conexiones son costosas
El costo es una serie de pasos, donde cada uno implica que la red o el servidor de la base de datos realicen un trabajo real.
- TCP handshake — un viaje de ida y vuelta (round trip) para establecer el socket. En una base de datos remota con un RTT de 30 ms, solo esto ya son 30 ms.
- TLS negotiation — si la conexión está cifrada, se requieren varios viajes de ida y vuelta más para acordar las claves. Es común que esto sume otros 50 a 100 ms.
- Autenticación — el cliente demuestra su identidad, a menudo con un hash de contraseña que el servidor debe computar. La autenticación SCRAM realiza deliberadamente un trabajo costoso.
- Proceso de backend — este es el costo específico de Postgres. Postgres crea un nuevo proceso del sistema operativo (fork) para cada conexión, cada uno con su propia memoria. MySQL utiliza un hilo (thread), que es más ligero pero tampoco es gratuito.
- Configuración de la sesión — se deben aplicar los search paths, la zona horaria, el nombre de la aplicación y otros ajustes.
Si sumamos todo, una nueva conexión puede costar decenas de milisegundos antes de la primera consulta. Si una página ejecuta diez consultas, abrir una conexión por cada consulta dominaría el tiempo de respuesta. Peor aún, el modelo de proceso por conexión significa que las conexiones inactivas siguen consumiendo memoria, por lo que unos pocos cientos de ellas pueden afectar seriamente al servidor.
Un pool convierte todo esto en un costo único. La conexión se crea una sola vez, se utiliza para miles de consultas y se cierra cuando el pool decide que es demasiado antigua o que ha estado inactiva demasiado tiempo.
El ciclo de vida de una conexión en el pool
Cada proceso de checkout sigue los mismos cuatro pasos, y comprenderlos explica casi cualquier comportamiento del pool que tengas que depurar.
Acquire. El llamador solicita una conexión al pool. Si hay una inactiva, se entrega inmediatamente. Si todas están ocupadas pero el pool está por debajo de max, se abre una nueva. Si el pool ha alcanzado max, el llamador espera en una cola FIFO hasta que se libere alguna. Esa espera es la señal de que el pool está saturado.
Use. El llamador ejecuta una o más consultas en la conexión. Aquí es donde la conexión realmente cumple su función. También es donde ocurren los errores: una transacción que queda abierta, un client que nunca se libera o una consulta sin timeout.
Release. El llamador devuelve la conexión al pool. Esto debe ocurrir dentro de un bloque finally, ya que una excepción entre el acquire y el release provoca que la conexión se filtre permanentemente. Una conexión filtrada es invisible hasta que el pool se agota y cada solicitud comienza a dar timeout.
Reap. Las conexiones que permanecen inactivas más tiempo del idleTimeoutMillis, o que superan un tiempo de vida máximo, se cierran. Esto evita que el pool acapare recursos que la base de datos podría usar en otro lugar, y es la forma en que se retiran las conexiones obsoletas previas a un reinicio.
const client = await pool.connect();
try {
return await client.query("SELECT now()");
} finally {
client.release();
}
Muchos drivers permiten omitir el checkout explícito para consultas sencillas — pool.query() se encarga del acquire y el release por ti — lo que elimina la fuente más común de fugas de conexiones. Utiliza la forma explícita solo cuando necesites ejecutar varias sentencias en la misma conexión.
Checkout, query, release, de forma segura
El patrón de tres líneas anterior es la estructura de cualquier interacción segura con el pool, pero el código de producción requiere un poco más de cuidado en los detalles.
Envuelve todo el proceso de checkout en un helper para que ningún llamador olvide el release. El helper se encarga del try/finally y del manejo de errores, y el resto de la base de código simplemente espera a que se ejecute una función.
export async function withClient<T>(
fn: (client: PoolClient) => Promise<T>,
): Promise<T> {
const client = await pool.connect();
try {
return await fn(client);
} finally {
client.release();
}
}
await withClient((client) =>
client.query("UPDATE jobs SET status = 'done' WHERE id = $1", [id]),
);
Considera qué sucede cuando la conexión misma se rompe. Una query puede fallar porque el SQL es incorrecto, lo cual es problema del llamador, o porque el socket murió, lo cual es problema del pool. El segundo caso amerita un reintento, pero solo si la operación es segura de repetir. Un SELECT lo es; un INSERT sin una clave de idempotencia no lo es.
async function queryWithRetry(text: string, params: unknown[], retries = 2) {
for (let attempt = 0; ; attempt++) {
try {
return await pool.query(text, params);
} catch (err) {
const isConnectionError = (err as { code?: string }).code === "ECONNRESET";
if (!isConnectionError || attempt >= retries) throw err;
}
}
}
Finalmente, establece un statement timeout en la conexión para que una sola query descontrolada no pueda retener un slot del pool indefinidamente. En Postgres, statement_timeout cancela la query en el lado del servidor; sin esto, el cliente espera tanto como lo haga la base de datos.
Dimensionando el pool
La tentación es configurar max con un valor alto para que nada tenga que esperar. Esto es exactamente lo contrario de lo que quieres. Una base de datos tiene un número limitado de núcleos de CPU y una cantidad limitada de memoria, y no puede ejecutar más consultas en paralelo de las que su capacidad permite. Las conexiones adicionales no aumentan el rendimiento (throughput); lo que añaden es cambio de contexto (context switching), contención de bloqueos y presión sobre la memoria.
La regla general clásica proviene de la wiki de PostgreSQL:
connections = (cores × 2) + effective_spindle_count
Para una base de datos de cuatro núcleos con almacenamiento SSD, eso son aproximadamente entre ocho y diez conexiones. Suena alarmantemente bajo, y es correcto: una consulta bien indexada se completa en uno o dos milisegundos, por lo que un puñado de conexiones puede atender miles de solicitudes por segundo. La fórmula es un punto de partida, no una ley: mide y ajusta.
La segunda parte del cálculo es la que la gente suele olvidar. Cada instancia de la aplicación ejecuta su propio pool, por lo que el presupuesto de la base de datos debe dividirse por el número de instancias:
pool max per instance = database budget / number of instances
Diez instancias con un pool de veinte solicitan a la base de datos doscientas conexiones, lo cual casi con seguridad supera max_connections. La solución es reducir el pool por instancia o colocar un pooler delante.
Finalmente, verifica los límites de la propia base de datos. Postgres establece por defecto max_connections en 100, y las conexiones reservadas para superusuarios y replicación reducen lo que realmente está disponible. Si solicitas más de eso, obtendrás errores en lugar de una degradación gradual del servicio.
El problema del fan-out de pool por instancia
Este es el fallo de conexión más común en los despliegues modernos, y ocurre de manera gradual.
Un servicio comienza en una sola instancia con un pool de veinte conexiones. Funciona. El tráfico crece, por lo que el servicio escala a cinco instancias; ahora la base de datos ve cien conexiones, exactamente en el límite. Escala a diez y son doscientas, superando ampliamente el límite. Nada cambió en la aplicación; lo que cambió fue el fan-out.
Este patrón se repite en Kubernetes, en serverless y en cualquier lugar donde los procesos se multipliquen. Un pool limita las conexiones por proceso, no por sistema. La solución es utilizar un pool pequeño por instancia dimensionado según la flota, o un pooler en el lado del servidor que presente un único pool pequeño a la base de datos, independientemente de cuántos clientes se conecten.
10 instances × pool max 20 = 200 database connections
↓
database max_connections = 100
↓
"too many clients already"
Un ejemplo práctico de dimensionamiento
Los números hacen que las compensaciones sean concretas. Supongamos que la base de datos es una instancia gestionada de cuatro núcleos con almacenamiento SSD y el max_connections predeterminado de 100, y que el servicio se ejecuta en ocho instancias de aplicación.
La regla general nos da un presupuesto de aproximadamente diez conexiones que la base de datos puede utilizar genuinamente en paralelo. Distribuidas entre ocho instancias, eso es un pool de una o dos por instancia; mucho menor que las diez que se suelen configurar, y a menudo es lo correcto para una carga de trabajo rápida y bien indexada. Si eso parece demasiado ajustado, la solución no es aumentar el pool, sino colocar un pooler delante para que los ocho pools pequeños compartan un conjunto controlado de backends.
database budget ≈ 10 connections
app instances = 8
pool max per instance = 10 / 8 ≈ 1
too small to be useful → add PgBouncer
PgBouncer default_pool_size = 10
app pool max (per instance) = 5 # clients may wait; backends stay bounded
La idea clave es que el pool de la aplicación y el presupuesto de la base de datos son números diferentes. El pool de la aplicación controla cuántas solicitudes puede ejecutar cada instancia a la vez; el pooler controla cuántas conexiones al servidor existen realmente. Configurar el pool de la aplicación un poco más grande que la cuota por instancia permite que una instancia tenga picos de demanda mientras el pooler mantiene segura la base de datos.
Deja siempre un margen de maniobra. Las bases de datos gestionadas reservan algunas conexiones para administración y replicación, y un failover requiere brevemente de más. Apuntar al 70 u 80 por ciento de max_connections deja espacio para migraciones, monitoreo y un despliegue fallido.
Manteniendo las conexiones saludables
Las conexiones de larga duración pueden quedar obsoletas. El reinicio de una base de datos, un failover, una partición de red o el tiempo de espera de inactividad (idle timeout) de un firewall pueden cerrar el socket sin que la aplicación se dé cuenta. La siguiente consulta en esa conexión fallará y, si no se gestiona, el error resultará confuso.
Tres configuraciones gestionan esto:
idleTimeoutMilliscierra las conexiones que no se han utilizado durante un tiempo. Los valores cortos mantienen el servidor limpio, pero conllevan el riesgo de tener que reabrir conexiones durante periodos de poca actividad; treinta segundos es un equilibrio común.- Un tiempo de vida máximo (maximum lifetime) retira las conexiones después de una edad fija, independientemente de su uso. Esto distribuye las reconexiones en lugar de permitir que todas las conexiones mueran al mismo tiempo durante un failover.
- La validación ejecuta una comprobación ligera, como
SELECT 1, antes de entregar una conexión, para que un socket muerto sea descartado en lugar de pasarse al llamador.
Incluso con estas tres medidas, gestiona los errores de conexión de forma explícita. Un cliente inactivo que genera un error debe ser eliminado del pool, y el driver suele emitir un evento error precisamente para esto:
pool.on("error", (err) => {
console.error("unexpected idle client error", err);
});
Ignorar ese evento convierte un problema recuperable en una excepción no gestionada que puede provocar la caída del proceso.
Las transacciones requieren una sola conexión
Una transacción está ligada a una única conexión. Cada BEGIN, sentencia y COMMIT debe ejecutarse en el mismo cliente, ya que el estado de la transacción reside en ese proceso del backend. Aquí es donde interactúan el pooling y las transacciones, y donde el código ingenuo falla.
El modo de fallo es sutil: si ejecutas BEGIN a través de pool.query(), el pool podría entregarte una conexión diferente para la siguiente sentencia, y la COMMIT fallará o no confirmará nada. La regla es sencilla: reserva un cliente para toda la transacción y libéralo únicamente después del commit o rollback.
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query("UPDATE accounts SET balance_cents = balance_cents - $1 WHERE id = $2", [100, from]);
await client.query("UPDATE accounts SET balance_cents = balance_cents + $1 WHERE id = $2", [100, to]);
await client.query("COMMIT");
} catch (err) {
await client.query("ROLLBACK");
throw err;
} finally {
client.release();
}
Debido a que una transacción mantiene una conexión durante toda su duración, las transacciones largas reducen el pool efectivo para los demás. Mantenlas cortas, evita llamadas de red dentro de ellas y establece un statement_timeout para que una consulta descontrolada no bloquee una conexión indefinidamente.
Sentencias preparadas y el pool
Las sentencias preparadas (prepared statements) son una característica de rendimiento: la base de datos analiza y planifica una consulta una sola vez, y luego reutiliza ese plan. Aquí es donde el pooling se vuelve complejo, ya que una sentencia preparada reside en una conexión de backend específica.
Con un pool en el mismo proceso, esto generalmente no es un problema. Los drivers como pg preparan una sentencia en cualquier conexión que esté disponible en ese momento, y el plan se reutiliza solo cuando esa misma conexión vuelve a procesar la consulta. Nada se rompe, pero el beneficio es irregular y el driver debe gestionar un caché creciente de sentencias nombradas.
El problema comienza cuando combinas sentencias preparadas nombradas con un pooler en modo transacción (transaction-mode). El pooler puede enviar tu consulta a un backend diferente al que preparó la sentencia, por lo que el servidor no reconoce el nombre y devuelve un error. Esta es la sorpresa más común al usar PgBouncer.
Las soluciones son sencillas:
- Desactiva las sentencias preparadas del lado del servidor en el driver cuando uses transaction pooling, y permite que envíe sentencias sin nombre. Muchos drivers tienen un flag específicamente para esto.
- O utiliza session pooling, que mantiene a un cliente en un solo backend y hace que las sentencias preparadas vuelvan a ser seguras.
- O configura
max_prepared_statementsen versiones modernas de PgBouncer, que hace el proxy del protocolo de preparación correctamente.
El principio general es que cualquier cosa almacenada en la sesión del servidor es frágil bajo transaction pooling. Las sentencias preparadas, las variables SET, las tablas temporales y los advisory locks pertenecen a una conexión, y el pooler tiene libertad de asignarte una diferente la próxima vez.
Serverless y el agotamiento de conexiones
Las funciones serverless representan el peor escenario para el connection pooling. Cada invocación puede ejecutarse en un contenedor nuevo y efímero sin memoria compartida, por lo que no puede reutilizar un pool creado por otra invocación. Bajo carga, cientos de funciones concurrentes abren una conexión cada una, y la base de datos se enfrenta a una tormenta de conexiones que no puede soportar.
La solución es un pooler del lado del servidor entre las funciones y la base de datos. PgBouncer, o un equivalente gestionado como RDS Proxy o el pooler integrado de un proveedor, mantiene un conjunto pequeño de conexiones reales y multiplexa las numerosas conexiones efímeras de los clientes sobre ellas.
1000 concurrent functions ──► PgBouncer ──► 20 Postgres connections
(clients) (pooler) (backends)
Tener un pool dentro de una función sigue siendo útil, pero debe ser diminuto — a menudo una sola conexión — y estar configurado para cerrarse rápidamente, ya que un contenedor que mantiene conexiones abiertas mientras está inactivo desperdicia el presupuesto de la base de datos. El pooler es lo que hace que las cifras funcionen.
PgBouncer y el pooling de transacciones
PgBouncer es el pooler externo estándar para Postgres. Utiliza el protocolo de red de Postgres, por lo que las aplicaciones se conectan a él exactamente igual que lo harían a la base de datos. Tiene tres modos de pooling, y la elección tiene consecuencias reales.
El Session pooling asigna una conexión al servidor durante toda la sesión del cliente. Es el modo más compatible — SET, LISTEN, los advisory locks y los prepared statements se comportan con normalidad — pero es el que menos multiplexa, ya que un cliente mantiene su conexión incluso mientras está inactivo.
El Transaction pooling asigna una conexión al servidor solo durante la duración de una transacción y la devuelve al pool al hacer el commit. Mil clientes pueden compartir veinte backends, razón por la cual es la opción predeterminada para aplicaciones web. La contrapartida es que el estado de la sesión no persiste entre transacciones: un SET puede caer en un backend diferente la próxima vez, los advisory locks mantenidos entre sentencias dejan de funcionar y los prepared statements del lado del servidor pueden entrar en conflicto.
El Statement pooling devuelve la conexión después de cada sentencia. Es el que más multiplexa y el más restrictivo; no se permiten transacciones de múltiples sentencias. Rara vez es lo que necesitas.
[databases]
shop = host=127.0.0.1 port=5432 dbname=shop
[pgbouncer]
pool_mode = transaction
default_pool_size = 20
max_client_conn = 1000
server_idle_timeout = 60
Si utilizas el modo de transacción, audita tu ORM y tus consultas. Usa SET LOCAL dentro de una transacción en lugar de SET, evita mantener advisory locks entre sentencias y configura el driver para desactivar los prepared statements del lado del servidor o utiliza los que no tienen nombre (unnamed).
Monitoreo de la salud del pool
Un pool tiene cuatro métricas clave que observar y, en conjunto, te indican si tiene el tamaño correcto.
- Tiempo de espera por una conexión. La señal más directa de saturación. Si quienes solicitan conexiones esperan regularmente, el pool es demasiado pequeño para la carga o algo está reteniendo las conexiones por demasiado tiempo.
- Conexiones activas frente a inactivas (idle). Un pool que siempre está al máximo con todas las conexiones activas es insuficiente. Un pool que está mayormente inactivo es excesivo y está desperdiciando la memoria de la base de datos.
- Total frente al máximo. Qué tan cerca está el pool de su límite. Estar consistentemente cerca de
maxsignifica que el próximo pico de tráfico causará colas de espera. - Errores y timeouts. Fallos de conexión, fallos de validación y expiraciones de
connectionTimeoutMillis. Un aumento en este conteo apunta a problemas de red, de la base de datos o de conexiones obsoletas.
Desde el lado de la base de datos, pg_stat_activity muestra cada conexión y su estado, que es la forma más rápida de ver si se están acumulando conexiones inactivas. Combina ambas vistas: las métricas de la aplicación te dicen cómo se está comportando el pool, y las métricas de la base de datos te dicen qué le está haciendo al servidor.
SELECT state, count(*)
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY state
ORDER BY count(*) DESC;
Un pool de aplicación saludable muestra un número pequeño de conexiones active y algunas idle. Una pila creciente de filas idle in transaction es el patrón peligroso: esas conexiones han sido solicitadas pero no están haciendo nada, a menudo porque se abrió una transacción que nunca se confirmó (commit). Estas ocupan espacios en el pool y, en Postgres, pueden bloquear el vacuum. Trata un aumento en este conteo como un bug, no como un problema de optimización.
Pools de ORM y el pooler
Los ORM no eliminan la necesidad de hacer pooling; simplemente lo ocultan. Prisma, TypeORM, Drizzle y Knex mantienen su propio pool y exponen una opción de connectionLimit o pool. Esto es conveniente, pero significa que se aplica la misma aritmética de dimensionamiento y existe el mismo problema de fan-out.
El error es ejecutar un pool de ORM y un pooler sin coordinarlos. El pool de ORM decide cuántas conexiones desea una instancia; el pooler decide cuántas permite la base de datos. Si el pool de ORM es grande y el default_pool_size del pooler es pequeño, el ORM mantendrá conexiones que el pooler no puede atender, y las solicitudes se encolarán en el pooler en lugar de en el ORM. Configura ambos deliberadamente, y prefiere un pool de ORM modesto detrás de un pooler que uno grande sin él.
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 30_000,
connectionTimeoutMillis: 5_000,
});
Un pool no es una caché
Vale la pena señalar la diferencia, ya que a menudo se confunden. Una caché almacena resultados para que puedas evitar ejecutar una consulta. Un pool almacena conexiones para que puedas ejecutar consultas de manera más eficiente. Añadir un pool no reduce el número de consultas; solo hace que el inicio de cada una sea más económico en términos de recursos.
Si una página ejecuta veinte consultas, un pool hace que esas veinte consultas sean rápidas, pero no hace que desaparezcan. La siguiente capa de optimización es cachear los resultados costosos, lo cual se trata en la guía de Caching. Ambos se complementan bien: un pool mantiene las consultas restantes optimizadas, mientras que una caché elimina aquellas que puedes evitar por completo.
Esta distinción también explica una decepción común. Algunos equipos añaden un pool, ven que la latencia disminuye y luego se preguntan por qué el throughput no cambia bajo una carga pesada. El pool eliminó la sobrecarga de la conexión, pero la base de datos sigue haciendo todo el trabajo. Solo una caché, un mejor índice o menos consultas pueden cambiar eso.
Pruebas con un pool
Las pruebas deben ejecutar la misma ruta de pooling que en producción, ya que los errores que realmente importan —como un cliente filtrado o una transacción en la conexión incorrecta— solo aparecen a través del pool.
Utiliza un único pool compartido para toda la suite de pruebas y ciérralo una sola vez al final. Abrir un pool por cada archivo de prueba es lento y puede superar el límite de conexiones de la base de datos cuando las pruebas se ejecutan en paralelo.
import { afterAll } from "vitest";
import { pool } from "../src/db.js";
afterAll(async () => {
await pool.end();
});
test("findUser returns null for a missing id", async () => {
const { rows } = await pool.query("SELECT * FROM users WHERE id = $1", ["nope"]);
expect(rows).toHaveLength(0);
});
Dirige las pruebas hacia una base de datos desechable o una transacción que haga rollback, y realiza aserciones sobre el comportamiento del pool donde sea relevante: una prueba que verifique pool.totalCount y pool.idleCount antes y después de una operación detectará un cliente filtrado que una aserción normal pasaría por alto. En Postgres, pg_stat_activity puede confirmar que el recuento de conexiones haya vuelto al estado inicial.
Mejores prácticas
- Usa siempre un pool; nunca abras una conexión por cada solicitud.
- Dimensiona el pool basándote en la capacidad de la base de datos y luego divídelo por el número de instancias.
- Configura un
connectionTimeoutMillispara que las llamadas fallen rápidamente en lugar de quedar colgadas. - Establece un tiempo de espera de inactividad (idle timeout) y una vida útil máxima para que las conexiones obsoletas sean retiradas.
- Libera las conexiones en un bloque
finallyy gestiona el eventoerrordel pool. - Mantén una sola conexión durante toda la transacción y haz que las transacciones sean cortas.
- Implementa un pooler frente a Postgres una vez que las instancias se multipliquen o utilices funciones serverless.
- Usa transaction pooling para aplicaciones web y audita las funcionalidades con alcance de sesión (session-scoped).
- Monitorea el tiempo de espera, el recuento de conexiones activas frente a las inactivas y los timeouts, no solo la latencia de las consultas.
- Coordina el tamaño del pool del ORM con el tamaño del pool del pooler.
Errores comunes
- Crear un
Clientpor cada solicitud en lugar de utilizar un pool. - Establecer
maxen cientos y llamarlo “optimización”. - Olvidar liberar un cliente y agotar el pool lentamente.
- Ejecutar
BEGINyCOMMITa través depool.query()en conexiones diferentes. - Mantener una transacción abierta durante una llamada HTTP o una interacción del usuario.
- Dejar
connectionTimeoutMillisen cero, haciendo que las solicitudes esperen indefinidamente. - Ignorar el evento
errordel pool y provocar un crash debido a un cliente inactivo muerto. - Ejecutar un pool en el proceso dentro de funciones serverless sin un pooler delante.
- Asumir que el modo de transacción de PgBouncer soporta prepared statements y estado de sesión.
- Monitorear la latencia de las consultas mientras el tiempo de espera del pool aumenta sin ser detectado.
Próximos pasos
El pooling es inseparable de la base de datos a la que sirve, por lo que la guía de PostgreSQL cubre a fondo max_connections, PgBouncer y el modelo de proceso por conexión. Antes de ajustar el pool, verifica si la consulta realmente necesita ejecutarse; la guía de Caching muestra cómo eliminar la carga desde el origen. Si utilizas workers, la sección de Batch Processing explica cómo evitar que una flota de ellos agote la base de datos, y vale la pena revisar Node.js para entender cómo el event loop y la E/S asíncrona interactúan con un pool.