Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
55 changes: 37 additions & 18 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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('❌ Не удалось подключиться к базе данных после нескольких попыток.');
Expand All @@ -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;
58 changes: 28 additions & 30 deletions src/config/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;