Akashic Records

Java theory and practice: 웹 티어의 상태 복제 본문

오래된글/Java

Java theory and practice: 웹 티어의 상태 복제

Andrew's Akashic Records 2018. 4. 9. 11:15
728x90

대부분의 중요한 웹 애플리케이션들은 세션 별로 상태를 관리할 필요가 있다. 클러스터링 애플리케이션에서 상태가 관리 및 복제되는 방법은 애플리케이션의 확장성에 큰 영향을 미친다. 많은 J2SE와 J2EE 애플리케이션은 서블릿 API에서 제공하는 HttpSession에 상태를 저장한다

J2EE나 J2SE 서버 애플리케이션을 구현할 때, JSP technology, Velocity, WebMacro 같은 프리젠테이션 레이어를 통해서든 Axis 또는 Glue 같은 서블릿 기반의 웹 서비스 구현을 통해서든 자바 서블릿을 사용할 기회를 갖는다. 서블릿 API의 가장 중요한 기능 중 하나는 HttpSession 인터페이스를 통해 사용자 별 세션 상태의 인증, 만료, 관리 같은 세션 관리 기능이다.


세션 상태

거의 모든 웹 애플리케이션은 세션 상태를 갖고 있다. 로그인을 했는지의 여부를 기억하는 것 같이 단순할 수도 있고 쇼핑 카트의 내용, 이전 쿼리의 캐싱 결과, 20 페이지에 달하는 질문서에 대한 완벽한 응답 내역 같이 자세한 것이 될 수도 있다. HTTP 프로토콜이 그 자체로 STATELESS이기 때문에 세션 상태는 어딘가에 저장되어야 하고 다음에 쉽게 검색될 수 있도록 브라우징 세션과 연결되어야 한다. 다행스럽게도 J2EE는 세션 상태를 관리할 수 있는 여러 방식을 제공한다. 상태는 데이터 티어에, Servlet API의 HttpSession 인터페이스를 사용하여 웹 티어에, STATEFUL 세션 빈을 사용하여 Enterprise JavaBeans (EJB) 티어에 또는 쿠키나 숨겨진 폼 필드를 사용하여 클라이언트 티어에 저장될 수 있다. 하지만 현명하지 않은 세션 상태 관리는 심각한 퍼포먼스 문제를 초래할 수 있다.

자신의 애플리케이션이 HttpSession에 유저 당 상태를 저장하기에 적합하다면 이 옵션은 다른 대안들 보다 낫다. http 쿠키나 숨겨진 형식 필드를 사용하여 클라이언트에 세션 상태를 저장하면 심각한 보안 위험이 생길 수 있다. 애플리케이션 내부의 일부를 믿을 수 없는 클라이언트 레이어에 노출한다.(일찍이 한 전자상거래 사이트는 가격을 포함하여 쇼핑 카트 내용을 숨겨진 폼 필드에 저장하여 모든 HTML과 HTTP 사용자들이 0.01 달러에 어떤 물건이라도 살 수 있을 정도로 조작이 쉬웠다.) 게다가 쿠키나 숨겨진 폼 필드를 사용하면 더욱 어지러워지고 에러도 생기기 쉽다.

J2EE 애플리케이션에 서버측 상태를 저장할 수 있는 또 다른 대안으로는 STATEFUL 세션 빈을 사용하거나 데이터베이스에 대화형 상태를 저장하는 것이다. STATEFUL 세션 빈이 세션 상태 관리에 있어 유연하다면, 웹 티어는 세션 상태의 저장에 그 장점이 있다. 비즈니스 객체가 STATELESS라면 애플리케이션은 웹 서버와 EJB 컨테이너 보다는 웹 서버를 추가하여 측정될 수 있다. 이는 일반적으로 값도 덜 비싸고 수행하기도 편리하다. 이외에도 대화형 상태 저장에 HttpSession를 사용하면 좋은 점은 서블릿 API가 세션이 만료될 때 공지를 받을 수 있는 쉬운 방식을 제공한다는 점이다. 데이터베이스에 대화형 상태를 저장하는 일은 비용이 많이 든다.

서블릿 스팩으로 서블릿 컨테이너가 어떤 유형의 세션 복제 또는 영속성을 수행할 것인가가 결정되는 것은 아니지만, 상태 복제는 서블릿의 중요한 요소인 만큼 세션 복제를 수행하도록 몇 가지 요구사항 결정에 영향을 미친다. 세션 복제는 로드 밸런싱, 확장성, 결함 내구성, 높은 가용성의 혜택이 있다. 따라서 대부분의 서블릿 컨테이너는 몇몇 형태의 HttpSession 복제를 지원하지만 복제의 메커니즘, 설정, 타이밍은 구현에 따라 달라진다.


HttpSession API

간단히 말해서 HttpSession 인터페이스는 서블릿, JSP 페이지, 기타 프리젠테이션 레이어 컴포넌트가 다중 HTTP 요청들을 통해 세션 정보를 관리하는데 사용할 수 있는 다양한 방법을 지원한다. 이 세션은 특정 사용자에게 묶여있지만 웹 애플리케이션의 모든 서블릿 전반에 걸쳐 공유된다. setAttribute를 사용하여 이름으로 세션 애트리뷰트를 저장하고 getAttribute를 사용하여 이를 검색할 수 있다. HttpSession 인터페이스는 invalidate()같은 세션 생명주기 메소드를 포함하고 있다. Listing 1은 가장 일반적으로 사용되는 HttpSession 인터페이스의 엘리먼트이다:


Listing 1. HttpSession API


public interface HttpSession {
   Object getAttribute(String s);
   Enumeration getAttributeNames();
   void setAttribute(String s, Object o);
   void removeAttribute(String s);

   boolean isNew();
   void invalidate();
   void setMaxInactiveInterval(int i);
   int getMaxInactiveInterval();
   ...
}


이론적으로 클러스터를 통해 세션 상태를 완전히 복제하는 것이 가능하기 때문에 클러스터상의 어떤 노드도 모든 요청을 서비스 할 수 있고 로드 밸런서도 round-robin 방식으로 요청을 라우팅하며 실패한 호스트를 피한다. 하지만 그와 같은 치밀한 복제는 상당한 퍼포먼스 비용이 들고 구현도 복잡하다. 또한 클러스터가 특정 사이즈에 도달하면 확장성 문제가 생긴다.

보다 일반적인 접근방식은 로드 밸런싱을 유사 세션과 결합하는 것이다. 로드 밸런서는 커넥션을 세션과 연결하여 세션 내의 후속 요청들을 같은 서버로 라우팅한다. 이 기능은 다양한 하드웨어와 소프트웨어 로드 밸런서들이 지원한다. 그리고 복제된 세션 정보는 주 커넥션 호스트가 실패할 때와 세션이 페일오버(fail over) 될 필요가 있을 때에만 액세스 된다.


복제 방식

복제는 많은 잠재적 장점들이 있다. 가용성, 결함 내구성, 확장성 등이 그런 것이다. 게다가 세션 복제에 사용할 수 있는 많은 메소드들이 있다. 메소드 선택은 애플리케이션 클러스터의 크기, 복제의 목적, 서블릿 컨테이너에서 지원하는 복제 기능에 따라 달라질 수 있다. 복제는 CPU 사이클, 네트워크 대역폭, 디스크 기반 스키마, 디스크 또는 데이터베이스의 쓰기 비용 등의 퍼포먼스 비용이 든다.

