243 lines
6.3 KiB
TypeScript
243 lines
6.3 KiB
TypeScript
import mysql from "mysql2/promise"
|
|
import config from "../config.json"
|
|
|
|
// Connection pool configuration
|
|
const poolConfig = {
|
|
host: config.database.host,
|
|
user: config.database.user,
|
|
password: config.database.password,
|
|
database: config.database.database,
|
|
charset: "utf8mb4",
|
|
connectionLimit: 5, // Reduced from 10
|
|
acquireTimeout: 30000, // Reduced from 60 seconds
|
|
timeout: 30000, // Reduced from 60 seconds
|
|
reconnect: true,
|
|
idleTimeout: 180000, // Reduced to 3 minutes
|
|
maxIdle: 2, // Reduced from 5
|
|
enableKeepAlive: true,
|
|
keepAliveInitialDelay: 0,
|
|
// Add these to prevent excessive connections
|
|
queueLimit: 0,
|
|
dateStrings: false,
|
|
debug: false,
|
|
}
|
|
|
|
let pool: mysql.Pool | null = null
|
|
|
|
// Create connection pool
|
|
export function createPool() {
|
|
if (!pool) {
|
|
try {
|
|
pool = mysql.createPool(poolConfig)
|
|
console.log("Database connection pool created successfully")
|
|
|
|
// Handle pool events
|
|
pool.on("connection", (connection) => {
|
|
console.log(`New database connection established as id ${connection.threadId}`)
|
|
})
|
|
|
|
pool.on("error", (err) => {
|
|
console.error("Database pool error:", err)
|
|
// Don't recreate pool automatically - let it handle reconnections
|
|
if (err.code === "PROTOCOL_CONNECTION_LOST") {
|
|
console.log("Connection lost, pool will handle reconnection automatically")
|
|
}
|
|
})
|
|
|
|
pool.on("acquire", (connection) => {
|
|
console.log(`Connection ${connection.threadId} acquired from pool`)
|
|
})
|
|
|
|
pool.on("release", (connection) => {
|
|
console.log(`Connection ${connection.threadId} released back to pool`)
|
|
})
|
|
} catch (error) {
|
|
console.error("Failed to create database pool:", error)
|
|
throw error
|
|
}
|
|
}
|
|
return pool
|
|
}
|
|
|
|
// Get connection from pool
|
|
export async function getConnection(): Promise<mysql.PoolConnection> {
|
|
try {
|
|
const connectionPool = createPool()
|
|
const connection = await connectionPool.getConnection()
|
|
|
|
// Don't set session timeouts - let pool handle this
|
|
return connection
|
|
} catch (error) {
|
|
console.error("Failed to get database connection:", error)
|
|
throw new Error("Database connection failed")
|
|
}
|
|
}
|
|
|
|
// Execute query with automatic connection management
|
|
export async function executeQuery(query: string, params: any[] = []) {
|
|
let connection: mysql.PoolConnection | null = null
|
|
|
|
try {
|
|
connection = await getConnection()
|
|
const [results] = await connection.execute(query, params)
|
|
return results
|
|
} catch (error) {
|
|
console.error("Query execution failed:", error)
|
|
console.error("Query:", query)
|
|
console.error("Params:", params)
|
|
throw error
|
|
} finally {
|
|
// Always release connection back to pool
|
|
if (connection) {
|
|
connection.release()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Safe query execution with automatic parameter binding and connection management
|
|
export async function safeQuery(query: string, params: any[] = []) {
|
|
try {
|
|
return await executeQuery(query, params)
|
|
} catch (error) {
|
|
console.error("Database query error:", error)
|
|
throw new Error("Database operation failed")
|
|
}
|
|
}
|
|
|
|
// Execute transaction with automatic connection management
|
|
export async function executeTransaction(queries: Array<{ query: string; params?: any[] }>) {
|
|
let connection: mysql.PoolConnection | null = null
|
|
|
|
try {
|
|
connection = await getConnection()
|
|
|
|
// Start transaction
|
|
await connection.beginTransaction()
|
|
|
|
const results = []
|
|
|
|
// Execute all queries in transaction
|
|
for (const { query, params = [] } of queries) {
|
|
const [result] = await connection.execute(query, params)
|
|
results.push(result)
|
|
}
|
|
|
|
// Commit transaction
|
|
await connection.commit()
|
|
|
|
return results
|
|
} catch (error) {
|
|
// Rollback transaction on error
|
|
if (connection) {
|
|
try {
|
|
await connection.rollback()
|
|
} catch (rollbackError) {
|
|
console.error("Transaction rollback failed:", rollbackError)
|
|
}
|
|
}
|
|
|
|
console.error("Transaction failed:", error)
|
|
throw error
|
|
} finally {
|
|
// Always release connection back to pool
|
|
if (connection) {
|
|
connection.release()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test database connection and pool health
|
|
export async function testConnection() {
|
|
let connection: mysql.PoolConnection | null = null
|
|
|
|
try {
|
|
connection = await getConnection()
|
|
await connection.ping()
|
|
console.log("Database connection test successful")
|
|
return true
|
|
} catch (error) {
|
|
console.error("Database connection test failed:", error)
|
|
return false
|
|
} finally {
|
|
if (connection) {
|
|
connection.release()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get pool statistics
|
|
export function getPoolStats() {
|
|
if (!pool) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
totalConnections: pool.pool._allConnections.length,
|
|
freeConnections: pool.pool._freeConnections.length,
|
|
acquiringConnections: pool.pool._acquiringConnections.length,
|
|
connectionLimit: poolConfig.connectionLimit,
|
|
}
|
|
}
|
|
|
|
// Close all connections in pool (for graceful shutdown)
|
|
export async function closePool() {
|
|
if (pool) {
|
|
try {
|
|
await pool.end()
|
|
pool = null
|
|
console.log("Database pool closed successfully")
|
|
} catch (error) {
|
|
console.error("Error closing database pool:", error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Health check for monitoring
|
|
export async function healthCheck() {
|
|
let connection: mysql.PoolConnection | null = null
|
|
|
|
try {
|
|
const startTime = Date.now()
|
|
connection = await getConnection()
|
|
|
|
// Test basic query
|
|
const [result] = await connection.execute("SELECT 1 as health_check")
|
|
const responseTime = Date.now() - startTime
|
|
|
|
const stats = getPoolStats()
|
|
|
|
return {
|
|
status: "healthy",
|
|
responseTime,
|
|
poolStats: stats,
|
|
timestamp: new Date().toISOString(),
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
status: "unhealthy",
|
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
timestamp: new Date().toISOString(),
|
|
}
|
|
} finally {
|
|
if (connection) {
|
|
connection.release()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize pool on module load
|
|
// createPool()
|
|
|
|
// Graceful shutdown handler
|
|
process.on("SIGINT", async () => {
|
|
console.log("Received SIGINT, closing database pool...")
|
|
await closePool()
|
|
process.exit(0)
|
|
})
|
|
|
|
process.on("SIGTERM", async () => {
|
|
console.log("Received SIGTERM, closing database pool...")
|
|
await closePool()
|
|
process.exit(0)
|
|
})
|