← back to Watches
database/connection.js
136 lines
/**
* Database Connection Pool
* Implements connection pooling, health checks, and automatic reconnection
*/
import pg from 'pg';
import dotenv from 'dotenv';
dotenv.config();
const { Pool } = pg;
// Pool configuration with production-grade settings
const poolConfig = {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'omega_watches',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
// Connection pool settings
max: parseInt(process.env.DB_POOL_MAX || '20'), // Maximum connections
min: parseInt(process.env.DB_POOL_MIN || '5'), // Minimum connections
idleTimeoutMillis: 30000, // Close idle connections after 30s
connectionTimeoutMillis: 2000, // Fail fast if can't connect in 2s
maxUses: 7500, // Recycle connections after 7500 uses
// Statement timeout (10 seconds for queries)
statement_timeout: parseInt(process.env.DB_STATEMENT_TIMEOUT || '10000'),
// Application name for monitoring
application_name: 'omega_watch_api'
};
// Create connection pool
const pool = new Pool(poolConfig);
// Connection pool event handlers
pool.on('connect', (client) => {
console.log('📊 New database client connected');
});
pool.on('acquire', (client) => {
// Client acquired from pool
});
pool.on('remove', (client) => {
console.log('📊 Database client removed from pool');
});
pool.on('error', (err, client) => {
console.error('❌ Unexpected database pool error:', err);
});
// Health check function
export async function checkDatabaseHealth() {
try {
const client = await pool.connect();
try {
const result = await client.query('SELECT NOW(), current_database(), version()');
return {
healthy: true,
timestamp: result.rows[0].now,
database: result.rows[0].current_database,
version: result.rows[0].version.split(' ')[1],
poolStats: {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount
}
};
} finally {
client.release();
}
} catch (error) {
return {
healthy: false,
error: error.message
};
}
}
// Execute query with automatic retry
export async function executeQuery(query, params, options = {}) {
const { retries = 3, retryDelay = 1000 } = options;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const result = await pool.query(query, params);
return result;
} catch (error) {
if (attempt === retries) {
throw error;
}
// Retry on connection errors
if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
console.log(`⚠️ Query failed (attempt ${attempt}/${retries}), retrying...`);
await new Promise(resolve => setTimeout(resolve, retryDelay * attempt));
} else {
throw error;
}
}
}
}
// Transaction helper
export async function withTransaction(callback) {
const client = await pool.connect();
try {
await client.query('BEGIN');
const result = await callback(client);
await client.query('COMMIT');
return result;
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
// Graceful shutdown
export async function closePool() {
console.log('📊 Closing database connection pool...');
await pool.end();
console.log('✅ Database connection pool closed');
}
// Process termination handlers
process.on('SIGTERM', closePool);
process.on('SIGINT', closePool);
export default pool;