Nginx 블로킹 문제점과 쓰레드풀

Nginx는 이벤트 기반의 비동기-논블로킹 방식을 사용한다. 덕분에 다량의 트래픽을 빠르게 처리하는 것이 가능하다. 하지만 Nginx의 이런 동작구조에서도 ‘블로킹’ 문제가 발생하는 이벤트들이 존재하였고 이 문제를 보완하기 위해 쓰레드 풀 매커니즘을 추가하게 되었다.

Nginx의 동작구조에 대한 내용은 이 포스트를 참고하자.

Nginx 블로킹 문제

Nginx의 각 Worker Process싱글스레드로 동작한다. 하지만 비동기 작업들을 Event Loop가 OS 커널의 비동기 I/O를 사용함으로써 대기없이 이벤트들을 빠르게 처리할 수 있었다. 그런데 만약 Event Loop가 비동기 I/O를 사용하지 못하고 해당 작업을 처리하게 된다면 어떻게 될까? 싱글 스레드를 사용하는 Worker Process의 특성상 후속 작업들은 처리되지 못하고 긴 시간 Event Queue에서 대기하는 상태가 될 것이다.

블로킹 작업 존재
블로킹 작업 존재할때

우리의 만능 비동기 I/O(보통 OS 커널의 epoll과 kqueue 매커니즘을 사용)가 있으니 위와 같은 상황이 발생하지 않을 거라고 생각할 수 있지만 블로킹은 꽤나 빈번하게 일어난다. 블로킹이 발생하면 성능에 지대한 영향을 끼치므로 민감하게 받아들일 수 밖에 없다. 특히, 서드 파티 모듈에서 블로킹 작업이 발생하게 되면 이러지도 저러지도 못하는 상황이 될 수도 있다. 블로킹 문제는 보통 아래와 같은 이벤트들을 처리하게 되면 블로킹 되는 상황이 발생한다.

  • CPU 집약적 작업
  • Disk I/O
  • mutex와 같이 외부 시스템과 동기식 상호작용
  • 리눅스에서 파일 접근 시 메모리 캐시를 우회하여 디스크에 직접 접근 시

💡 epoll과 kqueque
epoll 과 kqueue는 유닉스 시스템에서 사용되는 I/O 이벤트 알림 매커니즘이다.(epoll은 리눅스 계열, kqueue는 BSD 계열 - macOS)
파일 디스크립터(소켓)을 모니터링하고 상태 변경을 감지를 비동기적으로 감지한다. 상태가 변경되면 콜백 로직을 수행하거나 구독자들에게 전파한다.


쓰레드 풀

블로킹 작업을 최대한 회피하면 좋겠지만 작업자체가 그럴 수 없는 경우가 많다. 때문에 Nginx에서는 1.7.11 버전에서부터 쓰레드 풀 매커니즘을 도입하게 되었다.

쓰레드 풀은 Worker Process 에서 작업을 처리하는 싱글 스레드 이외에 블로킹 작업이 될 수 있는 것들을 처리하기 위해 전문적으로 처리하는 쓰레드들을 미리 만들어 놓는 공간이다.

만약 쓰레드 풀을 사용한다면 다음과 같이 동작하는 것을 예상할 수 있다.

❗️주의
쓰레드 풀의 Task Queue와 Worker Process의 Event Queue는 서로 다른 Queue이다.

  1. Worker Process가 Socket을 통해 Event를 받아 Event Queue에 적재한다.
  2. Event Loop가 Event Queue에서 Event를 꺼내 작업을 진행한다.
  3. 이 작업이 블로킹 작업이라고 가정해보자. 해당 작업을 Event Loop가 처리하지 않고 쓰레드 풀에 존재하는 Task Queue로 던진다.
  4. 쓰레드 풀은 Task Queue에서 작업들을 꺼내 쓰레드 풀에 존재하는 쓰레드들에게 작업을 위임한다.
  5. 쓰레드가 작업을 끝내면 Worker Process의 이벤트 사이클로 돌아간다.

Thread Pool
Thread Pool

블로킹 작업을 쓰레드 풀을 이용하여 처리한다면 Nginx의 성능이 최소 2배 이상은 올라간다고 한다. 하지만 무작정 쓰레드 풀을 사용하는 것은 권장하지 않는다.(쓰레드 풀은 기본적으로 disable 되어있다.) 성능 측정 후 비정상적이 성능을 보인다면 도입하는 것을 추천한다.

참고

카테고리:

업데이트:

댓글남기기