내용 목차
본 장에는 웹 애플리케이션의 성능 향상을 위한 JEUS WebCache를 적용하는 방법을 설명한다.
동시 요청자 수의 증가로 인하여 응답 속도가 떨어지는 등의 웹 애플리케이션 성능 저하 문제가 발생한다면 하드웨어 측면과 소프트웨어 측면의 해결 방법이 있을 것이다.
하드웨어 측면의 해결책은 서버를 증설하여 계속 들어오는 요청(request)을 로드 밸런싱(load balancing)하여 부하를 분산하여 응답 속도를 높이는 방법이다. 그러나 이 방법은 서버 추가에 대한 비용 증가와 클러스터링 등으로 인한 서버 운용 및 관리의 복잡함을 야기시킬 것이다.
소프트웨어 측면의 해결책은 서버 증설없이 웹 애플리케이션에서 많이 사용되는 데이터를 캐싱하는 것이다. 그러면 다음 요청에서는 데이터의 재생산없이 캐싱된 데이터를 이용함으로써 웹 애플리케이션의 응답시간을 줄여서 성능을 향상시킬수 있다.
본 절에서는 JEUS 시스템에서 웹 애플리케이션의 성능 향상을 위해서 JEUS WebCache를 어떻게 적용할 수 있는지에 대해서 알아 볼 것이다.
제공되는 캐싱 방법은 다음과 같다.
JSP Caching
태그 라이브러리(Tag Libarary)를 사용하여 JSP 내의 일부분을 캐싱한다. 전체 페이지 중에서 부분 변경이 발생하는 JSP 페이지 요청하는 경우에 적합하다.
HTTP Response Caching
HTTP 응답 전체를 캐싱한다. Static Content에 대한 요청에 적합하다.
JEUS WebCache에 캐싱되는 엔트리는 SoftReference로 구현되어있다. 그래서 심각한 메모리 증가로 인한 OutOfMemory 에러를 미연에 방지할 수 있다.
캐싱 기능을 효과적으로 사용하기 위해서 빈번하게 요청되거나, 수행 로직이 복잡해서 또는 DB로부터 데이터를 가져오는 시간이 오래 걸려 응답시간이 길어질수 있는 웹 페이지를 캐싱 대상으로 선택하는 것이 바람직하다.
JSP Caching은 JSP 태그 라이브러리를 사용하여 JSP 페이지 내의 일부분을 JEUS WebCache에 저장함으로써 웹 애플리케이션의 성능을 향상시키는 방법이다.
JEUS WebCache에서는 사용자 정의(custom) 태그로 jeus:cache를 사용한다. JSP 페이지 내에서 캐싱을 원하는 콘텐츠가 있는 부분을 이 태그로 감싸면 첫 번째 요청에서 태그 내의 바디 콘텐츠(body contents)가 생성되어 브라우저에 전송되고 이 콘텐츠는 캐싱된다. 다음 요청부터는 메모리에 캐싱된 콘텐츠가 브라우저로 보내진다.
jeus:cache 태그를 사용하는 기본적인 형식은 다음과 같다.
<%@ taglib uri=”http://www.tmaxsoft.com/jeuscache” prefix=”jeus” %> <jeus:cache name=”...” key=”...” scope=”...” timeout=”...” size=”...” async=”...” df=”...”> . . . Body content to be cached. . . </jeus:cache>
위 설명에서 제외된 flush 속성은 닫힌 태그(/>)를 사용하는데 이 후 절에서 설명된다.
JSP 캐싱에서 사용하는 알고리즘은 LRU이다. 그래서 JEUS WebCache 최대 허용 개수를 초과하면 LRU 알고리즘에 의해서 기존에 캐싱된 엔트리가 제거된다.
그리고 TLD(Tag Libarary Descriptor) 파일은 'jeus.jar' 파일에 포함되어 배포되며 그 위치는 'jeus/servlet/cache/resource/jeuscache.tld이다. jeuscache.tld' 파일에 대한 URI 정보를 JSP Engine에 전달하기 위해서 jeus:cache 태그 내의 taglib URI를 반드시 'http://www.tmaxsoft.com/jeuscache'으로 명시해야 한다.
다음은 "name 속성", "name 속성 + key 속성"을 사용하여 엔트리를 캐싱할 때 사용하는 자료 구조이다.
위 그림에서 Name1, Name3은 key 속성 없이 name 속성만으로 태그를 사용할 때 엔트리가 캐싱되는 방식이며 key 속성은 물론 name 속성까지도 사용되지 않을 때도 이 방식이 사용된다. 그러나 name 속성과 key 속성 모두가 사용되는 태그에서는 Name2와 같은 방식으로 엔트리가 캐싱된다.
다음은 jeus:cache 사용자 태그를 정의한 jeuscache.tld이다.
이 파일은 jeus.jar 내에 'jeus/servlet/cache/resource/jeuscache.tld'에 위치한다.
[예 8.1] <cache> 태그 설정 : <<jeuscache.tld>>
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>jeuscache</short-name> <uri>http://www.tmaxsoft.com/jeuscache</uri> <display-name>JEUSCache Tag Library</display-name> <tag> <name>cache</name> <tag-class>jeus.servlet.cache.web.tag.CacheTag</tag-class> <body-content>JSP</body-content> <description>JEUS WebCache</description> <attribute> <name>flush</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>timeout</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>scope</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>name</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>size</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>key</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> <attribute> <name>async</name> <required>false</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
다음은 각 태그의 속성에 대한 설명이다.
<name>
여러 페이지에서 캐싱된 데이터를 공유하기 위해서 사용되며 해당 scope 내에서 유일해야 한다.
name 속성을 지정하지 않았을 때는 HTTP URI 정보 등을 사용하여 내부적으로 생성하여 관리된다. jeus:cache 내 바디 콘텐츠가 공유될 필요가 없다면 이 속성을 설정하지 않는 것이 바람직하다.
<key>
캐싱되는 엔트리를 식별하는 추가적인 값이다. 이 속성은 name 속성과 반드시 함께 사용되어야 한다. 그래서 엔트리를 식별할 때는 name + key 값이 유일한 식별자로 사용된다.
key 속성은 다음과 같은 scope를 지정하는 나열 형식으로 설정 가능하다.
<jeus:cache name=”. . . ” key=”[parameter|page|session|request|application].keyname” . . . >
<scope>
Cache되는 엔트리의 운용 범위를 나타낸다. 유효한 값으로는 ‘application’과 ‘session’이다. (기본값: ‘application’)
<timeout>
콘텐츠가 캐싱될 기간을 설정하며 Simple Date Format 형식으로 명시된다. (기본값: 1시간)
숫자와 시간을 나타내는 한 문자의 결합으로 표시한다. 기간을 명시하는 유효한 문자는 ‘s’(seconds), ‘m’(minutes), ‘h’(hours), ‘d’(days), ‘w’(weeks)이다.
예를 들어 10s는 10초, 10d는 10일, 그리고 4w는 4주는 나타낸다. 그리고 문자 없이 숫자만 설정하면 초(second) 단위로 인식한다. 0으로 설정하면 매번 바디 콘텐츠를 생성하여 refresh 효과를 낼수 있다. -1이 설정된다면 강제로 flush되지 않는 한 콘텐츠는 expire되지 않는다.
<size>
캐싱할 수 있는 객체의 최대 개수를 설정한다. 설정된 값을 초과했을 경우에는 LRU 알고리즘에 따라서 Cache에 저장된 객체가 제거된다. 이 값은 웹 애플리케이션 상황에 맞게 적절하게 설정되어야 한다. (기본값: Integer.MAX_VALUE)
<async>
하나의 Thread가 엔트리를 업데이트하고 있을 경우(아직 완료되지 않은 상태)에서 동일한 엔트리를 요구하는 다른 Thread에 대해서 blocking 여부를 설정한다.
설정값 | 설명 |
---|---|
true | Thread는 blocking되지 않고 이전 엔트리 즉, 업데이트되지 않은 엔트리를 가져간다. (기본값 : true) |
false | 엔트리가 업데이트될 때까지 기다린(blocking)후 최신 엔트리를 가져간다. |
<df>
overflow가 발생했을 경우 공간확보를 위해서 엔트리를 제거한다. 이때 제거되는 victim 개수를 설정된 "size 속성"에 대한 비율을 "df 속성"으로 설정할 수 있다.
유효한 값의 범위는 '0.0 <= factor <= 1.0' 사이의 실수값이다. (기본값: 0.25)
예를들어 factor 1.0는 메모리에 Cache되어 있는 모든 엔트리를 제거한다는 의미이며, factor 0.0은 overflow가 발생할 경우 추가 요청에 대해 더 이상 Cache하지 않는다는 의미이다.
<flush>
Cache 내 엔트리를 제거하기 위해서 사용한다. 특정 엔트리를 제거하기 위해서는 반드시 "name 속성"이나 "name 속성 + key 속성"을 지정해야 된다.
한 가지 주의할 것은 "name 속성"에 "key속성"을 이용해서 다수의 엔트리를 캐싱한 경우 "name 속성"만을 사용하여 "flush 속성"을 수행할 때 "key 속성"을 식별자로 하는 모든 엔트리가 제거된다. 그리고 이 속성은 다른 속성과는 다르게 바디 콘텐츠가 없는 닫힌 태그(/>)를 사용한다.
본 절에서는 jeus:cache 태그 속성의 사용법에 대하여 설명한다.
다음은 cache.jsp 페이지를 요청하는 경우 현재 날짜와 캐싱된 날짜를 비교해보는 간단한 예제이다.
[예 8.2] <<cache.jsp>>
<%@ taglib uri=”http://www.tmaxsoft.com/jeuscache” prefix=”jeus” %> <HTML> <BODY> Current time: <%= new Date() %><br> <jeus:cache timeout=”60s”> Cached time: <%= new Date() %> </jeus:cache> </BODY> </HTML>
jeus:cache 태그의 첫 번째 요청에서는 현재 날짜를 구하여 화면에 출력되고 JEUS WebCache에 저장된다. 다음 호출부터는 캐싱된 날짜가 출력될 것이다. 60초가 지난 다음에는 갱신된 날짜가 출력된다.
다음은 jsp:include를 사용하여 다른 페이지를 포함할 때 jeus:cache 태그를 사용하는 예이다.
[예 8.3] <<main.jsp>>
<HTML>
<BODY>
<jsp:include page="cache.jsp"/>
</BODY>
</HTML>
Main.jsp에 include된 cache.jsp 내의 jeus:cache 태그의 내용은 cache.jsp만 사용하는 첫 번째 예제와 동일한 수행 결과를 출력한다.
flush 기능은 캐싱된 엔트리를 강제로 제거하는 기능이다. 대상이 되는 엔트리를 지정하는 "name 속성" 또는 "name 속성 + key 속성"을 반드시 명시해야 한다.
예를 들어서 다음과 같이 "name 속성 + key 속성"을 사용해서 stock content를 캐싱했다고 가정하자.
<jeus:cache name=“stock” key=”parameter.company” scope=”application”> . . . stock content . . . </jeus:cache>
캐싱된 parameter.company에 해당하는 stock content를 제거하기 위해서는 다음과 같이 설정한다.
<jeus:cache name=“stock” key=”parameter.company” scope=”application” flush=”true”/>
위와 같이 flush 기능을 성공적으로 수행했다면 다음번 jeus:cache 태그 내의 stock content를 요청하는 경우에는 갱신된 stock content를 보게 된다. 만일 위 flush 속성을 다음과 같이 "key 속성" 없이 "name 속성"만을 사용한다면 parameter.company key 속성으로 저장된 모든 엔트리가 제거될 것이다.
<jeus:cache name=“stock” scope=”application” flush=”true”/>
물론 key 속성을 사용하지 않고 name 속성만으로 엔트리를 캐싱했다면 당연히 name 속성만 사용하면 된다.
모든 jeus:cache 태그를 호출할 때마다 바디 콘텐츠를 생성하게 하는 refresh기능을 제공한다.
이 기능은 jeus:cache 태그 속성을 사용하지 않는다. 대신 ‘_jeuscache_refresh’를 키로, true를 값으로 해서 원하는 scope에 설정할 수 있다.
애플리케이션과 session scope의 모든 엔트리를 refresh하기 위해서는 다음과 같이 각각 작성한다.
<% application.setAttribute(“_jeuscache_refresh”, “true”); %> <% session.setAttribute(“_jeuscache_refresh”, “true”); %>
이 기능은 jeus:cache 태그에 접근했을 때 각각의 scope에 '_jeuscahe_refresh' 값을 조사하여 true일 경우 그 바디 콘텐츠를 갱신한다. 그리고 더 이상 refresh 기능을 사용하지 않기를 원한다면 '_jeuscahe_refresh'을 false로 지정한다.
timeout 속성, flush 속성을 사용하면 하나 또는 일부분의 갱신된 엔트리를 볼 수 있는 반면, refresh기능을 사용하면 설정한 scope 내의 모든 바디 콘텐츠가 매번 갱신된다.
JEUS WebCache는 servlet filter를 사용하여 HTTP 응답 전체를 캐싱하는 방법을 제공한다.
페이지 내용이 동적으로 변하는 웹 페이지에는 적절하지 않으며 Static Content에 대한 HTTP 요청에 적합한 방법이다. 이미지 파일, PDF 등의 바이너리 콘텐츠를 요구하는 HTTP 요청이 여기에 해당 된다. 물론 일정시간 동안 웹 페이지의 내용이 변경되지 않거나 변경사항이 반영되지 않아도 되는 웹 페이지에도 이 방법을 사용할 수 있다.
주의할 것은 HTTP 응답의 상태가 200 OK(HttpServletResponse.SC_OK)인 경우에만 해당 HTTP 응답이 캐싱된다는 것이다. HTTP response를 JEUS WebCache에 저장할 때 사용되는 엔트리 키로 HTTP URI가 사용된다.
http://www.sample.com/filter/respcacheTest.jsp?key=value
위와 같이 key, value를 포함해서 전체 HTTP URI가 엔트리 키로 적용된다. 만일 key, value 값이 다양하게 변화한다면 각각의 URI에 해당하는 HTTP response가 캐싱된다.
이제부터 HTTP response Caching을 웹 애플리케이션에 어떻게 적용하는지 살펴보자.
웹 애플리케이션에서 HTTP response Caching을 사용하기 위해서는 다음과 같이 web.xml에 필터를 등록한다.
다음은 <url-pattern>이 '/filter/'인 모든 HTTP 요청에 대한 HTTP 응답을 10분 동안 캐싱하는 예제이다. 주의할 것은 <filter-class>로 반드시 jeus.servlet.cache.web.filter.CacheFilter 클래스를 사용해야 된다.
[예 8.4] 필터 설정 : <<web.xml>>
<web-app>
. . .
<filter>
<filter-name>CacheFilter</filter-name>
<filter-class>jeus.servlet.cache.web.filter.CacheFilter </filter-class>
<init-param>
<param-name>timeout</param-name>
<param-value>600</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<url-pattern>/filter/*</url-pattern>
</filter-mapping>
. . .
</web-app>
다음은 필터 클래스에 전달하는 초기화 파라미터에 대한 설명이다.
timeout
HTTP response를 캐싱하고 있을 시간을 설정한다. (기본값: 3600, 단위: 초(second))
JSP Caching에서 사용하는 timeout속성과 설정 방법이 동일하다. 단, HTTP response Caching에서는 flush 기능이 제공되지 않기 때문에 timeout을 ‘-1’로 설정하면 웹 애플리케이션이 Undeploy되지 않는한 expire되지 않기 때문에 주의한다.
lastModified
HTTP 응답으로 Last-Modified 헤더를 보낼지를 결정하기 위해서 사용된다. 이는 웹 컨테이너의 부하를 줄이는 목적으로 사용된다.
브라우저는 자신이 Cache하고 있는 콘텐츠가 마지막 요청 이후에 변경되었는지 웹 컨테이너에 요청할 수 있다. 그러면 웹 컨테이너는 HTTP 요청에서 If-Modified-Since 헤더 정보와 현재 엔트리의 최종 변경시간을 비교하여 엔트리에 변경사항이 없다는 304 상태코드(HttpServletResponse.SC_NOT_MODIFIED)를 브라우저로 전송한다.
다음은 유효한 값에 대한 설명이다.
설정값 | 설명 |
---|---|
on | 304 상태코드를 보낸다. filter chain 수행 중에 결정된다. |
off | 설정하면 HTTP 응답으로 304 상태코드를 보내지 않는다. |
initial | 304 상태코드를 보낸다. 최종 변경시간을 현재시간으로 한다. (기본값) |
expires
HTTP 응답으로 Expires 헤더 정보를 전송할지를 결정하기 위해서 사용된다. 브라우저에서 Cache 기능을 사용하고 있다면 Cache된 콘텐츠는 사용 기간이 만료될 때까지 유효하며 이후에 계속적인 HTTP 요청에 대해서는 브라우저 Cache에 저장된 콘텐츠를 사용할 것이다.
그런데 만일 JEUS WebCache의 엔트리가 업데이트되었다고 했을 때 새로운 엔트리는 브라우저 Cache에 저장된 콘텐츠와 다른 문제가 발생한다. 이런 경우에 웹 컨테이너에서 Expires 헤더 정보를 설정하여 브라우저 Cache에 저장된 콘텐츠를 기간만료시키고, JEUS WebCache의 업데이트된 엔트리를 사용하도록 해야한다.
다음은 유효한 값에 대한 설명이다.
설정값 | 설명 |
---|---|
on | Expires 헤더 정보를 전송한다. filter chain에서 값이 설정된다면 헤더 정보를 전송한다. |
off | Expires 헤더정보를 전송하지 않기 위해서 사용한다. |
time | Expires 헤더 정보를 전송한다. HTTP 응답의 last-modified값에 상기에 기술한 time 파라미터 값이 더해져서 Expires 헤더 정보로 설정되어서 클라이언트로 전송한다. |
여기에 scope, size, async, df
들을 설정할 수 있는데 이들은 “8.2. JSP Caching”에서 설명한 것과 동일한 의미를
가지고 있으므로 해당 부분을 참조한다.