Rails 요청 처리에 대한 약간의 오해와 진실 :-)

[정보] rails에서 전역아닌 전역변수 - 인스턴스 변수

제목은 거창하지만 내용은 별거 아닙니다.

기본적으로 레일즈는 다른 언어의 유명 프레임웍처럼 요청을 다중 스레드로 처리하지 않습니다. 초기부터 2.2 이전까지는 선택 옵션도 없었죠. 무조건 싱글 스레드로 처리 했습니다. 그래서 레일즈 어플리케이션의 인스턴스가 하나 뿐이라면 하나의 요청이 끝나기 전까지 요청을 처리할 수 없었습니다.  동일한 컨트롤러의 동일메소드이냐 아니냐 혹은 다른 컨트롤러냐를 다 떠나서 말입니다. 예를 들어 아래와 같은 코드가 있고 script/server 명령을 통해 서비스를 기동했다고 가정해보죠.

#first_controller.rb
class FirstController < ApplicationController
  def index
    @start = Time.now
    sleep(30)
    @end = Time.now
    sleep(3)
  end

  def show
    @render = Time.now
    sleep(3)
  end
end

#first/index.html.erb
start: <%= @start %><br/>
end: <%= @end %>

#first/show.html.erb
render: <%= @render %>

#second_controller.rb
class SecondController < ApplicationController
  def index
    @render = Time.now
    sleep(3)
  end
end

#second/index.html.erb
render: <%= @render %>

일단 먼저 http://localhost:3000/first 로 index 메서드를 호출한 뒤 바로 다른 창에서 http://localhost:3000/first/show 와 http://localhost:3000/second 중 아무거나 먼저 호출한 뒤 역시나 또 다른 창에서 나머지 하나를 호출해 봅니다.

결과는 재미있게도 first/index 에서는 start와 end의 차이가 30초로 표시되고, 두 번째 호출한 것에서는 앞서의 end에 3~4초 더한 시간이, 마지막 호출한 것에서는 역시나 두 번째 시간에 3~4초 더한 시간이 표시됩니다. 레일즈 어플리케이션을 통틀어서 한번에 하나의 요청만 처리 되는 것입니다. 이렇게 처리되는 경우 실제 서비스 환경에서는 큰 문제가 될 수 있습니다. 한번에 한명의 유저가 보낸 하나의 요청만 처리되는 서비스라면 생각만 해도 끔찍하죠. 그래서 레일즈 어플리케이션의 운영 서비스는 일반적으로 여러 인스턴스를 띄우고 앞단에 Proxy 서버등을 두어서 처리하는게 일반적입니다. 여러 인스턴스이니깐 여러 요청을 동시에 처리할 수 있게 되는 것입니다. Passenger 를 이용해도 결국은 똑같습니다. Passenger가 설정된 범위내에서 인스턴스의 갯수를 요청에 따라 자동 조정한다 정도가 다릅니다. 이렇기 때문에 레일즈 어플리케이션에서는 긴 처리시간이 필요한 요청은 가능한 message queue 등을 이용해 처리하는 것이 좋습니다.

아마 원글의 작성자이신 zeous 님도 이러한 이유로 좀 헷갈리신 듯 합니다. 모든 객체지향 프로그래밍 언어에서 인스턴스 변수는 그 인스턴스의 컨텍스트내에서 전역변수 처럼 작동합니다. 인스턴스가 다르다면 A인스턴스와 B인스턴스의 인스턴스 변수는 전혀 별개니깐 아무런 상관이 없습니다. 레일즈도 루비 어플리케이션이기 때문에 @name 은 해당 컨트롤러의 인스턴스 변수가 되는 것이고 레일즈에서 컨트롤러의 인스턴스는 한번에 단 한개만 생성되기 때문에(기본적으로) zeous 님이 놀라신 것 같은현상이 자연스럽게 처리 되는 것입니다.

