Graceful shutdown

ClassBinu·2024년 5월 13일

F-lab

목록 보기
19/65

우아한 종료

애플리케이션을 안전하게 종료하는 프로세스
반대되는 개념으로는 Hard Shutdown: 종료 시그널과 동시에 모든 작업 중단
Graceful Shtudown은 하고 있던 작업을 적절히 마무리한 뒤 종료되는 것

필요성

  1. 데이터 무결성 보장
  2. 자원 정리
  3. 사용자 경험 개선
  4. 시스템 유지 관리 용이성

구현

대부분 웹 프레임워크에서 시그널 처리로 graceful shutdown 구현

시그널

  • SIGTERM(Signal + Terminate): 시스템에서 프로세스를 종료하도록 요청하는 신호(주로 컨테이너를 종료할 때 사용)
  • SIGINT(Signal + Interrupt): 사용자가 인터럽트(컨트롤 + C)를 발생시킬 때 발생하는 신호
  • SIGQUIT(Signal + Quit): 사용자가 종료 명령을 내렸을 때 발생(컨트롤 + ) SIGINT와 유사하지만 코어 덤프를 생성함. 코어 덤프는 프로그램 비정상 종료 시 디버깅 위한 정보 저장함.

코어 덤프: 현재의 메모리 상태를 파일로 저장

INT와 QUIT 차이

SIGINT가 일반적으로 프로그램 안전하게 중지시키는 데 사용되며, 사용자가 의도적으로 프로그램 실행 중단을 원할 때 발생
SIGQUIT는 디버깅 목적으로 사용.

Node 예시

process.on('SIGTERM', () => {
  console.log('SIGTERM received. Shutting down gracefully.');
  server.close(() => {
    console.log('HTTP server closed.');
    // 데이터베이스 연결 해제, 필요한 정리 수행
    database.close(() => {
      console.log('Database connection closed.');
      process.exit(0);
    });
  });
});

process.on('SIGINT', () => {
  console.log('SIGINT received. Shutting down gracefully.');
  // 비슷한 종료 절차 수행
  process.exit(0);
});

Nest에서 적용

  1. 서버 인스턴스 획득
  2. 종료 시그널 감지해서 처리
  3. 서버 종료 로직 추가: 감지된 시그널에 대한 핸들러 내에서 연결을 종료, 데이터베이스 연결 및 기타 리소스 해제 수행
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DataSource } from 'typeorm';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const dataSource = new DataSource(/* configuration */);
  const port = process.env.PORT || 3000;

  // NestJS 애플리케이션 및 데이터베이스 리스너 시작
  await app.listen(port);
  console.log(`Application is running on: ${await app.getUrl()}`);

  // Graceful shutdown 로직을 통합 관리
  // 시그널 핸들러
  const shutdown = async () => {
    console.log('Shutting down gracefully...');
    await dataSource.close();  // 데이터베이스 연결 종료
    await app.close();  // NestJS 애플리케이션 종료
    console.log('Resources released, application closed.');
    process.exit(0);  // 프로세스 종료
  };

  // 시그널 처리 핸들러 등록
  process.on('SIGINT', shutdown);
  process.on('SIGTERM', shutdown);
}

bootstrap();

close()

await dataSource.close(); - 데이터베이스 연결 종료

이 코드는 typeorm 라이브러리를 사용하여 설정된 데이터베이스 연결을 종료합니다. DataSource 객체는 데이터베이스 연결을 관리하는 주체로, 여기에는 SQL 데이터베이스와의 연결이 포함됩니다. close() 메소드는 다음과 같은 일련의 작업을 수행합니다:

연결 종료: 데이터베이스와의 모든 활성 연결을 안전하게 종료합니다. 이는 현재 진행 중인 모든 쿼리가 완료되거나 시간 초과될 때까지 기다립니다.
리소스 해제: 연결을 통해 사용되는 모든 리소스(메모리, 네트워크 소켓 등)를 해제합니다.
풀(Pool) 닫기: 연결 풀이 사용되는 경우, 풀에 있는 모든 연결을 닫고 관련 리소스를 정리합니다.
이 작업은 데이터베이스 작업을 안전하게 종료하고, 데이터베이스 서버에 대한 불필요한 부하를 방지하며, 데이터 손실의 위험을 최소화하는 데 중요합니다.

await app.close(); - NestJS 애플리케이션 종료

app.close() 메소드는 NestJS 애플리케이션을 종료할 때 호출됩니다. NestJS는 Node.js 위에서 동작하는 프레임워크로, app.close() 메소드는 애플리케이션과 관련된 다양한 종료 작업을 수행합니다. 이 메소드의 주요 작업은 다음과 같습니다:

HTTP 서버 종료: NestJS가 사용하는 HTTP 서버 인스턴스를 종료합니다. 이 과정에서 모든 들어오는 요청이 처리 완료되거나 거절됩니다.
이벤트 루프 정리: Node.js의 이벤트 루프에서 NestJS 애플리케이션이 사용하고 있는 모든 비동기 작업이 완료되도록 기다립니다. 이는 메모리 누수를 방지하고, 애플리케이션 종료 시 안정성을 보장합니다.
종속성 주입(Dependency Injection) 시스템 해제: NestJS의 종속성 주입 컨테이너를 해제하고, 관련 서비스와 컨트롤러 인스턴스의 정리 및 종료 로직을 수행합니다.
모듈 해제: 등록된 모든 모듈을 해제하고, 각 모듈에 등록된 프로바이더(서비스, 컨트롤러 등)의 onModuleDestroy 생명주기 이벤트를 호출하여 추가적인 종료 처리를 수행합니다.

왜 이렇게 우아하게 종료해야하지?

  1. 데이터 무결성 보장: 트랜잭션 안전하게 완료
  2. 사용자 경험 개선: 사용자의 마지막 요청까지 안전하게 처리하고 응답 보냄
  3. 시스템 자원 정리: 오픈된 파일 핸들, 네트워크 연결, 메모리 등을 체계적으로 해제(리소스 누수 방지)
  4. 유지보수 및 업데이트 용이성: 안정적 종료, 안전한 재시작
  5. 이벤트 로깅 및 모니터링: 문제 추적 분석
  6. 법적 및 규제 준수: 특정 산업에서 데이터 보호 및 처리

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks(); // Shutdown Hooks 활성화

  const port = process.env.PORT || 3000;
  await app.listen(port);
}

bootstrap();

0개의 댓글