본문 바로가기

소프트웨어-이야기/인프라

(Phusion Passenger) 패신저에서 트래픽을 어떻게 분산시켜줄까?


Pushion Passenger는 프로세스를 관리하고, HTTP Request를 라우팅처리해준다.

그리고 패신저는 성능 최적화를 위해 CPU 코어에 대한 처리를 분산시키거나, 

응답시간을 최소화하기 위해 제일 놀고있는 프로세스에서 HTTP Request가 처리될 수 있게 해준다.


이 포스팅은 루비온레일즈에 셋팅된 패신저에서 로드밸런싱 처리를 해주는 방법에 대한 내용을 담고 있다.



루비온레일즈는 기본적으로 한 번에 한 요청을 처리한다.

그래서 패신저는 어플리케이션에서 여러 프로세스들이 작동될 수 있게 해주는 역할을 한다. 

( 프로세스마다 어플리케이션의 인스턴스를 하나씩 띄우는 개념인 것같다. 개발환경에서 레일즈 앱을 하나 띄우는 것처럼, 패신저가 레일즈 앱을 알아서 여러개 띄어준듯한 느낌으로 이해된다. )

Request가 날라오면, 패신저는 이걸 Request Queue 라는 곳에 요청 정보를 쌓는다. 

그리고 놀고있는 어플리케이션 프로세스가 생기면, 큐에 쌓였던 요청을 꺼내서 처리해낸다.


패신저에 떠있는 프로세스들이 모두 사용중이면, 패신저는 새로운 프로세스를 하나 더 띄운다.

그런데 만약 이 프로세스가 패신저 설정에 셋팅해놓은 최대 프로세스 수만큼 차게 되면, 

이 요청은 앞서 말한 Request Queue에 쌓여서 다음 차례를 대기하게 된다. 


Maximum proccess concurrency

Maximum proccess concurrency는 한 번에! 동시에! 처리할 수 있는 Request의 수를 의미한다.

레일즈에서는 동시에 처리할 수 있는 요청 수는 1개이다. 이는, 패신저 프로세스가 한 번에 한 요청만 처리할 수 있다는 걸 의미한다.


루비온레일즈에서는 한 프로세스에서 여러 요청들이 처리되는 멀티쓰레드 설정을 할 수 있기도 하다. ( 쓰레드 사용을 위한 설정값 : passenger_concurrency_model ) 아, 회사 분들이 알려주셨는데 멀티쓰레드 옵션은 Enterprise에서만 사용가능하다고 한다. 

만약 패신저 옵션을 쓰레드로 설정해놓은 경우, 한 프로세스에서 쓰레드 갯수만큼 요청이 처리될 수 있기도 하다. 


가장! 펑펑~~ 놀고있는 프로세스에서 제일 먼저 요청이 처리된다!

패신저에서 프로세스에 Reuqest를 할당해주는 기본 개념은 아래와 같다.

패신저는 각 프로세스들이 최근에 얼마나 많은 리퀘스트를 처리했는지를 저장해놓고 있다.

그래서 요청이 왔을 때, 가장 적게 Request를 처리했던 프로세스에서 요청이 처리될 수 있도록 할당해준다.


A. 제일 먼저 만들어진 프로세스가 먼저 채택된다! 

프로세스들이 다 놀고있으면, 패신저는 가장 먼저 만들어진 프로세스에게 새로 온 요청을 처리하도록 시킨다.


Process A: handling 1 request
Process B: handling 0 requests
Process C: handling 0 requests

예를 들어 프로세스가 이렇게 떠있을 때, 요청이 들어오면 

패신저는 항상 B에게 요청을 처리하라고 시킨다.


이 방식이 주는 장점은 2가지가 있다.


1. 이 원칙은 다이나믹 프로세스 스케일링 알고리즘 ( Dynamic Process Scailing )에서 사용된다.

   패신저는 오토스케일인을 할 때, 

   Request를 한동안 처리하지 않고, 가장! 오랫동안 놀고있던 프로세스를 골라서 죽인다. 

   ( 랜덤으로 죽인다거나, 라운드로빈처럼 돌아가면서 죽인다거나 하는 개념이 아니다! )


   만약 요청이 왔을 때, 랜덤하게 처리하게 한다면

   가장 오랫동안 일을 안하고 놀고있던 프로세스를 고르기 어려워진다. 