사실 레일즈가 단일 스레드 어픞리케이션으로 동작하는 이유는 레일즈의 잘못이라기보다 루비 자체의 스레드가 갖는 한계때문이었다고 생각됩니다.  현재까지의 레일즈 스레드는 멀티 CPU나 멀티 코어를 제대로 활용하지 못합니다. 스레드를 여러개 만들어도 루비 스레드의 특징상 실제로 동작하는 것은 한번에 하나뿐 입니다. 1.9에서는 좀 더 향상되긴 했습니다만 근본적인 문제가 해결된 것은 아닙니다. 그런데 아무래도 다른 유명 프레임웍들은 멀티 스레드로 잘만 동작하는데 왜 레일즈는 안되느냐에 대한 말들이 많았었나봅니다. 지금은 합병(?) 절차에 들어갔습니다만 레일즈의 좋은 경쟁자였던 Merb 는 초기부터 "우린 스레드 세이프하다!! 요청을 멀티 스레드로 처리한다!!" 라고 이야기 하기도 했습니다(그게 실제로 멀티코어나 멀티CPU의 이점을 잘 활용할 수 있는지는 차지하고라도). 그러다보니 레일즈 2.2 부터는 새로운 옵션이 추가되었습니다.  바로 config/environments/production.rb 파일에 (넵. 프러덕션 모드에서만 됩니다)

config.threadsafe!

를 추가하는 것입니다. 이렇게하고 서버를 프러덕션 모드로 기동하면 이제 레일즈 어플리케이션은 멀티 스레드 어플리케이션으로 동작합니다. 위에서 보여드렸던 예제를 다시 해보시면 앞의 호출이 끝나지 않아도 뒤의 호출이 실행되고 결과를 먼저 보여준다는 것을 아실 수 있습니다.  각기 다른 두 요청을 처리하는 메서드에서 동일한 인스턴스 변수 명을 공유하더라도 값이 뒤섞이거나 하지 않습니다.  다른 스레드에서 생성된 다른 인스턴스이기 대문입니다. 하지만 여전히 인스턴스 변수가 아닌 클래스 변수나 진정한 전역 변수 - 루비엔 이런게 있죠. (-_-)> - 레벨에서는 여전히 멀티 스레드 하에서 공유된 값이 뒤죽박죽 될 수 있다는 문제점과 멀티 스레드로 기동하더라도 성능이 향상되는 것은 아니라는 문제는 존재합니다. 물론 레일즈 자체의 인스턴스는 이전보다 더 적게 띄워도 된다는 장점이 있긴 합니다만. ㅎㅎㅎ http://m.onkey.org/2008/10/23/thread-safety-for-your-rails 를 한번 읽어보시면 좋을 듯 합니다. 제 허접한 설명보다는 백만배쯤 좋습니다 :-)

우연찮게 zeous 님의 글을 보고 생각나서 간략히 쓴다는게 주저리 주저리 길어졌습니다. 마날님의 말씀대로 전 너무 verbose 하다니깐요. -_-;;

PS1. 레일즈 서버의 인스턴스와 컨트롤러 클래스의 인스턴스 둘 다에서 인스턴스란 말이 나와 익숙하지 않으신 분들은 헷갈리실 수 있을 것 같습니다만, 다 써놓고 나니 수정하기 너무 귀찮아서...
PS2. JRuby등을 쓰면 멀티스레드의 이점을 충분히 활용할 수 있습니다. 물론 그래도 본문에서 언급한  스레드 안전성 문제는 여전합니다만 :-)

덧글

  • 레인블루 2009/10/06 11:22 # 답글

    와 저도 잘 배웠습니다.~
  • 허진영 2009/10/06 13:01 #

    제 짧은 지식이 도움이 되었다니 저도 좋네요 :-)
  • 제우스 2009/10/06 12:00 # 답글

    지적해주신것에 대해서 너무나도 감사하게 생각합니다. 사실 쪽팔리지만요..
    엉터리로 알고 있던것에 대해서 정리할수 있는 기회가 된것 같습니다.
    그래서 제 블로그에 반성문을 올렸습니다.. ㅠㅜ
  • 허진영 2009/10/06 13:27 #

    지적이라뇨, 당치않은 말씀을. 전 개인적으론 모르는 것 자체는 전혀 문제가 없다고 생각합니다. 단지 모르면서도 알려고 하지 않는 사람들이 문젠거죠. :-) 모르는걸 모른다고 인정하고 남의 지식을 받아들이는게 진정 대인배 스러운거죠 ( -_-)b! 어찌되었건 저도 모르는게 많은데 그나마 제가 알고있는 것으로 도움을 드려서 좋네요 ㅎㅎ
댓글 입력 영역