mariadb-pool-cuenti
v1.5.9
Published
Gestor inteligente de pools de conexiones para MariaDB/MySQL que maneja múltiples bases de datos de empresas (multi-tenant) de forma dinámica y eficiente
Maintainers
Readme
MariaDB Pool Cuenti 🗄️
Gestor inteligente de pools de conexiones para MariaDB/MySQL que maneja múltiples bases de datos de empresas (multi-tenant) de forma dinámica y eficiente.
🚀 Características
- Pool de conexiones dinámico para múltiples bases de datos
- Multi-tenant con configuración automática por empresa
- Reconexión automática con backoff exponencial
- Verificación de salud automática de conexiones
- Manejo robusto de errores con timeouts configurables
- Cleanup automático de recursos
- 🔥 NUEVO: Detección de fugas - Sistema inteligente para prevenir connection leaks
- Memory leak prevention con detección automática
- Soporte para conexiones directas (proxy) y pools
- Logging detallado para debugging
- TypeScript con tipado completo
📦 Instalación
npm install [email protected]🛠️ Configuración
Variables de Entorno
# Configuración del ambiente (dev o pro)
ENVIRONMENT_DATA_BASE=dev
# Configuración de la base de datos principal - DESARROLLO
DB_HOST_DEV=localhost
DB_USER_DEV=root
DB_PWD_DEV=tu_password_aqui
DATABASE_DEV=nombre_base_datos_dev
DB_PORT_DEV=3306
POOL_SIZE_BASE_DEV=5
# Configuración de la base de datos principal - PRODUCCIÓN
DB_HOST_PRO=host_produccion
DB_USER_PRO=usuario_produccion
DB_PWD_PRO=password_produccion
DATABASE_PRO=nombre_base_datos_pro
DB_PORT_PRO=3306
POOL_SIZE_BASE_PRO=15
# Configuración de la aplicación
ID_APPLICATION=1
# Configuración de pools de tenant (opcional)
TENANT_POOL_SIZE=10🔧 Uso Básico
1. Importar y usar
import dbManager from "mariadb-pool-cuenti";
// Obtener conexión para una empresa específica
const conn = await dbManager.getConnectionEmpresa(12345);
// Ejecutar consulta
const resultado = await conn.query("SELECT * FROM tabla WHERE id = ?", [1]);
// Liberar conexión (importante!)
await conn.safeRelease();2. Método simplificado (recomendado)
import dbManager from "mariadb-pool-cuenti";
// Ejecutar consulta con manejo automático de conexiones
const resultado = await dbManager.executeQuery(
12345, // ID de empresa
"SELECT * FROM usuarios WHERE activo = :activo",
{ activo: 1 }
);
console.log(resultado);4. Detección y prevención de fugas de conexiones 🔥
import dbManager, {
getConnectionStats,
cleanupStaleConnections
} from "mariadb-pool-cuenti";
// 1. Obtener estadísticas de conexiones para diagnóstico
const stats = getConnectionStats();
console.log("Estadísticas de conexiones:", stats);
// 2. Ejecutar limpieza y detección de fugas
const result = await cleanupStaleConnections();
console.log(`Limpieza completada: ${result.closed} conexiones cerradas, ${result.leaksDetected} fugas detectadas`);
// 3. Patrón seguro para prevenir fugas de conexiones
async function executeSafely(empresaId, callback) {
const conn = await dbManager.getConnectionEmpresa(empresaId);
try {
// Ejecutar las operaciones de base de datos dentro del callback
return await callback(conn);
} finally {
// Garantizar que siempre se libere la conexión
await conn.safeRelease();
}
}
// Uso del patrón seguro
const usuarios = await executeSafely(1, async (conn) => {
return conn.query("SELECT * FROM usuarios WHERE activo = 1");
});
console.log("Usuarios activos:", usuarios);4. Manejo completo con Express
import express from "express";
import dbManager, { shutdown } from "mariadb-pool-cuenti";
const app = express();
app.get("/usuarios/:empresaId", async (req, res) => {
try {
const empresaId = parseInt(req.params.empresaId);
const usuarios = await dbManager.executeQuery(
empresaId,
"SELECT id, nombre, email FROM usuarios WHERE activo = :activo",
{ activo: 1 }
);
res.json(usuarios);
} catch (error) {
console.error("Error:", error);
res.status(500).json({ error: "Error interno del servidor" });
}
});
const server = app.listen(3000, () => {
console.log("Servidor iniciado en puerto 3000");
});
// Manejo de cierre sincronizado
process.on("SIGINT", async () => {
console.log("Cerrando servidor...");
server.close(() => {
console.log("Servidor cerrado");
// La librería se cerrará automáticamente
process.exit(0);
});
});
process.on("SIGTERM", async () => {
console.log("Cerrando aplicación por SIGTERM...");
server.close(() => {
console.log("Aplicación cerrada");
// La librería se cerrará automáticamente
process.exit(0);
});
});🔍 Monitoreo
Verificar estado del servicio
// Verificar si está listo
if (dbManager.isReady()) {
console.log("Servicio listo para usar");
}
// Obtener estadísticas de pools
const stats = dbManager.getPoolStats();
console.log("Estadísticas:", stats);🚨 Manejo de Errores
La librería maneja automáticamente:
- Sin timeouts artificiales - Permite consultas extremadamente largas
- Motor MariaDB nativo - Maneja sus propios timeouts optimizados
- Reconexiones automáticas con backoff exponencial
- Errores de configuración con mensajes descriptivos
- Cleanup automático al terminar el proceso
Ejemplo de manejo de errores:
try {
const resultado = await dbManager.executeQuery(
empresaId,
"SELECT * FROM tabla_grande WHERE condicion = :valor",
{ valor: "algún_valor" }
);
} catch (error) {
if (error.message.includes("timeout")) {
console.error("La consulta tardó demasiado - considera optimizar");
} else if (error.message.includes("Variables de entorno")) {
console.error("Problema de configuración:", error.message);
} else {
console.error("Error inesperado:", error);
}
}📊 Configuración Avanzada
Para Bases de Datos Muy Grandes
# La librería v1.3.0+ NO usa timeouts artificiales
# Esto permite consultas que tomen horas si es necesario
# Pools más grandes para alta concurrencia
POOL_SIZE_BASE_PRO=20
TENANT_POOL_SIZE=25
# Configuración recomendada en tu servidor MariaDB
# wait_timeout = 28800 # 8 horas
# interactive_timeout = 28800 # 8 horas
# max_execution_time = 0 # Sin límite (solo para consultas SELECT)Para Desarrollo
# Configuración más pequeña para desarrollo
POOL_SIZE_BASE_DEV=3
TENANT_POOL_SIZE=5🆕 Novedades en v1.5.9
✅ Detección Avanzada de Fugas de Conexiones
- Problema resuelto: "A possible connection leak on thread XXXX (connection not returned to pool for 120001ms)"
- Método
getConnectionStats(): Obtiene estadísticas detalladas de todas las conexiones - Método
cleanupStaleConnections(): Detecta y cierra automáticamente conexiones con fugas - Tracking de actividad: Registro automático de última actividad para cada conexión
- Limpieza periódica: Escaneo regular para prevenir fugas y cerrar conexiones antiguas
✅ Mejoras en el Diagnóstico de Problemas
- Estadísticas detalladas: Información completa sobre el estado del pool y conexiones
- Identificación temprana: Detección de fugas después de 2 minutos sin actividad
- Cierre inteligente: Liberación automática de conexiones muy antiguas (>5 minutos)
- Prevención proactiva: Actualización automática de timestamps en cada actividad
- Patrones seguros: Ejemplos de uso para garantizar liberación de conexiones
🔧 Migración desde versiones anteriores
Cambios en Variables de Entorno
# ANTES (v1.1.1 y anteriores)
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=password
DB_NAME=database
# AHORA (v1.1.3)
ENVIRONMENT_DATA_BASE=dev
DB_HOST_DEV=localhost
DB_USER_DEV=root
DB_PWD_DEV=password
DATABASE_DEV=databaseCódigo sin cambios necesarios
// Tu código existente sigue funcionando igual
import dbManager from "mariadb-pool-cuenti";
const resultado = await dbManager.executeQuery(empresaId, sql, params);
// ¡Sin cambios necesarios!📋 Ejemplos de Cierre Seguro
// Ver ejemplos completos en:
// /examples/safe-shutdown-example.js
// /examples/shutdown-example.js
// Ejemplo básico:
import { shutdown, isShuttingDown } from "mariadb-pool-cuenti";
process.on("SIGINT", async () => {
if (isShuttingDown()) return;
console.log("Cerrando aplicación...");
// 1. Detener servidor web si existe
if (server) server.close();
// 2. Cerrar conexiones a base de datos
await shutdown();
console.log("Cierre exitoso");
process.exit(0);
});🎯 Casos de Uso
1. Aplicación SaaS Multi-tenant
// Cada cliente (empresa) tiene su propia base de datos
const clienteId = req.user.empresaId;
const datos = await dbManager.executeQuery(clienteId, sql, params);2. Cierre Seguro con Manejo de Errores
import { shutdown, isShuttingDown } from "mariadb-pool-cuenti";
// Manejador de cierre seguro
async function handleShutdown(signal) {
if (isShuttingDown()) {
console.log(`Ya cerrando, ignorando señal ${signal}`);
return;
}
console.log(`Señal ${signal} recibida, cerrando servicios...`);
try {
// 1. Detener servidor HTTP
await new Promise(resolve => server.close(resolve));
console.log("Servidor HTTP detenido");
// 2. Cerrar conexiones de Redis/caché
if (redisClient && redisClient.isOpen) {
await redisClient.quit();
console.log("Conexión Redis cerrada");
}
// 3. Cerrar conexiones a MariaDB
await shutdown();
console.log("Conexiones MariaDB cerradas");
// 4. Cerrar otros recursos
// ...
console.log("Cierre exitoso");
process.exit(0);
} catch (error) {
console.error("Error durante cierre:", error);
process.exit(1);
}
}
// Configurar manejadores de señales
['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach(signal => {
process.once(signal, () => handleShutdown(signal));
});
// Manejar errores no capturados y promesas rechazadas
process.once('uncaughtException', async (error) => {
console.error("Error no capturado:", error);
await handleShutdown('uncaughtException');
});
process.once('unhandledRejection', async (reason) => {
console.error("Promesa rechazada no manejada:", reason);
await handleShutdown('unhandledRejection');
});3. Consultas a Bases de Datos Extremadamente Grandes
// Sin timeouts artificiales - permite consultas que tomen horas
const reporteCompleto = await dbManager.executeQuery(
empresaId,
`SELECT
t.id, t.fecha, t.monto, c.nombre, p.descripcion
FROM transacciones t
JOIN clientes c ON t.cliente_id = c.id
JOIN productos p ON t.producto_id = p.id
WHERE t.fecha BETWEEN :inicio AND :fin
ORDER BY t.fecha`,
{ inicio: "2020-01-01", fin: "2024-12-31" }
);
// Esta consulta puede tomar 30 minutos, 2 horas, o el tiempo que necesite
console.log(`Procesadas ${reporteCompleto.length} transacciones`);🛡️ Seguridad
- Consultas parametrizadas por defecto
- Conexiones SSL soportadas
- Timeouts para prevenir ataques DoS
- Validación de configuración antes de inicializar
- Manejo seguro de errores sin exposición de detalles internos
📈 Rendimiento
- Pools dinámicos solo cuando se necesitan
- Conexiones reutilizadas entre consultas
- Health checks automáticos
- Reconexión inteligente con backoff
- Memory leak prevention
🔍 Troubleshooting
Errores "Connection leak" o "Connection not returned to pool"
// Problema:
// - "A possible connection leak on thread 4826769 (connection not returned to pool for 120001ms)"
// - "Has connection.release() been called?"
// Solución:
import { getConnectionStats, cleanupStaleConnections } from "mariadb-pool-cuenti";
// 1. Revisar estadísticas para identificar fugas
const stats = getConnectionStats();
console.log(`Total conexiones: ${stats.total.connections}`);
console.log(`Conexiones por empresa:`, stats.byEmpresa);
// 2. Si hay fugas, ejecutar limpieza de conexiones antiguas
if (stats.byEmpresa[1]?.potential_leaks > 0) {
console.log("Detectadas posibles fugas de conexiones, limpiando...");
const result = await cleanupStaleConnections();
console.log(`Limpieza completada: ${result.closed} conexiones cerradas, ${result.leaksDetected} fugas detectadas`);
}
// 3. Revisar en tu código si todas las conexiones se liberan correctamente
// ✅ CORRECTO: Liberar conexión después de usarla
const conn = await dbManager.getConnectionEmpresa(1);
try {
const result = await conn.query("SELECT * FROM tabla");
// Procesar resultado...
} finally {
// Siempre liberar la conexión, incluso si hay errores
await conn.safeRelease();
}
// ❌ INCORRECTO: Olvidar liberar la conexión
const conn = await dbManager.getConnectionEmpresa(1);
const result = await conn.query("SELECT * FROM tabla");
// ¡Falta conn.safeRelease()! - Esto causará una fuga de conexión🤝 Contribuir
Las contribuciones son bienvenidas. Por favor:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/mi-feature) - Commit tus cambios (
git commit -am 'Agregar mi feature') - Push a la rama (
git push origin feature/mi-feature) - Crea un Pull Request
📄 Licencia
MIT License - ver archivo LICENSE para más detalles.
🏷️ Versión
v1.5.9 - Detección avanzada de fugas de conexiones (connection leaks) y manejo mejorado de recursos
¿Problemas o preguntas? Abre un issue en GitHub.
