Skip to content

node.js에서 graceful shutdown 적용하기 #31

@yuiseo

Description

@yuiseo

📝 node.js에서 graceful shutdown 적용하기

📚 주제:

  • graceful shutdown이란 무엇인가
  • 왜 적용해야 할까?
  • 실제 node.js 서버에서의 구현 예시
  • next.js에서의 적용

📖 핵심 내용:

Graceful Shutdown이란?

애플리케이션이나 서버가 종료될 때, 실행 중인 작업을 안전하게 처리하고 리소스를 정리하며 종료하는 과정을 말합니다. 이는 단순히 강제로 프로세스를 종료하는 것과는 달리, 시스템이 정상적인 상태에서 종료되도록 도와줍니다.

❔ Graceful Shutdown의 필요성

  • 데이터 손실 방지: 실행 중인 요청이나 트랜잭션이 갑작스럽게 중단되면 데이터가 손실되거나 손상될 수 있습니다. Graceful Shutdown은 이를 방지합니다.
  • 일관성 유지: 애플리케이션이 예상치 못한 종료로 인해 시스템 상태가 불일치하게 되는 것을 방지합니다.
  • 리소스 정리: 파일, 데이터베이스 연결, 네트워크 소켓 등 사용 중인 리소스를 정리하여 메모리 누수나 리소스 잠금을 방지합니다.
  • 사용자 경험 향상: 서비스가 종료되더라도 사용자 요청을 가능한 한 마무리하여 부정적인 영향을 최소화합니다.

🏗️ Graceful Shutdown의 작동 방식

  1. 종료 신호 수신:
    • 운영 체제에서 종료 신호(SIGTERM, SIGINT 등)를 받습니다.
  2. 새 작업 수락 중단:
    • 새 요청을 수락하지 않고, 기존 요청 처리에 집중합니다.
    • 예를 들어, 웹 서버는 더 이상 클라이언트 요청을 받지 않습니다.
  3. 기존 작업 완료:
    • 현재 진행 중인 작업(예: 데이터 저장, 파일 처리, API 응답 등)을 완료합니다.
    • 제한 시간을 두어 너무 오래 걸리지 않도록 설정할 수도 있습니다.
  4. 리소스 정리:
    • 열려 있는 파일, 데이터베이스 연결, 네트워크 소켓 등을 닫습니다.
    • 캐시나 임시 데이터를 정리합니다.
  5. 프로세스 종료:
    • 모든 작업이 완료되면 프로세스를 안전하게 종료합니다.

Node.js 기반의 Express 서버 구현 예시 코드

const express = require('express');
const app = express();
let server;

app.get('/', (req, res) => {
    res.send('Hello World!');
});

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

// Graceful Shutdown 구현
process.on('SIGTERM', () => {
    console.log('SIGTERM signal received: closing HTTP server');
    server.close(() => {
        console.log('HTTP server closed');
        // 추가적인 리소스 정리 작업 (예: DB 연결 종료)
        process.exit(0);
    });
});

process.on('SIGINT', () => {
    console.log('SIGINT signal received: closing HTTP server');
    server.close(() => {
        console.log('HTTP server closed');
        process.exit(0);
    });
});

next.js에서 적용하기

#8 에서 작성한 server.js 에 graceful shutdown 코드를 추가하였다.

//server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const v8 = require('v8');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

const activeConnections = new Set(); // 활성 연결을 추적하기 위한 Set

app.prepare().then(() => {
  const server = createServer((req, res) => {
    const parsedUrl = parse(req.url, true);
    handle(req, res, parsedUrl);
  });

  console.log(`App started with PID: ${process.pid}`);

  server.listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on frontend server');
  });

  // 활성 연결 추적
  server.on('connection', (socket) => {
    activeConnections.add(socket);
    socket.on('close', () => {
      activeConnections.delete(socket);
    });
  });

  // Graceful shutdown 처리
  process.on('SIGTERM', async () => {
    console.log('SIGTERM received. Closing server...');

    server.close(() => {
      console.log('Server closed. Closing active connections...');

      // 모든 활성 연결 강제 종료
      activeConnections.forEach((socket) => socket.destroy());

      setTimeout(() => {
        console.log('Shutdown after sleep.');
        process.exit(0); // 정상 종료
      }, 70000);
    });
  });

  // SIGINT 처리 (Ctrl+C)
  process.on('SIGINT', () => {
    console.log('SIGINT received. Performing graceful shutdown...');
    server.close(() => {
      console.log('Server closed.');
      process.exit(0); // 정상 종료
    });
  });
});

// 종료 시 code 출력
process.on('exit', (code) => {
  console.log(`Process exited with code: ${code}`);
});

SIGTERM 발생을 위한 종료 명령어

ps aux | grep node

해당 명령어를 통해 PID를 알아낸다.

kill -SIGTERM [PID]

알아낸 PID을 kill.

🔥 SIGINT 신호는 인식하나, SIGTERM은 인식하지 못하는 현상

image
로컬에서 위의 명령어를 통해 SIGTERM kill을 시도했으나 SIGTERM 로그가 남지 않고 143 코드를 남기며 종료 되었다.
image
SIGINT (ctrl+c)를 통한 kill의 경우에는 의도한 바 대로 로그를 남기며 정상 종료되었다.

👩🏻‍🚒 왜 SIGTERM 신호는 인식하지 못하는가?

  • stack overflownode.js 깃헙 이슈 답에 따르면 window환경에서 unix 스타일의 신호 (SIGTERM, SIGNUP등)를 기본적으로 지원하지 않아, Node.js에서 이런 신호를 수신하거나 처리하는 것이 제한적입니다.

image
image

  • Node.js는 Windows에서 process.kill() 및 ChildProcess.kill() 메서드를 통해 일부 신호를 에뮬레이트합니다. 그러나 이는 완전한 신호 지원이 아니며, 특정 신호(SIGTERM, SIGHUP 등)는 Windows에서 지원되지 않습니다.

👩🏻‍🚒 mac OS에서 테스트 - 성공

image
image


💡 참고 자료:
https://www.ryuollojy.com/articles/nodejs-graceful-shutdown
vercel/next.js#19693
https://velog.io/@hwisaac/NextJS-Depoyment#%EC%88%98%EB%8F%99-graceful-shutdown
vercel/next.js#60059
https://nextjs-ko.org/docs/app/building-your-application/deploying#manual-graceful-shutdowns
https://dtrunin.github.io/2022/04/05/nodejs-graceful-shutdown.html
https://stackoverflow.com/questions/63241807/nodejs-process-kill-doesnt-seem-to-work-on-windows-when-i-try-to-programaticall
https://stackoverflow.com/questions/63241807/nodejs-process-kill-doesnt-seem-to-work-on-windows-when-i-try-to-programaticall

Metadata

Metadata

Assignees

Labels

프론트엔드질문 리스트의 직무 구분을 위한 라벨

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions