[자바 클래스 릴로딩 301] 톰캣, 글래스피쉬, OSGi, Tapestry 5 등 웹 배포시 클래스로더

참조: http://www.zeroturnaround.com/blog/rjc301/

자바 EE 애플리케이션

자바 EE 웹 애플리케이션을 배포할 땐 .WAR로 압축하고 톰캣 같은 서블릿 컨테이너에 배포한다. 하지만 이런 방법은 제품을 배포할 때나 쓸만하지 개발할 때는 소스 코드를 고치면 웹 브라우저에서 바로 확인하길 원한다.

재배포

서버에 애플리케이션을 배포하면 애플리케이션 당 클래스로더를 하나씩 만든다. 톰캑은 .WAR 애플리케이션 마다 StandardCOntext 클래스의 인스턴스가 WebappClassLoader를 만들어서 해당 웹 애플리케이션 클래스를 로딩한다. 사용자가 “reload” 버튼을 클릭하면 다음과 같은 일이 발생한다.

  1. StandardContext.reload() 메서드 호출
  2. 이전에 만든 WebappClassLoader를 새 인스턴스로 교체
  3. 서블릿을 참조하던 모든 레퍼런스 사라짐
  4. 새 서브릿 생성
  5. Servlet.init() 호출

tomcat-cl-reload

Sevlet.init()을 호출하면 애플리케이션 상태가 초기화 되고 수정한 클래스를 새 클래스로더에서 읽어 사용할 수 있다.

이때 문제가 바로 “초기화” 된다는 것이다. 애플리케이션을 재배포 할 때 마다 메타데이터/환결설정을 다시 읽어들이고, 캐시를 만들고, 어떤 확인 작업을 하는 등 초기화 작업하는데 시간을 쓰게 된다. 물론 애플리케이션 규모가 작다면 몇초안에 초기화 작업이 끝나버리겠지만 말이다.

핫 디플로이

웹 컨테이너는 보통 특정한 디렉토리(톰캑에서 webapp, JBoss에선 deploy)를 주기적으로 살펴보다가 새 웹 애플리케이션이 추가되거나 기존의 것이 변경되면 스캐너가 재배포를 수행한다. 톰캣의 경우 배포했던 .WAR 파일이 변경되면 StandardContext.redeploy()를 호출한다.

이때 사용자 입장에서는 아무런 부가작업 없이 변경되기 때문에 이걸 가지고 “핫 디플로이”라고 표현하고 있으며 이런 기능은 여러 애플리케이션 서버마다 각기 다른 이름으로 명하고 있다. autodeployment, rapid deployment, autopublishing, hot reload 등이 있다. 일부 컨테이너는 자신이 주시하고 있는 디렉토리가 아니라 사용자가 원하는 디렉토리를 주시할 수 있는데 이클립스에서 파일을 저장하면 재배포가 발생하게 되는 경우도 그것을 이용한 것이다. 수동으로 ‘재배포’ 버튼을 누른것과 거의 같기 때문에 코드를 변경한 결과가 곧바로 웹 브라우저에 반영되진 않는다.

여기서 주목할 또 한가지 문제는 재배포나 핫 디플로이 도중에 발생할 수 있는 “클래스로더 누수”다. 이전 글에서도 살펴봤듯이 클래스로더는 정말 쉽게 누수될 수 있고 또 금새 OutOfMemoryError를 야기할 수 있다.

Exploded 배포

웹 컨테이너의 주요 기능 중 하나로 “exploded 배포”라는 것이 있다. “unpackaged” 또는 “directory” 배포라고도 하는데 .WAR 압축 파일로 배포한느 것이 아니라 .WAR로 압축할 것과 동일한 구조를 지닌 디렉토리자체로 배포하는 것이다. (나도 이렇게 하는데ㅋㅋ)

exploded

왜 이렇게 하냐면 압축파일 만드는데 오래 걸리기도 하고 프로젝트 내부에 .WAR 압축파일 구조와 동일한 구조로 디렉토리를 만들 수 있기 때문이다. 이렇게 하면 서버에 .WAR 파일을 복사하지 않고도 디렉토리에 접근해서 직접 수정할 수 있다. 하지만~ 그렇게 한다고 해서 재배포 하지 않고 변견되진 않는다. 자바 클래스는 재배포하지 않고 다시 로딩 될 수 없다.

세션 영속화

재배포할 때 애플리케이션도 초기화 되기 때문에 세션 상태에 들어있던 정보도 초기화 된다. HTTP 세션에는 로그인 정보와 대화형 상태 정보가 들어있을 수 있다. 개발시에 이런 정보가 초기화 되면 귀찮기 때문에 대부분의 컨테이너는 이 문제를 해결하고자 자바 직렬화를 사용해서 HttpSession 맵에 들어있는 객체들을 직렬화 했다가 역질렬화 하는 형태로 세션 정보를 복구한다.

hot-deploy-session

세션 영속화는 오래전부터 대부분의 주요 컨테이너가 지원해왔다.(톰캣의 경우 Restart Persistence가 동작한다.)

OSGi

OSGi는 기본적으로 자체 클래스로더를 가지고 있는 모듈의 집합으로 볼 수 있다. 따라서 해당 클래스로더를 버리거나 새로 만들어서 모듈 자체를 새로 추가하고 없애는게 가능하다. 이때 모듈 하나를 집중해서 모면 웹 애플리케이션 하나랑 동일한 방식으로 만들어진다.

osgi

OSGi와 웹 컨테이너의 차이는 OSGi가 애플리케이션을 더 세밀하게 쪼개서 노출하고 있다는 것이다. 따라서 설계상 모듈이 단일 웹 애플리케이션보다 재배포 하는 단위가 작고 그만큼 재배포 시간이 빨라질 순 있지만 모듈을 어떻게 구성하느냐에 따라 달라질 것이다.

Tapestry 5, RIFE, Grails

테페스트리 5, RIFE, Grails 같은 최근 몇몇 웹 프레임워크는 애플리케이션 상태 관리를 직접 해준다. 애플리케이션 상태를 직렬화 하거나 어떻게든 간단하게 다시 생성할 수 있도록 만들어서 클래스로더를 버리고 난 뒤에 다시 초기화 하는 작업이 필요없다.

즉 개발자가 프레임워크가 제공하는 컴포넌트와 해당 컴포넌트의 라이프사이클을 사용하는 것이고 프레임워크가 각 컴포넌트를 초기화, 실행, 소멸시켜주는 것이다.

component

컴포넌트가 작고 클래스로더는 상세하게 나눠져 있으니까 빠르게 재배포한다는 장점이 있다. 따라서 코드가 즉각 다시 로딩 되고 개발이 유연해 진다. 하지만 여러 클래스 버전이 동시에 로딩되어 ClassCastException 같은 비호환 문제가 발생할 수도 있다.