Fix: Restore website functionality - all pages and APIs working
This commit is contained in:
@@ -17,10 +17,58 @@ const pool = new Pool({
|
||||
keepAlive: true, // TCP keepalive
|
||||
keepAliveInitialDelayMillis: 10000,
|
||||
statement_timeout: 30000, // 30s query timeout
|
||||
query_timeout: 30000, // SAFEGUARD: Force query timeout at pool level
|
||||
});
|
||||
|
||||
pool.on("connect", () => logger.info("✓ PostgreSQL connected"));
|
||||
pool.on("error", (err) => logger.error("PostgreSQL error:", err));
|
||||
// SAFEGUARD: Track pool health
|
||||
let poolConnected = false;
|
||||
let connectionAttempts = 0;
|
||||
const MAX_CONNECTION_ATTEMPTS = 3;
|
||||
|
||||
pool.on("connect", (client) => {
|
||||
poolConnected = true;
|
||||
connectionAttempts = 0;
|
||||
logger.info("✓ PostgreSQL connected", {
|
||||
total: pool.totalCount,
|
||||
idle: pool.idleCount,
|
||||
waiting: pool.waitingCount,
|
||||
});
|
||||
});
|
||||
|
||||
pool.on("error", (err, client) => {
|
||||
poolConnected = false;
|
||||
connectionAttempts++;
|
||||
logger.error("💥 PostgreSQL pool error", {
|
||||
error: err.message,
|
||||
code: err.code,
|
||||
attempts: connectionAttempts,
|
||||
pool: {
|
||||
total: pool.totalCount,
|
||||
idle: pool.idleCount,
|
||||
waiting: pool.waitingCount,
|
||||
},
|
||||
});
|
||||
|
||||
// SAFEGUARD: Critical failure detection
|
||||
if (connectionAttempts >= MAX_CONNECTION_ATTEMPTS) {
|
||||
logger.error(
|
||||
"🚨 Database connection critically unstable - manual intervention required"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
pool.on("acquire", (client) => {
|
||||
logger.debug("Pool client acquired", {
|
||||
total: pool.totalCount,
|
||||
idle: pool.idleCount,
|
||||
});
|
||||
});
|
||||
|
||||
pool.on("release", (err, client) => {
|
||||
if (err) {
|
||||
logger.warn("Client released with error", { error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Query cache for SELECT statements with crypto-based keys
|
||||
const queryCache = new Map();
|
||||
@@ -28,6 +76,7 @@ const queryCacheOrder = []; // LRU tracking
|
||||
const QUERY_CACHE_TTL = 15000; // 15 seconds (increased)
|
||||
const QUERY_CACHE_MAX_SIZE = 500; // 500 cached queries (increased)
|
||||
const SLOW_QUERY_THRESHOLD = 50; // 50ms threshold (stricter)
|
||||
const QUERY_TIMEOUT = 35000; // SAFEGUARD: 35s query timeout (slightly higher than pool's 30s)
|
||||
|
||||
// Generate fast cache key using crypto hash
|
||||
const getCacheKey = (text, params) => {
|
||||
@@ -53,7 +102,22 @@ const query = async (text, params) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await pool.query(text, params);
|
||||
// SAFEGUARD: Add query timeout wrapper
|
||||
const queryPromise = pool.query(text, params);
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(
|
||||
new Error(
|
||||
`Query timeout after ${QUERY_TIMEOUT}ms: ${text.substring(
|
||||
0,
|
||||
50
|
||||
)}...`
|
||||
)
|
||||
);
|
||||
}, QUERY_TIMEOUT);
|
||||
});
|
||||
|
||||
const res = await Promise.race([queryPromise, timeoutPromise]);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
// Cache SELECT queries with LRU eviction
|
||||
@@ -84,11 +148,22 @@ const query = async (text, params) => {
|
||||
} catch (error) {
|
||||
const duration = Date.now() - start;
|
||||
logger.error("Query error", {
|
||||
text: text.substring(0, 100),
|
||||
error: error.message,
|
||||
duration,
|
||||
code: error.code,
|
||||
duration,
|
||||
text: text.substring(0, 100),
|
||||
});
|
||||
|
||||
// SAFEGUARD: Clear potentially corrupted cache entry
|
||||
if (isSelect) {
|
||||
const cacheKey = getCacheKey(text, params);
|
||||
queryCache.delete(cacheKey);
|
||||
const index = queryCacheOrder.indexOf(cacheKey);
|
||||
if (index > -1) {
|
||||
queryCacheOrder.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -141,34 +216,75 @@ const clearQueryCache = (pattern) => {
|
||||
};
|
||||
|
||||
// Health check with pool metrics
|
||||
const healthCheck = async () => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT NOW() as time, current_database() as database"
|
||||
const healthCheck = async (timeoutMs = 5000) => {
|
||||
// SAFEGUARD: Wrap health check in timeout promise
|
||||
const healthPromise = (async () => {
|
||||
try {
|
||||
const result = await query(
|
||||
"SELECT NOW() as time, current_database() as database"
|
||||
);
|
||||
return {
|
||||
healthy: true,
|
||||
database: result.rows[0].database,
|
||||
timestamp: result.rows[0].time,
|
||||
pool: {
|
||||
total: pool.totalCount,
|
||||
idle: pool.idleCount,
|
||||
waiting: pool.waitingCount,
|
||||
connected: poolConnected,
|
||||
},
|
||||
cache: {
|
||||
size: queryCache.size,
|
||||
maxSize: QUERY_CACHE_MAX_SIZE,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error("Database health check failed:", error);
|
||||
return {
|
||||
healthy: false,
|
||||
error: error.message,
|
||||
pool: {
|
||||
total: pool.totalCount,
|
||||
idle: pool.idleCount,
|
||||
waiting: pool.waitingCount,
|
||||
connected: poolConnected,
|
||||
},
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
// SAFEGUARD: Add timeout protection
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(
|
||||
() => reject(new Error(`Health check timeout after ${timeoutMs}ms`)),
|
||||
timeoutMs
|
||||
);
|
||||
return {
|
||||
healthy: true,
|
||||
database: result.rows[0].database,
|
||||
timestamp: result.rows[0].time,
|
||||
pool: {
|
||||
total: pool.totalCount,
|
||||
idle: pool.idleCount,
|
||||
waiting: pool.waitingCount,
|
||||
},
|
||||
cache: {
|
||||
size: queryCache.size,
|
||||
maxSize: QUERY_CACHE_MAX_SIZE,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.race([healthPromise, timeoutPromise]);
|
||||
};
|
||||
|
||||
// SAFEGUARD: Graceful pool shutdown for scripts/testing
|
||||
const closePool = async () => {
|
||||
try {
|
||||
await pool.end();
|
||||
logger.info("Database pool closed gracefully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("Database health check failed:", error);
|
||||
return {
|
||||
healthy: false,
|
||||
error: error.message,
|
||||
};
|
||||
logger.error("Error closing database pool:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// SAFEGUARD: Get pool status for monitoring
|
||||
const getPoolStatus = () => ({
|
||||
total: pool.totalCount,
|
||||
idle: pool.idleCount,
|
||||
waiting: pool.waitingCount,
|
||||
connected: poolConnected,
|
||||
cacheSize: queryCache.size,
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
pool,
|
||||
query,
|
||||
@@ -176,4 +292,6 @@ module.exports = {
|
||||
batchQuery,
|
||||
clearQueryCache,
|
||||
healthCheck,
|
||||
closePool,
|
||||
getPoolStatus,
|
||||
};
|
||||
|
||||
77
backend/scripts/db-health.js
Executable file
77
backend/scripts/db-health.js
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Database Health Check Script
|
||||
* Tests database connectivity and performance
|
||||
* Usage: node scripts/db-health.js
|
||||
*/
|
||||
|
||||
const db = require("../config/database");
|
||||
const logger = require("../config/logger");
|
||||
|
||||
async function runHealthCheck() {
|
||||
console.log("🔍 Running database health check...\n");
|
||||
|
||||
try {
|
||||
// Run health check with timeout
|
||||
const result = await db.healthCheck(5000);
|
||||
|
||||
if (result.healthy) {
|
||||
console.log("✅ DATABASE HEALTHY");
|
||||
console.log("━━━━━━━━━━━━━━━━━━━━━━");
|
||||
console.log(`Database: ${result.database}`);
|
||||
console.log(`Timestamp: ${result.timestamp}`);
|
||||
console.log(`\nConnection Pool:`);
|
||||
console.log(` Total Connections: ${result.pool.total}`);
|
||||
console.log(` Idle Connections: ${result.pool.idle}`);
|
||||
console.log(` Waiting Requests: ${result.pool.waiting}`);
|
||||
console.log(` Pool Connected: ${result.pool.connected ? "✓" : "✗"}`);
|
||||
console.log(`\nQuery Cache:`);
|
||||
console.log(
|
||||
` Cached Queries: ${result.cache.size}/${result.cache.maxSize}`
|
||||
);
|
||||
console.log(
|
||||
` Usage: ${((result.cache.size / result.cache.maxSize) * 100).toFixed(
|
||||
1
|
||||
)}%`
|
||||
);
|
||||
|
||||
// Get additional pool status
|
||||
const poolStatus = db.getPoolStatus();
|
||||
console.log(`\n📊 Pool Status: OPERATIONAL`);
|
||||
|
||||
process.exitCode = 0;
|
||||
} else {
|
||||
console.log("❌ DATABASE UNHEALTHY");
|
||||
console.log("━━━━━━━━━━━━━━━━━━━━━━");
|
||||
console.log(`Error: ${result.error}`);
|
||||
if (result.pool) {
|
||||
console.log(`\nPool State:`);
|
||||
console.log(
|
||||
` Total: ${result.pool.total}, Idle: ${result.pool.idle}, Waiting: ${result.pool.waiting}`
|
||||
);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("💥 HEALTH CHECK FAILED");
|
||||
console.log("━━━━━━━━━━━━━━━━━━━━━━");
|
||||
console.log(`Error: ${error.message}`);
|
||||
console.log(`\nThis usually indicates:`);
|
||||
console.log(` 1. Database connection timeout`);
|
||||
console.log(` 2. PostgreSQL service not running`);
|
||||
console.log(` 3. Network/firewall issues`);
|
||||
console.log(` 4. Database credentials incorrect`);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
// Gracefully close the pool
|
||||
console.log("\n🔌 Closing database connections...");
|
||||
await db.closePool();
|
||||
console.log("✓ Database pool closed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Run the check
|
||||
runHealthCheck().catch((err) => {
|
||||
console.error("Fatal error:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
19
backend/test_db_quick.js
Normal file
19
backend/test_db_quick.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const {query} = require('./config/database');
|
||||
|
||||
console.log('Testing query wrapper...');
|
||||
const timeout = setTimeout(() => {
|
||||
console.log('TIMEOUT - query() is hanging!');
|
||||
process.exit(1);
|
||||
}, 3000);
|
||||
|
||||
query('SELECT NOW() as time')
|
||||
.then(r => {
|
||||
clearTimeout(timeout);
|
||||
console.log('SUCCESS:', r.rows[0]);
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(e => {
|
||||
clearTimeout(timeout);
|
||||
console.log('ERROR:', e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
24
backend/test_healthcheck.js
Normal file
24
backend/test_healthcheck.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const db = require('./config/database');
|
||||
|
||||
console.log('Testing healthCheck...');
|
||||
const timeout = setTimeout(() => {
|
||||
console.log('TIMEOUT - healthCheck() is hanging!');
|
||||
console.log('Pool stats:', {
|
||||
total: db.pool.totalCount,
|
||||
idle: db.pool.idleCount,
|
||||
waiting: db.pool.waitingCount
|
||||
});
|
||||
process.exit(1);
|
||||
}, 5000);
|
||||
|
||||
db.healthCheck()
|
||||
.then(result => {
|
||||
clearTimeout(timeout);
|
||||
console.log('SUCCESS:', JSON.stringify(result, null, 2));
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(e => {
|
||||
clearTimeout(timeout);
|
||||
console.log('ERROR:', e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user