diff --git a/main.js b/main.js index f2a0d22..998bcff 100644 --- a/main.js +++ b/main.js @@ -5,6 +5,37 @@ dotenv.config(); const host = process.env.APP_IP || '0.0.0.0'; const PORT = process.env.PORT || 3000; -app.listen(PORT, host, () => { +const server = app.listen(PORT, host, () => { console.log(`Server running on http://${host}:${PORT}`); }); + +// Graceful shutdown handlers +const shutdown = async (signal) => { + console.log(`\n${signal} получен. Начинаем graceful shutdown...`); + + // Останавливаем прием новых запросов + server.close(() => { + console.log('✅ HTTP сервер закрыт'); + }); + + // Закрываем соединения с БД + if (app.gracefulShutdown) { + await app.gracefulShutdown(); + } else { + process.exit(0); + } +}; + +// Обработка сигналов для graceful shutdown +process.on('SIGTERM', () => shutdown('SIGTERM')); +process.on('SIGINT', () => shutdown('SIGINT')); + +// Обработка необработанных ошибок +process.on('unhandledRejection', (reason, promise) => { + console.error('❌ Unhandled Rejection at:', promise, 'reason:', reason); +}); + +process.on('uncaughtException', (error) => { + console.error('❌ Uncaught Exception:', error); + shutdown('uncaughtException'); +}); diff --git a/src/app.js b/src/app.js index 758b864..cafc231 100644 --- a/src/app.js +++ b/src/app.js @@ -37,21 +37,17 @@ app.use((err, req, res, next) => { // Register routes registerRoutes(app); -// Database connection check -function checkDatabaseConnection() { - return db - .getConnection() - .then((connection) => { - return connection.ping().then(() => { - console.log('✅ Успешное подключение к базе данных MySQL'); - connection.release(); - return true; - }); - }) - .catch((err) => { - console.error('❌ Ошибка подключения к базе данных:', err); - return false; - }); +// Оптимизированная проверка подключения - использует pool.query вместо getConnection +// Это не занимает отдельное соединение +async function checkDatabaseConnection() { + try { + await db.query('SELECT 1'); + console.log('✅ Успешное подключение к базе данных MySQL'); + return true; + } catch (err) { + console.error('❌ Ошибка подключения к базе данных:', err.message); + return false; + } } // Try to connect multiple times before giving up @@ -60,8 +56,10 @@ async function ensureDatabaseConnection(attempts = 5, delay = 5000) { const connected = await checkDatabaseConnection(); if (connected) return true; - console.log(`Повторная попытка подключения к БД (${i + 1}/${attempts})...`); - await new Promise((resolve) => setTimeout(resolve, delay)); + if (i < attempts - 1) { + console.log(`Повторная попытка подключения к БД (${i + 1}/${attempts})...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } } console.error('❌ Не удалось подключиться к базе данных после нескольких попыток.'); @@ -75,4 +73,25 @@ ensureDatabaseConnection().then((connected) => { } }); -module.exports = app; +// Graceful shutdown function +async function gracefulShutdown() { + console.log('🛑 Начало graceful shutdown...'); + const dbModule = require('./config/db'); + + try { + if (dbModule.closePool) { + await dbModule.closePool(); + } + console.log('✅ Graceful shutdown завершен'); + process.exit(0); + } catch (err) { + console.error('❌ Ошибка при graceful shutdown:', err); + process.exit(1); + } +} + +// Экспортируем app и функцию для graceful shutdown +const appWithShutdown = app; +appWithShutdown.gracefulShutdown = gracefulShutdown; + +module.exports = appWithShutdown; diff --git a/src/config/db.js b/src/config/db.js index e6779b7..84275dd 100644 --- a/src/config/db.js +++ b/src/config/db.js @@ -4,12 +4,12 @@ require('dotenv').config(); const { SQL_HOST, SQL_USER, SQL_PASSWORD, SQL_DB } = process.env; if (!SQL_HOST || !SQL_USER || !SQL_PASSWORD || !SQL_DB) { - throw new Error( - '❌ Отсутствуют переменные окружения для подключения к базе данных', - ); + throw new Error('❌ Отсутствуют переменные окружения для подключения к базе данных'); } -const poolSize = 30; +// Оптимизированный pool size: после оптимизации запросов (SQL JOIN) нужно меньше соединений +// 10 соединений достаточно для большинства нагрузок +const poolSize = 10; const pool = mysql.createPool({ host: SQL_HOST, user: SQL_USER, @@ -18,27 +18,13 @@ const pool = mysql.createPool({ waitForConnections: true, connectionLimit: poolSize, queueLimit: 0, - enableKeepAlive: true, // Enable TCP keep-alive - keepAliveInitialDelay: 10000, // Initial delay before sending keep-alive probes - connectTimeout: 30000, // Increase connection timeout to 30 seconds + enableKeepAlive: true, + keepAliveInitialDelay: 10000, + connectTimeout: 30000, + // Соединения автоматически освобождаются после запросов + // Keep-alive ping не нужен - pool сам управляет соединениями }); -// More frequent keep-alive to prevent ECONNRESET -const pingInterval = 30000; // 30 seconds -setInterval(() => { - pool - .query('SELECT 1') - .then(() => { - console.log('✅ MySQL keep-alive ping successful'); - }) - .catch((err) => { - console.error( - '❌ Ошибка при поддержании соединения с MySQL:', - err.message, - ); - }); -}, pingInterval); - // Wrapper function to retry failed queries async function executeWithRetry(queryFn, retries = 3, delay = 500) { let lastError; @@ -50,13 +36,8 @@ async function executeWithRetry(queryFn, retries = 3, delay = 500) { lastError = err; // Only retry on connection errors - if ( - err.code === 'ECONNRESET' || - err.code === 'PROTOCOL_CONNECTION_LOST' - ) { - console.log( - `Reconnecting after ${err.code}, attempt ${attempt + 1}/${retries}`, - ); + if (err.code === 'ECONNRESET' || err.code === 'PROTOCOL_CONNECTION_LOST') { + console.log(`Reconnecting after ${err.code}, attempt ${attempt + 1}/${retries}`); if (attempt < retries - 1) { await new Promise((resolve) => setTimeout(resolve, delay)); continue; @@ -77,4 +58,21 @@ pool.query = function (...args) { return executeWithRetry(() => originalQuery(...args)); }; +// Graceful shutdown function +async function closePool() { + return new Promise((resolve, reject) => { + pool.end((err) => { + if (err) { + console.error('❌ Ошибка при закрытии pool:', err); + reject(err); + } else { + console.log('✅ Pool закрыт успешно'); + resolve(); + } + }); + }); +} + +// Экспортируем pool и функцию для graceful shutdown module.exports = pool; +module.exports.closePool = closePool;