거의 모든 서블릿 컨테이너들은 HttpSession에 저장된 객체를 직렬화 하기 위해 HttpSession 복제를 수행한다. 따라서 분산형 애플리케이션을 만들고자 한다면 세션에 직렬화가 가능한 객체를 두어야 한다. (어떤 컨테이너에는 EJB 레퍼런스, 트랜잭션 콘텍스트, 기타 직렬화 할 수 없는 J2EE 객체 유형 같은 엔터티들을 위한 특별한 조치가 있다.)


JDBC기반 복제

세션 복제의 접근 방식 중 하나는 세션 내용을 직렬화 하여 이를 데이터베이스에 작성하는 것이다. 이 방식은 단순하고 세션이 다른 모든 호스트로 페일오버 될 수 있을 뿐만 아니라 세션 데이터가 전체 클러스터의 실패에도 살아날 수 있다. 데이터베이스의 지원을 받는 복제의 단점은 퍼포먼스 비용이다. 데이터베이스 트랜잭션은 비싸다. 웹 티어에서는 잘 측정되는 반면 데이터 티어에서는 측정 문제가 생긴다. 클러스터가 너무 커진다면 세션 데이터의 양을 수용할 때 데이터 티어를 측정하기 힘들다.


파일 기반 복제

파일 기반의 복제는 데이터베이스를 사용하여 직렬화 된 세션을 저장하는 것과 비슷하다. 단, 공유 파일 서버가 데이터베이스가 아닌 세션 데이터를 저장하는데 사용된다는 점이 다르다. 이 방식은 데이터베이스를 사용하는 것 보다 비용이 적게 든다.


메모리 기반 복제

다른 복제 방법으로는 특별한 세션 데이터의 카피를 클러스터의 다른 서버와 공유하는 것이다. 모든 세션들을 모든 호스트에 복제하면 최상의 가용성과 로드 밸런서에 가장 쉽지만 결국 각 노드에 대한 메모리 소비 요구사항과 복제 메시지에 의해 소비되는 네트워크 대역폭 때문에 클러스터의 크기에 제한이 생긴다. 어떤 애플리케이션 서버는 버디(buddy) 노드로의 메모리 기반 복제를 지원한다. 각 세션이 주 서버와 하나(또는 그 이상)의 백업 서버에 존재해 있는 곳에서 가능하다. 그와 같은 스키마는 모든 세션을 모든 서버에 복제하는 것 보다 낫지만 세션을 다른 서버로 페일오버할 때 로드 밸런서의 작업을 복잡하게 한다. 어떤 서버가 세션을 갖고 있는지를 규명해야 하기 때문이다.


타이밍 문제

복제된 세션 데이터를 저장하는 방법을 결정하는 것 외에도 데이터를 언제 복제할 것인가 라는 문제도 제기된다. 가장 믿을 수 있지만 가장 값비싼 방법은 데이터가 변경될 때 마다 복제하는 것이다. 덜 비싸지만 페일오버 발생 시 데이터 손실의 위험을 초래할 수 있는 방식은 데이터를 매 N 초 마다 복제하는 것이다.

타이밍 문제와 관련하여 전체 세션을 복제할 것인지 또는 변경된 세션의 애트리뷰트만을 복제할 것인지의 문제도 있다. 이 모든 것은 신뢰성과 퍼포먼스의 차이이다. 애플리케이션이 그 차이를 만든다. 서블릿 개발자들은 페일오버 발생 시 세션 상태가 "오래된 것(stale)"(이전의 여러 요청으로부터 복제된 것에 기반함)이 될 수 있고 업데이트 되지 않은 세션 내용을 처리할 준비를 해야 한다.


컨테이너 지원

서블릿 컨테이너들은 HttpSession 복제 옵션과 이 옵션을 설정하는 방법에 따라 다양하다. IBM WebSphere®는 복제 옵션이 매우 다양하다. 인메모리 또는 데이터베이스 기반 복제, end-of-servlet 또는 시간에 따른 복제 타이밍, 전체 세션 스냅샷의 광고 또는 변경된 애트리뷰트의 광고 등, 다양한 선택 옵션들이 있다. 메모리 기반 복제는 JMS 퍼블리시-등록에 기반한다. 이는 모든 클론들, 하나의 버디 복제물, 또는 복제 전용 서버로 복제될 수 있다.

WebLogic 역시 많은 선택폭이 있다. 인메모리, 파일 기반, 데이터베이스 기반 등이 그것이다. Tomcat 또는 Jetty 서블릿을 사용할 때 JBoss는 메모리 기반 복제를 수행한다. 이때, end-of-servlet 또는 시간 기반 복제 타이밍 옵션과 변경된 애트리뷰트만 스냅샷을 하는 옵션이 수반된다. Tomcat 5.0은 모든 클러스터 노드에 메모리 기반 복제를 제공한다. 게다가 WADI 같은 프로젝트를 통해 세션 복제는 서블릿 필터링 메커니즘을 통해 Tomcat 또는 Jetty 같은 서블릿 컨테이너에 추가될 수 있다.


분산 웹 애플리케이션에서 퍼포먼스 향상하기

세션 복제에 어떤 메커니즘을 선택하든지 간에 몇 가지 방식으로 웹 애플리케이션의 퍼포먼스와 확장성을 증대 시킬 수 있다. 우선 세션 복제의 혜택을 받으려면 웹 애플리케이션을 전개 디스크립터에 분산 가능한 것으로 표시하고 세션에 배치된 모든 것이 직렬화 할 수 있도록 해야 한다.


세션을 최소화하기

복제 세션들은 세션에 저장된 객체 그래프의 사이즈에 따라 비용이 늘어나기 때문에 세션 안에 데이터를 최소화하도록 해야 한다. 그렇게 함으로서 직렬화 오버헤드, 네트워크 대역폭, 복제에 필요한 디스크 필요량을 줄일 수 있다. 특히 세션에 공유 객체를 저장하는 것은 좋은 생각이 아니다.


setAttribute를 무시하지 말 것

세션의 애트리뷰트를 변화시킬 때 서블릿 컨테이너가 최소한의 업데이트를 수행하고 있다면 조심해야 한다. setAttribute를 호출하지 않는 한 컨테이너는 애트리뷰트를 변경을 인식하지 못한다.


작은 단위의 세션 애트리뷰트 사용

최소한의 업데이트를 지원하는 컨테이너의 경우 하나의 큰 객체 보다는 여러 개의 작은 단위 객체를 세션에 둠으로써 세션 복제의 비용을 줄일 수 있다. 빠르게 변하는 데이터를 변경 할 때 컨테이너가 느리게 변화하는 데이터를 직렬화하고 광고하는데 영향을 끼치지 않는다.


무효화

사용자가 세션을 마감한다는 것을 알고 있다면 httpSession.invalidate()를 호출한다. 그렇지 않으면 세션은 만료 시 까지 지속된다. 이는 꽤 오랜 기간 메모리를 소비하게 될 것이다. 많은 서블릿 컨테이너들은 모든 세션을 통해 사용될 수 있는 메모리의 양을 제한하고 있다. 한계점에 다다르면 가장 최근에 사용된 세션이 직렬화 되고 이것이 디스크에 작성된다. 사용자가 세션을 마친 것을 알게 되면 컨테이너와 작업을 저장하고 이를 무효화한다.


세션을 깨끗하게 유지할 것

세션의 큰 아이템들이 그 세션의 일부를 위해 사용된다면 이들이 더 이상 필요가 없을 때 제거하라. 이들을 제거함으로써 세션 복제 비용을 줄일 수 있다.


728x90

'오래된글 > Java' 카테고리의 다른 글

자바 nio - 2  (0) 2018.04.09
자바 nio - 1   (0) 2018.04.09
Java Testing with SPOCK  (0) 2018.04.09
Java Persistence with MyBatis 3  (0) 2018.04.09
Java performance tips-3  (0) 2018.04.09
Comments