이번 글에서는 프로세스의 종료, 특히나 Graceful shutdown에 대해서 짧게만 다뤄볼 예정입니다.
0. 프로세스의 완전한 종료란?
프로세스의 종료에 대해서 언급하기 이전에, 프로세스가 어떠한 과정을 통해서 실행되는지에 대해서 아주 간략하게 알아보겠습니다.
- 스토리지에 있는 프로그램을 메모리에 올린다
- 이전에 실행된적이 있는 프로세스라면 해당 프로세스에 대응하는 PCB (Process Control Block)을 읽어내서 이전 상태로부터 시작하고, 이전에 실행된적이 없는 프로세스라면 프로그램 코드를 읽어내서 프로세스를 실행한다. 그와 동시에 PCB를 생성한다. 그리고 프로세스는 Ready 상태에 진입한다.
- 프로세스 스케쥴러에 의해서 CPU에 디스패치가 되면 프로세스는 자신의 작업을 수행합니다.
프로세스의 종료는 프로세스의 실행의 반대로만 한다면 아래의 과정을 거쳐야 할 것으로 예상됩니다.
- 종료 신호를 받아온다
- 프로세스를 종료하기 전에 자신이 수행하던 작업을 모두 마무리 지은 다음에, 지금의 프로세스의 상태를 PCB에 기록한다
- 프로세스에 할당된 모든 자원을 운영체제에 반납한다
1. 프로세스 종료 시그널
프로세스의 종료 시그널을 5개 정도만 소개하고 다음 단락으로 넘어가겠습니다.
번호 | 이름 | 설명 | 기본 처리 |
1 | SIGHUP (HUP) | Hang Up의 약자로, 데몬 관련 환경 설정 파일을 변경시키고 변화된 내용을 적용하기 위해 재시작할 때 해당 시그널이 적용됩니다. | 종료 |
2 | SIGINT (INT) | 키보드로부터 오는 인터럽트 시그널로, 실행을 중지시키는 시그널입니다. [Ctrl + C] | 종료 |
9 | SIGKILL (KILL) | 프로세스를 강제로 종료시키는 시그널 | 종료 |
15 | SIGTERM (TERM) | 가능한 정상 종료를 시키는 시그널로, kill 명령어의 기본 시그널입니다. | 종료 |
17 | SIGCHLD (CHILD) | 자식 프로세스가 stop 되거나 종료되었을 때 부모 프로세스에게 전달되는 시그널 | 무시 |
2. Graceful Shutdown
Disposability - The Twelve-Factor App 을 참고하면, Graceful shutdown에 대한 아래의 언급을 확인해볼 수 있습니다.
Maximize robustness with fast startup and graceful shutdown.
Processes with the twelve factors shut down gracefully when they receive a SIGTERM signal from the process manager.
즉, 견고한 백엔드 어플리케이션이라면 프로세스의 빠른 시작과, 그리고 graceful shutdown을 지켜야한다는 뜻인데요, graceful shutdown에 대해서 좀 알아보도록 하겠습니다.
Graceful shutdown이란, 프로세스가 종료될 때 최대한 side effect가 일어나지 않도록 로직들을 잘 처리하고 종료하는 것을 의미합니다.
결국에는 process가 사용자 혹은 운영체제로부터 종료 신호를 받게되면 진행하던 로직을 마저 잘 처리한 다음에 종료되는 것이 핵심이라고 볼 수 있겠습니다.
1) Web process에서의 Graceful Shutdown
Web process에 대한 graceful shutdown은 그저 종료 signal을 받으면 listening port를 닫음으로써 더 이상의 요청을 받지 않고 처리 중인 request는 마저 잘 처리하고 DB connection이 존재한다면 커넥션을 끊고 프로세스를 종료하면됩니다.
2) Worker process에서의 Graceful Shutdown
Worker process에서의 Graceful Shutdown은 이전의 방식보다 복잡해집니다.
만일 RabbitMQ 혹은 Kafka와 같은 메세지 큐 또는 이벤트 큐를 사용하여 발행된 메세지를 구독하여 처리하는 컨슈머 프로세스를 shutdown 한다고 가정해봅시다. 그리고 해당 프로세스에는 shutdown hook이 설정되어있지 않다고 가정합니다.
다행스럽게도 처리 중인 이벤트가 없는 상황 (전 파티션에 거쳐서 컨슈머 랙이 0이며, 그리고 polling된 이벤트가 하나도 없는 상황)이라면 프로세스를 바로 죽이더라도 상관이 없겠으나, 만일 해당 토픽의 이벤트를 구독중인 컨슈머가 하나라도 있는 상태에서 해당 토픽을 구독하는 컨슈머 프로세스를 하나라도 죽이게 된다면 즉시 컨슈머 리밸런싱이 일어나면서 처리중인 이벤트가 소실되어 서비스에 일시적인 장애를 일으킬 가능성이 존재합니다.
따라서 worker process에 대한 graceful shutdown은 아래의 과정을 거쳐서 진행해주는게 좋습니다.
- 사용자 혹은 운영체제로부터 종료 signal을 수신하면 처리중인 이벤트에 대해서 이벤트 큐에 반환한다. 이 때 반환되는 내용은 NACK에 대응되는 이벤트를 반환해야하며, 멱등성이 있는 내용으로 반환을 해야합니다. (보상 트랜잭션)
- shutdown hook을 정의하여 종료되면서 오프셋을 갱신해줍니다
3. 결론
그러면 모든 백엔드 어플리케이션에 대해서 Graceful Shutdown을 적용하면 좋은게 아닌가 라고 생각할 수 있습니다. 물론 일반적인 web backend application의 경우 처리 중인 request가 있다고 하더라도 해당 request가 반드시 처리되어야만 하는 요청이 아닌 경우에는 graceful shutdown 없이 kill -9 옵션을 통해서 프로세스를 종료하여도 괜찮다고 생각합니다.
그러나 분산 시스템에 있어서는 Graceful Shutdown을 적용하지 않으면 발생할 수 있는 side effect가 존재하기 때문에 shutdown hook을 통한 종료전 로직 처리, 그리고 안전한 프로세스 종료를 통해 프로세스를 종료해주는 것이 좋다고 생각합니다.
4. References