2. 메모리 캐시 재사용 관점

  첫번째 요청으로 프로세스가 메모리단에 캐싱처리를 해놨다면,

  다음번에도 이 프로세스를 사용하는게 캐시를 재사용할 수 있단 점에서 장점이 있다.

  어플리케이션 레벨에서 데이터를 가져오는 데에서 성능 향상을 기대할 수 있다.


B. 그래서 트래픽이 골고루 분배되지 않는 것처럼 보일 때가 있다! 

패신저는 놀고있는 프로세스들 중, 가장 먼저 생겨난 프로세스에게 트래픽을 보내준다. 

라운드로빈처럼 골고루 분배해주는 개념이 아니다.


그래서 만약 10개의 프로세스가 떠있는데, 요청이 거의 뜨문뜨문 오고, 

오는 경우에도 동시에 처리해야하는 요청수가 1개라고 생각해보자. 

그러면 프로세스가 10개가 있더라도, 제일먼저 생겨난 첫번째 프로세스에 계속 트래픽이 할당될 것이다. 


/var/www/phusion_blog/current/public:
  App root: /var/www/phusion_blog/current
  Requests in queue: 0
  * PID: 18334   Sessions: 0       Processed: 4595    Uptime: 5h 53m 29s
    CPU: 0%      Memory  : 99M     Last used: 4s ago
  * PID: 18339   Sessions: 0       Processed: 2873    Uptime: 5h 53m 26s
    CPU: 0%      Memory  : 96M     Last used: 29s ago
  * PID: 18343   Sessions: 0       Processed: 33      Uptime: 5m 8s
    CPU: 0%      Memory  : 96M     Last used: 1m 4s ago
  * PID: 18347   Sessions: 0       Processed: 16      Uptime: 2m 16s
    CPU: 0%      Memory  : 96M     Last used: 2m 9s ago


여기에서 첫번째, 두번째에만 Processed가 몰려있는 것을 볼 수 있다. 그런데 이건 뭐 지극히 정상적인 상황이라고 한다.

Processed는 이 프로세스에서 요청이 처리된 갯수를 의미한다.


생각해보니, 

지난주에 회사에서 서버의 패신저 프로세스를 모니터링 했었을 때,

특정 프로세스에서 메모리가 몰려있는 경우가 있었다.

극단적인 예시로 보자면 어떤 프로세스는 400MB인데, 어떤 프로세스는 40MB인 상황이였던 셈이다

.

아무쪼록.. 어디선가 메모리릭이 발생하고 있긴 했던 상황이였는데

가장 먼저 생겨난 프로세스에게 트래픽을 보내준 이 알고리즘을 기반으로 생각하면 

그렇게 이상한, 납득하지 못할만한 상황은 아니였던것같다.


C. HeadOfBlocking 문제가 없다

HeadOfBlocking은 앞에서 보냈던 요청에 문제가 생겨서, 

뒤에 있는 요청들을 처리하는 데에도 문제가 발생하는 병목현상을 의미한다.


패신저의 경우, Single Shared Queue를 사용하고 있다.

즉 하나의 Reuqest Queue에 요청이 오는 것들을 쌓아놓고, 이걸 프로세스들이 가져다가 쓸 수있게 하는거다.

이 덕분에 한 프로세스에서 응답을 처리하는데 시간이 오래 걸리면, 

이 프로세스가 처리되기를 기다리기 보다는, 다른 여유있는 프로세스서 요청을 처리할 수 있게 되었다. 


Queue가 넘치는 경우

기본으로 지정해놓은 큐의 크기보다 많은 Request가 큐에 쌓이는 경우, 큐가 넘쳐버리는 현상이 발생한다.

이 때 오는 요청들은 503 응답코드를 내려주게 되고, 패신저가 로드밸런싱 기능을 정상적으로 처리할 수 없게 된다.

( 지난주에 일하면서 이 503 응답을 봤었다... 프로세스 수가 너무 많아져서 최대 프로세스수를 줄이니, 큐에 너무 많은 리퀘스트가 쌓여서 큐가 넘치는 현상이 생겼었다. 그래서 서버를 우선 증설하고, Request 수를 줄이는 방식으로 처리했었다 )




<끝>