Skip to content

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

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 overflow와 node.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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

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

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions