백엔드 파일이 실행되다가 자꾸 꺼지는 에러

사이트를 운영할 때, 백엔드를 위한 파일을 따로 둡니다.

이때, 해당 파일을 

				
					node backend.js 
				
			

위와 같은 명령어로 실행을 할 수 있습니다.

문제는 이런 명령어로 실행한 세션이 닫힌다고 가정할까요?

저는 Putty 를 통해서 EC2 에 접근하고, 그곳에서 백엔드 파일을 실행하는데

이렇게 실행한 백엔드 파일을 컨트롤+C로 끄거나, 세션을 닫으면 사이트의 백엔드 로직은 더 이상 작동하지 않습니다. 

				
					nohup node backend.js &
				
			

때문에 보통 이런 명령어를 실행합니다.

해당 명령어를 하면 세션이 꺼지더라도 지속해서 백엔드가 실행되게 됩니다.

찾아보면 이론은 그렇습니다. 

그런데 문제가 발생합니다. 

				
					Database error: Error: Can't add new command when connection is in closed state
				
			

분명히 백엔드 파일은 실행을 했고, 사이트는 잘 작동을 하고 있던 와중에…

꼭 시간이 어느 정도 흐르면 위와 같은 에러와 함께 백엔드 로직 작동이 멈춥니다. 

저는 그 이유를 알아보고 이것을 해결코자 합니다. 

무슨 이유일까?

				
					이 오류 메시지는 MySQL 데이터베이스 연결과 관련된 문제를 나타내고 있습니다.
구체적으로는 MySQL 연결이 닫힌 상태에서 명령을 실행하려고 할 때 발생하는 오류입니다.
				
			

GPT를 이용해서 디버깅을 해본 결과 이런 답변을 받았네요.

그러니까, 이 오류는 MySQL DB를 이용하기 때문에 그런 거였습니다. 

그렇다면 지금 현재 저는 MySQL DB접근을 위해 어떤 방법을 취하고 있는지 보겠습니다. 

				
					const express = require('express');
const mysql = require('mysql2');
const app = express();

// MySQL 연결을 단일 연결로 설정 (잘못된 방식)
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'my_database'
});

// 잘못된 방식의 라우터 예시
app.get('/api/get-users', (req, res) => {
  const query = "SELECT * FROM users";

  // MySQL 쿼리를 요청할 때마다 단일 연결 사용
  connection.query(query, (error, results) => {
    if (error) {
      console.error('Error fetching users:', error);
      res.status(500).send("Error fetching users.");
      return;
    }

    res.status(200).json(results);
  });
});

// 서버 실행
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

				
			

이게 바로 제가 사용하던 방식이었습니다.

mysql.createConnection 을 통해서 연결을 위한 단일 설정을 취하고 있었지요.

그렇지만, 이 방식에는 다음과 같은 문제가 있었습니다. 

문제점 1 : connection 생성 자체에서 잘못된 방식을 취함

mysql.createConnection()을 사용하여 하나의 MySQL 연결만 생성합니다. 이 연결은 일정 시간이 지나면 유휴 상태로 인해 MySQL 서버에서 끊길 가능성이 있습니다. 그 상태에서 새로운 쿼리를 보내면, “Can’t add new command when connection is in closed state” 오류가 발생할 수 있습니다.

문제점 2 : 쿼리를 요청할때 connection.query 식으로 주먹구구식 요청을 보냄

매 요청마다 같은 단일 연결을 사용하고 있습니다. MySQL 연결이 끊긴 상태에서는 이 쿼리가 제대로 실행되지 않고, 연결을 재사용할 수 없는 상태가 됩니다. 연결 상태를 확인하지 않고 계속 사용하려고 하면 오류가 발생합니다.

문제점 3 : Pool 방식을 사용하지 않음

한 번 끊긴 연결을 자동으로 복구하거나, 여러 요청을 효율적으로 처리할 수 있는 연결 풀(Connection Pool)을 사용하지 않음으로 인해 다수의 동시 요청을 처리할 때 비효율적입니다. 각 요청마다 연결을 확인하거나 새로운 연결을 맺어야 할 수도 있습니다.

어떻게 해결할까?

				
					const express = require('express');
const mysql = require('mysql2');
const app = express();

// MySQL 연결 풀 설정 (올바른 방식)
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'my_database',
  waitForConnections: true,
  connectionLimit: 10, // 동시에 허용되는 최대 연결 수
  queueLimit: 0
});

// 올바른 방식의 라우터 예시
app.get('/api/get-users', (req, res) => {
  const query = "SELECT * FROM users";

  // MySQL 풀에서 연결을 가져와 쿼리 실행
  pool.query(query, (error, results) => {
    if (error) {
      console.error('Error fetching users:', error);
      res.status(500).send("Error fetching users.");
      return;
    }

    res.status(200).json(results);
  });
});

// 서버 실행
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

				
			

방금 위에서 문제가 있는 코드로 예시를 들었던 방식을 이렇게 고치면 해결이 됩니다.

const pool 을 만들고, 이를 이용해서 mysql DB 접근을 실행하는 방식인 겁니다. 

고치긴 해야겠습니다.

그리고 고칠 때, 또 한 가지를 더 정리하려 합니다.

저는 하나의 파일 내부에 라우터를 잔뜩 생성했는데… 이 정리 방식은 가독성이 매우 좋지 않고 효율적이지 못합니다. 

또한 유지 및 보수를 할 때, 작동하고 있던 백엔드를 종료했다가 다시 켜야하는 번거로움이 있어서 이걸 고칠 겁니다. 

어떻게 고칠까?

				
					// 필요한 모듈들 불러오기
const express = require("express");
const cors = require("cors");
const session = require("express-session");
const path = require("path");
require("dotenv").config(); // .env 파일을 통해 환경 변수를 로드

// 라우터 모듈들 불러오기
const authRouter = require("./routes/auth");
const projectRouter = require("./routes/project");
const dataRouter = require("./routes/data");

// Express 앱 생성
const app = express();
const port = process.env.PORT || 5000; // 포트를 환경 변수로 설정, 기본값은 5000

// CORS 설정 (클라이언트와 서버 간의 통신을 허용)
app.use(cors());

// JSON 형태의 요청을 처리할 수 있도록 설정
app.use(express.json());

// 정적 파일 제공 (예: 이미지, 파일 등)
app.use("/data", express.static(path.join(__dirname, "data")));

// 세션 설정
app.use(
  session({
    secret: process.env.SESSION_SECRET || "your_secret_key", // 세션 암호화 키
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false, httpOnly: true }, // 쿠키 설정
  })
);

// 라우터 설정
// /api/auth 경로로 authRouter에서 처리
app.use("/api/auth", authRouter);

// /api/project 경로로 projectRouter에서 처리
app.use("/api/project", projectRouter);

// /api/data 경로로 dataRouter에서 처리
app.use("/api/data", dataRouter);

// 서버 실행
app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

				
			

바로 이런 식으로 고칠 것입니다. 

backend.js 가 있는 파일 위치에 “routes” 라는 폴더를 만들고 그 폴더 내부에 여러 라우터 파일들을 만드는 거죠.

그리고 app.use 으로 처리를 하는 겁니다. 

그렇다면 DB 정보를 처리하기 위해서 어떻게 하면 좋을까요?

				
					// config/db.js
const mysql = require("mysql2");

// MySQL 연결 풀 설정
const pool = mysql.createPool({
  host: process.env.DB_HOST,        // 환경변수로부터 DB 호스트 정보 가져옴
  user: process.env.DB_USER,        // 환경변수로부터 DB 사용자명 가져옴
  password: process.env.DB_PASS,    // 환경변수로부터 DB 비밀번호 가져옴
  database: process.env.DB_NAME,    // 환경변수로부터 사용할 DB 이름 가져옴
  waitForConnections: true,         // 연결이 부족할 경우 대기하도록 설정
  connectionLimit: 10,              // 동시에 허용할 최대 연결 수 (여기서 10으로 설정)
  queueLimit: 0                     // 대기열 제한 없음 (무제한 대기)
});

// Promise 기반의 pool을 내보내기
module.exports = pool.promise();

				
			

저는 위와 같은 코드를 Backend.js 파일이 위치한 곳에 “config” 라는 폴더를 만들고 안에 db.js 를 생성하여 넣었습니다. 

그럼 이제 우리는 기존에 connection.query 를 이용하던 라우터들을 다음과 같이 고치면 됩니다. 

				
					const express = require("express");
const router = express.Router();
const bcrypt = require("bcrypt");
const pool = require("../config/db"); // DB 연결 풀을 불러옴

// 로그인 라우터
router.post("/login", async (req, res) => {
  const { id, password } = req.body;

  try {
    // DB에서 사용자 정보 조회
    const [results] = await pool.query("SELECT * FROM table WHERE id = ?", [
      id,
    ]);

    if (results.length > 0) {
      const user = results[0];

      // 비밀번호 검증
      const match = await bcrypt.compare(password, user.password);
      if (match) {
        res.status(200).send({
          message: "로그인 성공",
          user: { id: user.id, email: user.email },
        });
      } else {
        res.status(401).json({ message: "Invalid credentials." });
      }
    } else {
      res.status(404).json({ message: "Corresponding ID does not exist." });
    }
  } catch (error) {
    res.status(500).send("로그인 처리 중 오류가 발생했습니다.");
  }
});

module.exports = router;

				
			

포인트는 경로를 통해 db.js 를 불러오고, pool을  활용한다는 점입니다.

이제 훨씬 더 깔끔하게 라우터를 관리하며, DB 끊길 걱정은 없을 것 같네요. 

추가적으로 기존에 사용하던 nohup node backend.js & 방식 말고 다른 방식으로 백엔드 관리를 합시다. 

마치며...

				
					npm install pm2 -g

				
			

전역으로 pm2를 설치합니다. 

				
					pm2 start backend.js

				
			

위와 같이 pm2 로 특정 백엔드 파일을 실행  가능합니다. 

				
					pm2 list

				
			

다음의 방법으로 실해오디고 있는 파일들의 id 들을 확인 가능합니다. 

				
					pm2 logs

				
			

백엔드 디버깅을 위해 console.log 해놓은 것들은 이렇게 볼 수 있습니다. 

				
					pm2 save
pm2 startup


				
			

위 두 명령어를 차례대로 입력하여 지속적으로 pm2 실행이 가능합니다. 

				
					pm2 stop <프로세스 ID 또는 이름>

				
			

실행되던 특정 작업을 끄고 싶으면 위와 같이 실행하면 됩니다. pm2 list 를 통해서 확인 후 실행하면 되겠죠?

				
					pm2 kill

				
			

아예 종료하고 싶으면 이렇게 pm2를 끄면 됩니다. 

이제 조금 더 효율적으로 백엔드를 관리 가능하지 않을까 싶습니다.^^