2011년 6월 1일 수요일

iBATIS in Action (9/14)

1. 캐시를 통한 성능 향상

1.1. 간단한 iBATIS 캐싱 예제

iBATIS 견고하고 간단한 캐싱 매커니즘은 모두 설정을 통해 이루어지며 캐시를 직접적으로 관리해야 하는 부담을 덜어준다. 언제, 그리고 어떻게 iBATIS 캐싱을 사용하는지 알아보기 전에 간단히 흝어보자. 다음에서 간단한 캐시 설정과 이를 사용하는 매핑 구문 가지를 살펴보자.

id="getCategory" parameterClass="Category"

resultClass="Category" cacheModel="categoryCache">

FROM Category

WHERE categoryId=#categoryId#

여기서는 주요 컴포넌트 가지를 있다. 캐시 모델과 매핑 구문이다. 캐시 모델은 새로 나온 결과를 어떻게 저장하고 오래되서 퀴퀴한 냄새가 나는 데이터를 캐시에서 어떻게 없애버릴지를 결정한다. 매핑 구문에서 캐시를 사용하려면

INSERT INTO Category

(title,description,sequence)

VALUES

(#title#,#description#,#sequence#)

요소를 사용하려면 statement 속성에 캐시를 지우도록 지시하는 매핑 구문의 이름을 지정해야 한다. 해당 매핑 구문이 namespace 속성을 사용하는 SQL Map 안에 포함돼 있다면 statement 속성에 명명공간을 포함한 완전한 이름을 지정해야만 한다. Statement 속성이 동일한 SQL Map 파일 안에 있는 매핑 구문을 지정할 경우에도 완전한 명명공간 표기방식을 사용해야만 한다. 다른 SQL Map 파일에 존재하는 매핑 구문을 지정할 때는 statement 속성이 매핑 구문을 참조하기 전에 먼저 해당 SQL Map 파일에 적재되어 있는 상태여야만 한다.

요소는 캐시의 내용을 관리하는데 사용하는 다른 flush 요소다. 요소는 요소보다 약간 간단한 편인데 시간을 제외한 다른 설정에는 의존하지 않기 때문이다. 요소는 특정 시간 간격에 따라 반복적으로 캐시를 지운다. 간격은 설정이 적용되는 동안 캐시가 생성될 시작되고 애플리케이션이 종료될 때까지 계속된다. 다음에서 보다시피 ,, 혹은 밀리초를 지정할 있다.

l Hours (선택사항) : 캐시를 지우기까지 지나야 시간

l Minutes (선택사항) : 캐시를 지우기까지 지나야

l Seconds (선택사항) : 캐시를 지우기까지 지나야

l Milliseconds (선택사항) : 캐시를 지우기까지 지나야 밀리

잠재적인 혼란을 방지하기 위해 캐시를 지우는 특정 시각을 지정할 수는 없다. 순수하게 시간 간격에만 기초한다. 다음에서 요소를 사용하여 캐시된 객체의 수명을 12 시간으로 제한한 예를 보여준다.

id="getCategory" parameterClass="Category"

resultClass="Category" cacheModel="categoryCache">


FROM Category

WHERE parentCategoryId=#categoryId#

사용할 주의할 사항은 요소에는 오직 개의 속성만을 허용한다는 점이다. 그러므로 12시간 10 10 5밀리초 마다 캐시를 지우고 싶다면 이를 밀리초로 계산해서 지정해야만 한다.

9.4.2 캐시 모델 구현체의 프로퍼티 설정하기

캐시 모델은 프레임워크에 플러그인 형태로 작동하는 컴포넌트이기 때문에 컴포넌트에 임의의 설정 값을 제공할 있는 방법이 필요하다. 요소가 바로 역할을 한다. 다음은 요소의 속성을 보여준다.

l Name (필수사항) : 설정할 프로퍼티의 이름

l Value (필수사항) : 설정할 프로퍼티의

Name value 속성 모두가 필수 사항이고 모두 캐시 모델 컴포넌트를 초기화할 Properties 객체로 만들어져 전달된다.

일반적으로 캐시 설정은 개발자가 사용하고자 하는 캐시 타입에 따라 달라지게 된다. 그러므로 어떤 타입의 캐시 모델이 있는지 그리고 각각의 캐시 모델 타입에 따른 옵션은 무엇인지 이해하고 있어야 한다.

1.5. 캐시 모델 타입

9.3.1 절에서 언급한 바와 같이 iBATIS 함께 제공되어 애플리케이션에서 사용할 있는 캐시 모델은 가지가 있다.

* MEMORY

* LRU

* FIFO

* OSCACHE

다음 절에서 캐시 모델 타입을 알아보자.

9.5.1 MEMORY

MEMORY 캐시는 객체 참조를 기반으로 캐시이다. (java.lang.ref javadoc 참조하라). 캐시 내의 객체는 참조 타입을 갖게 된다. 참조 타입은 가비지 컬렉터에게 객체를 어떻게 다룰지에 대한 힌트를 제공해준다. Java.lang.ref 패키지처럼 MEMORY 캐시도 WEAK SOFT 참조를 제공해준다. 추가로 참조 타입을 WEAK 혹은 SOFT 라고 지정하면 가비지 컬렉터는 메모리 용량의 제한 그리고 혹은 캐시된 객체에 대한 현재 접근 상태에 따라서 무엇이 캐시에 남아있고 무엇을 내보낼지를 결정한다. STRONG 참조 타입을 사용하면 캐시는 캐시 비우기 시간 간격(flushInterval) 때까지는 무엇을 호출하든 상관없이 객체를 계속 보관할 것을 보장해준다.

MEMORY 캐시 모델은 객체에 접근하는 방식보다는 메모리 관리에 더욱 중점을 애플리케이션에 적합하다. STRONG, SOFT 그리고 WEAK 참조 타입들이 어떤 객체를 다른 객체보다 오래 가지고 있을지 결정해 주기 때문이다. 표는 각각의 참조 타입이 어떻게 기능을 수행하는지에 대해 간단하게 보여주며, 타입에 따라 객체를 메모리에 얼마동안 캐싱할지를 어떻게 결정하는지 보여준다.

WEAK

WEAK 참조 타입은 캐시된 객체를 빨리 비워버린다. 참조 타입은 객체가 가비지 컬렉터에 의해 수거되는 것을 막지 않고 놔둔다. 가비지 컬렉터는 캐시된 객체를 처음 보자마자 삭제해 버릴 것이다. 참조 타입은 단지 삭제되기 전의 객체에 접근하는 방업을 제공할 뿐이다. 이는 디폴트로 지정되는 참조 타입이다. 방식은 일관성있게 객체에 접근하는 캐시를 사용할 작동한다. 방식은 캐시를 비우는 비율이 빠른 편이기 때문에 메모리 제한을 넘기지 못하도록 보장해준다. 방식을 사용하면 데이터베이스 히트 확률이 높아진다.

SOFT

SOFT 참조 타입도 중요한 메모리 용량 제한에 다달았을 객체를 삭제해도 되는 경우에 좋다. SOFT 참조 타입은 메모리 용량이 허락하는 캐시된 객체를 계속 보관한다. 가비지 컬렉터는 많은 메모리가 필요하다고 판단되기 전까지는 객체들을 수거하지 않는다. SOFT 참조는 또한 메모리의 용량을 넘지 않을 것을 보장해 주고 WEAK 참조 타입보다는 데이터베이스 히트 횟수가 적은 편이다.

STRONG

STRONG 참조 타입은 메모리의 용량 한계가 얼마든지 간에 관계 없이 캐시된 객체를 계속 보유하고 있다. STRONG 타입으로 저장된 객체는 지정된 비우기 시간 간격이 되기 전까지는 캐시에서 삭제되지 않는다. STRONG 타입의 캐시는 정적이고 작고 정기적으로 사용할 객체를 저장할 사용하도록 한다. 참조 타입은 데이터베이스 히트를 줄여서 성능을 향상시켜준다. 하지만 캐시의 용량이 지나치게 커져서 메모리 부족 오류를 발생시킬 있는 위험성을 안고 있다.

데이터베이스 히트 : database hit, 캐시된 객체가 없기 때문에 다시 데이터베이스에 접속해서 쿼리를 하는 행위

MEMORY 캐시 타입은 오직 reference-type 프로퍼티 하나만을 가지고 있다. 프로퍼티에 참조 타입을 지정한다.

다음은 WEAK 참조 타입을 사용하여 캐시된 데이터를 저장하고 최대 24시간 마다 그리고 insert, update, delete 매핑 구문이 실행될 마다 캐시를 지우는 간단한 MEMORY 캐시 모델이다.

MEMORY 캐시 타입은 간단하지만 애플리케이션에서 캐시를 다루기에 충분히 효과적인 방법이다.

9.5.2 LRU

LRU 캐시 모델 타입은 최근에 가장 오랫동안 사용되지 않은 (Least Recently Used) 것을 제거하는 방식으로 캐시를 관리한다. 캐시의 내부에서는 최근에 가장 오랫동안 접근하지 않은 객체를 결정하고 용량이 초과하게 되면 해당 객체를 제거한다. 캐시의 객체를 제거하는 것은 오직 캐시의 용량이 제한을 넘겼을 번만 발생한다. 용량 제한은 캐시가 보유할 있는 객체의 개수로 정의한다. 이런 형태의 캐시에는 대용량 메모리를 차지하는 객체를 넣으면 메모리 부족 오류가 발생할 있으니 주의해야 한다.

LRU 캐시는 특정 객체에 상당히 자주 접근하는 캐시를 관리할 매우 적합한 형태이다. 보통 페이지로 나뉜 결과이거나 검색어로 검색한 결과에 사용되는 객체를 캐싱하는 애플리케이션에서 이러한 캐싱 방식을 사용한다.

요소를 사용하여 LRU 캐시 타입에 지정할 있는 프로퍼티는 size 하나뿐이다. 값은 캐시에 저장될 있는 객체의 최대 개수를 지정한다.

다음은 가장 최근의 200 개의 캐시된 객체를 메모리에 저장하고 최대 24시간마다 그리고 insert, update, delete 매핑 구문이 실행될 때마다 캐시를 지우는 간단한 LRU 캐시 모델이다.

LRU 캐시는 특정 기간동안 서로 다른 데이터의 일부를 사용하는 애플리케이션에서 유용하게 사용할 있다.

9.5.3 FIFO

FIFO 캐시 모델은 먼저 들어온 것을 먼저 내보내는 전략을 사용한다. FIFO 생존 시간 기반의 전략으로 캐시된 객체중 가장 오래된 것을 먼저 삭제한다. 캐시된 객체의 삭제는 캐시 용량 제한을 넘었을 오직 번만 수행한다. 용량 제한은 캐시가 보유할 있는 객체의 개수로 정의한다. LRU 타입과 마친가지로 이런 타입의 캐시에는 대용량 메모리를 차지하는 객체를 넣으면 메모리 부족 오류가 발생할 있으니 주의해야 한다.

FIFO 생존 시간에 기반을 두고 있기 때문에 초기에 캐시에 저장되는 순간에 많이 사용되는 객체를 캐싱할 좋은 효과를 보여준다. 시간이 지나면 객체는 사용되겠지만 여전히 접근할 있다. 시간에 기반한 리포팅 애플리케이션에서 이러한 캐싱이 유용함을 있다. 주식 가격을 리포팅한다면 대부분의 요청은 요청 순간에 가장 유효하고, 시간이 지남에 따라 중요도가 떨어지게 된다.

요소를 사용할 FIFO 캐시 타입을 위해 지정할 있는 오직 하나의 프로퍼티는 size 이다. 값은 캐시에 저장될 있는 객체의 최대 개수를 지정한다.

다음은 가장 최근의 200개의 캐시된 객체를 저장하고 최대 24시간마다, 그리고 insert, update, delete 매핑 구문이 실행될 때마다 캐시를 비우는 간단한 FIFO 캐시 모델이다.

FIFO 캐시는 예를 들면 장바구니처럼 특정 기간에 특정한 데이터 형태를 순환적으로 사용하는 애플리케이션에서 유용하게 사용할 있다.

9.5.4 OSCACHE

OSCACHE 캐시 모델은 Open Symphony(http://www.opensymphony.com/oscache/) OSCache 2.0 제품을 사용한다. OSCache iBATIS 다른 캐싱 모델로 제공하는 많은 타입의 캐싱 전략을 수행할 있는 견고한 캐싱 프레임워크이다. OSCACHE 모델을 사용할 때는 OSCache jar 파일들이 필요하다. 설정을 사용할 때는 OSCache jar 파일들을 프로젝트 디렉터리에 포함시켜야 한다. 캐시는 표준 OSCache 설치와 동일한 방법으로 설정하면 된다. 말은 OSCache 읽을 있도록 oscache.properties 파일을 클래스패스의 최상위에 저장해둬야 한다는 뜻이다. OSCache 설치하고 설정하는 방법에 대해 알고 싶다면, http://www.opensymphony.com/oscache/documentation.action 에서 작성된 문서를 보면 된다. 다음은 OSCACHE 캐시 모델의 예제를 보여준다.

9.5.5 스스로 만든 캐시 모델

앞에서 캐시 모델은 프레임워크에 플러그인되는 형태의 컴포넌트라고 언급한 적이 있다. 자기 자신의 캐시모델을 만들 있는 방법을 알고 싶거나 혹은 단지 호기심만 가지고 있을 수도 있을 것이다.

오직 가지만 기억하면 된다. 하나는 iBATIS 제공해 주는 가지 캐시 모델은 단순히 com.ibatis.sqlmap.engine.cache.CacheController 인터페이스를 구현한 것이라는 점이다. 하나는 디폴트 캐시 모델들의 이름은 단순히 구현체들의 완전한 클래스 이름에 대한 별칭이라는 점이다.계속 나아가면서 이제는 당신 자신의 캐시 모델도 있으니 이걸 어떻게 작동시키는지 가지 방법을 알아보자.

1.6. 캐싱 전략 수립하기

캐싱 전략을 수립할 때는 먼저 요구사항을 명확하게 해야 한다. iBATIS 데이터 접근 계층을 위해서 앞에서 설명한 캐싱 전략들을 제공하고 있다. iBATIS 제공하는 캐싱 전략들은 여러분이 이루고자 하는 캐싱 전략 전반에 걸쳐서 원활하게 작동하도록 있으며, 또한 작동할 것이다. 하지만 애플리케이션의 전반에 걸쳐 사용할 있는 캐싱 전략에 이것들만 있지는 않을 것이다. 전반적인 캐싱 전략을 탐구하고 있다면 책의 범위를 벗어나는 고려사항과 논의 사항들이 매우 많다. 어쨌든 iBATIS 캐싱 기능이 여러분의 캐싱 전략 전반에서 어떤 부분을 담당할지 결정하는 것이 중요하다.

데이터 접근 계층에서 데이터를 캐싱할 해당 애플리케이션 전용 데이터베이스나 혹은 다른 애플리케이션과 공유하는 데이터베이스를 사용하게 것이다. 전용 데이터베이스를 사용할 때는 개발중인 애플리케이션을 통해서만 데이터베이스에 접근하게 된다. 공유 데이터베이스의 경우에는 여러 애플리케이션들이 데이터베이스에 접근할 것이고 데이터를 수정하는 것이 가능해진다.

만약 여러가지 애플리케이션들이 접근하고 데이터를 수정하는 데이터베이스에 애플리케이션이 접속한다면, 캐시를 많이 사용하지 않는 것이 좋다. 다른 애플리케이션이 데이터베이스에 변경을 가해서 원본 데이터가 변경된 오래 되었다면 사용하는 것도 효과적인 방법이 되지 못한다. 그래도 여전히 읽기/쓰기 가능한 데이터에 영향을 주지 않는 정적이고 읽기 전용인 데이터를 캐싱하여 효과를 수있다. 예를 들면 시간에 따라 변경되지 않는 리포팅 데이터, 장바구니 데이터 혹은 정적인 드롭 다운 목록 데이터 등이 그렇다.

한편 단일 애플리케이션에 의해서만 접근 가능한 데이터베이스를 캐싱한다면 iBATIS 캐싱 기능을 적극적으로 도입할 있다. 전용 애플리케이션에서는 훨씬 효율적으로 사용할 있다. 개발자가 특정 매핑 구문을 실행하면 캐시된 데이터를 이상 유효하지 않게 만든다는 것을 알고 있으므로 캐시 지우기 전략을 수립할 있다.

만약에 특정 항목이 캐시에게 해제될 , 세부적으로 제어하고 싶거나 혹은 클러스터링된 캐시를 원한다고 생각해보자. iBATIS 자체는 그런 방식의 캐시를 제공하지 않는다. 하지만 OSCACHE 캐시 타입(9.5.4) 조합해서 이런 형태의 견고한 기능을 제공할 있다. 어쨌든 이전에 말했던 것과 같은 규칙이 여기서도 적용된다. 사용할 데이터베이스에 접근할 있는 애플리케이션이 하나로 제한되어 있을 때는 적극적으로 케시를 사용할 있다. 하지만 전용 데이터베이스가 아닌 경우에는 주의해야 한다.

다음에 나올 몇몇 절에서는 캐시 사용에 관련된 경우에 대한 스터디와 어떻게 캐시 구현을 시작할지 보여주는 코드 조각들을 살펴볼 것이다.

9.6.1 읽기전용, 장기간 유지 데이터 캐싱

읽기 전용의 장기간 유지되는 데이터는 자주 캐시의 대상이 된다. 이것은 자주 변경되지 않기 때문에 캐시하기가 쉽다. 이런 종류의 데이터를 캐시에 집어넣고 캐시를 지울 시간이 되거나 혹은 캐시를 비우도록 지정된 매핑 구문이 실행될 때까지 잊어버리고 있어도 된다. 장바구니 애플리케이션을 다시 보면서 읽기 전용 캐시의 설정 과정을 살펴보자.

8장에서 장바구니 카테고리를 살펴보았기에 여기서 다시 사용할 것이다. 장바구니 방문자가 카테고리를 선택하면 관련된 하위 카테고리를 화면에 함께 보여준다. 카테고리들은 자주 사용되며 값이 그다지 변경되지 않는다. 따라서 이들이 장기간 유지할 있는 읽기 전용 캐시의 주요 대상이 된다.

어떻게 카테고리 캐싱을 설정할지 생각할 때는, 어떻게 결과를 조회할지 그리고 데이터에 접근하는 패턴에 가장 적합한 캐싱 전략은 어떤 것인지를 고려해야 한다. 하위 카테고리 리스트를 캐싱하는 경우에 사용자들은 대개 관련된 상위 카테고리를 기반으로 하위 카테고리의 리스트를 조회해 혼다. 이것을 SQL 용어로 표현하면 WHERE 조건이 parentCategoryID 값이 전달받는 파라미터 값과 동일한지 여부에 기초를 두고 있다는 의미이다. 다른 고려사항으로 얼마나 자주 캐시를 지워야 하고 어떠한 캐싱 전략을 사용해야 하는가가 있다. 사용자들은 종종 특정 카테고리를 다른 카테고리보다 자주 사용하는 경우가 있다. 그래서 LRU 전략으로 사용하고자 하는 항목들을 측정한다. 접근 방법을 사용하면 최근에 접근한 항목들을 계속 캐싱하고 캐시에 오랫동안 있었던 데이터를 삭제한다.

에서는 캐시 모델을 설정하는 것으로 시작하였다. Type 속성을 LRU 지정하였다. Id 속성의 값은 캐시를 사용할 매핑 구문을 설정할 캐시 모델의 유일한 식별자 역할을 한다. 요소를 사용하여 캐시가 절대로 24시간 이상 지속되지 않도록 보장하였다. categoryCache 식별되는 캐시에 저장돼 있는 모든 데이터를 삭제할 것이다. 사용해서 insert, update, delete 지정된 매핑 구문을 호출하면 id categoryCache 캐시에 저장된 모든 결과를 지우도록 지정하였다. 마지막으로 요소와 size 프로퍼티를 이용해서 캐시에 저장할수 있는 항목의 개수를 제한하도록 설정하였다. 캐시에 50 이상의 결과가 저장되면 최근에 이용되지 않는 항목을 삭제하기 시작한다.

다음에서 getChildCategories 매핑 구문은 cacheModel 속성에서 관련 캐시로 categoryCache 라는 id 지정해서 categoryCache 사용한다. 사용자가 장바구니 어플리케이션의 카테고리를 살펴볼 getChildCategories 매핑 구문이 호출된다. 이때 하위 카테고리 결과를 캐시한다. 캐시의 크기가 50 도달하게 되면 캐시에서 오래된 결과를 삭제하기 시작한다. 이로인해 끊임없이 접근한 하위 카테고리 목록이 캐시에 오래 남아있게 되고 사용자들에게 좋은 성능을 제공하게 된다. 만약 관리자가 카테고리에 insert, update, delete 실행하면 캐시가 자동으로 지워지고 캐시된 결과를 다시 처음부터 구축하기 시작한다.

이러한 지우기와 도태시키기(오랫동안 접근하지 않은 캐시를 지우는 작업) 조합을 통해 불필요한 데이터베이스 접근 이라는 짐을 덜어내면서 하위 카테고리를 유효한 가장 최근의 것으로 유지한다.

LRU 캐시의 삭제 처리는 size 프로퍼티 값을 증가시키거나 감소시켜 많거나 적은 항목을 캐시하도록 세부적으로 조절할 있다. LRU 목표가 지속적으로 사용되는 항목들만 캐싱하는 것임을 기억하라. 캐시의 크기 (size 프로퍼티) 너무 크게 하지 말라. 그렇게 하면 LRU 캐시가 사실상 STRONG 메모리 캐시가 되어 버린다. 이는 LRU 캐시의 전체적인 효과를 무용지물로 만들어버릴 것이다.

이제 장기간 유지되는 읽기 전용 데이터를 캐싱하는 효과적인 방법을 알아보았으니, “캐시를 하느냐 마느냐라는 고민에 자주 처하게 되는 다른 상황을 생각해보자. 바로 읽기와 쓰기가 모두 가능한 데이터에 대한 캐싱이다.

9.6.2 읽기/쓰기 가능한 데이터 캐싱

변경되는 특징이 있는 객체를 캐시해야 된다고 생각해보자. 그렇게 하려면 주의해야만 한다. 트랜잭션이 많은 환경이라면 캐시가 과도하게 변경되고 사실상 쓸모 없어진다는 것을 알게될 것이다. 트랜잭션이 많은 데이터를 캐시하려고 하면 캐시를 자주 지워야만 하기 때문이다. 캐시를 너무 자주 지우게 되면 이중적인 부담을 떠안게 된다. 첫째로 애플리케이션이 데이터베이스에 요청을 보낼때마다 항상 지속적으로 캐시를 검사하고 지우고 다시 캐시에 데이터를 채우는 상태가 된다. 따라서 캐시를 계속적으로 지운다면 새로운 결과를 가져오기 위해 계속해서 데이터베이스에 접근해야 함을 의미한다. 애플리케이션에서 캐시를 사용하기보다는 데이터베이스에서 인덱싱과 테이블 피닝(table pinning, 테이블 내용을 메모리에 저장하기) 같은 기술을 사용하는 것이 성능 향상에 도움이 되리라는 것을 알게 것이다.

비록 변경될 가능성이 있는 데이터를 캐싱할 주의할 필요가 있긴 하지만 데이터의 변경이 덜한 상태에 있을 때에도 주의할 필요가 있다. JGameStore 애플리케이션에서 제품을 캐싱하는 좋은 예를 있다. 많은 상점 애플리케이션들은 관리자가 새로운 제품을 입력하고 이미 존재하는 제품의 정보를 수정하고 어떤 것은 할인 품목이라고 표시하고 외의 유사한 작업을 있는 기능이 필요하다. 이런 종류의 작업들은 적은 양의 변경을 일으킨다. 이런 경우 높은 트랜잭션을 요구하는 환경이 아니기 때문에 애플리케이션의 전반적인 성능을 향상시키는데 캐싱이 역할을 있다. 캐시를 구축할 시간이 필요하고 잠시 사용자에게 성능 향상을 제공해줄 있는 , 개발자는 앞서 설명한 싱크홀을 피하게 것이다.

싱크홀 : sinkhole, 땅속 지반의 밀도가 높지 않은 곳의 땅꺼짐 현상을 뜻한다. 여기서는 트랜잭션이 많은 환경에서의 캐시 사용은 오히려 성능상에 치명적인 역효과를 낳는 현상을 의미한다.

캐싱하고자 하는 데이터의 특성을 판단할 때는 몇몇 사항을 고려해야 한다.

l 의미를 가지는 제품의

l 제품 데이터의 변경 특성

l 가장 자주 접근하는 제품은 소비자의 습관에 따라서 하루 종일 변경될 것이다.

우리의 예제에서는 제한적인 캐싱 방법인 WEAK 메모리 케시를 사용하기로 결정하였다. 캐싱해야 결과의 개수를 결정하려면 특정한 수준의 예측 가능성을 필요로 하는 LRU와는 달리 WEAK 메모리 캐시는 미리 결정된 인위적인 제약에 도달하기 전에 개발자에게 어떤 항목을 남겨두고, 어떤 항목을 버릴지를 결정할 있는 권한을 준다. 캐시에 데이터를 저장하기 위해 java.lang.ref.Reference 구현체를 사용하기 때문에 내부적인 분석에 기초하여 캐시된 데이터를 삭제하거나 계속 유지할 있다. MEMORY 캐시에 WEAK 참조 타입을 사용하면 결과 데이터를 WeakReference (java.lang.ref.WeakReference) 포장해서 캐시에 저장한다. 그러고 나서 가비지 컬렉터가 포장한 결과를 스스로 판단하여 처리하게 된다.

이제는 요소 설정으로 넘어가 보자. 이미 알다시피 cacheModel 타입 속성은 MEMORY 지정된다. 읽기/쓰기가 가능한 환경임에도 요소에 readOnly 속성을 true 설정했다는 것에 주의하라. 더욱이 serialize false 설정하여 깊은 복사를 해야하는 부담을 제거하였다. 말은 캐시에서 가져오는 객체들이 변경될수도 있음을 의미한다. 이런 방식은 여러가지 이유에서 안전한 접근법이라 있다. 첫째로 오직 장바구니를 관리하는 사람만이 제품 객체를 변경할 있을 것이다. 실제로 쇼핑을 하고 있는 사용자는 어떤 방식으로도 제품 객체를 절대로 바꿀 없다. 둘째로 제품 정보가 수정될 때마다 캐시를 지우게 된다. 마지막으로 reference-type 속성을 WEAK 지정함으로써 제품을 오랜기간 동안 캐시되지 않게 한다. 가비지 컬렉터의 재량에 따라 삭제하기 때문이다. 다음에서 우리의 캐시 모델 설정 예제를 있다.

readOnly="true" serialize="false">

이제 정의를 쿼리 매핑 구문에서 사용할 있게 되었다. cacheModel 속성을 지정해서 getProductById 쿼리 매핑 구문이 productCache 캐시 모델을 사용하도록 하였다. 애플리케이션에서 getProductById 쿼리를 실행할 때마다 productCache 캐시 모델에 지정된 대로 캐시된 제품 객체를 가져오게 것이다. 다음은 위에서 정의한 사용하는

parameterClass="Product" cacheModel="productCache">

* FROM Product WHERE productId=#productId#

9.6.3 낡게 되는(aging) 정적 데이터 캐싱하기

마지막 케이스 스터디로 다소 일반적이지는 않은 상황을 다룰 것이다. 하지만 재미있는 테스트 케이스이다. 작고 정적인 데이터가 시간이 지남에 따라 사용률이 떨어지는 경우를 다룰 필요가 생기는 상황이다. 보통 이런 캐싱 타입은 시간에 기초하는 분석과 관련되어 있다. 시간에 기초한 분석의 예를 들자면 콜센터 애플리케이션의 성능 통계나 지난 시간//월의 통계를 제공하는 증권 시세 표시기 같은 것이 있다. 이런 데이터들의 특징을 요약하자면 시간이 지남에 따라 사용하게 된다는 것이라고 말할 있다. 많은 양의 데이터는 아니지만 데이터 생성시점에서는 매우 빈번하게 사용되고 시간이 지남에 따라 차츰 사용하게 된다. 데이터는 변경되지 않는데, 달리 말하자면 정적 데이터이다.

우리의 JGameStore 애플리케이션에는 이런 타입의 요구사항은 없다. 하지만 장바구니 예제를 계속 사용할 것이다. 고객들이 시간다마 구입하는 최다 판매 품목 5개에 대한 통계를 수집할 필요가 있다고 가정해보자. 그러고 나서 데이터를 장바구니 홈페이지에 올려서 쇼핑객들에게 시간별로 인기가 높은 상품이 무엇인지 알려줄 있다. 또한 사용자가 이전 시간을 선택하는 드롭 다운 목록을 제공해서 예전 인기 구매 상품을 있도록 하자. 일단 제품 구입이 이뤄질 때는 값이 변하지 않는다. 다섯 개의 최신 3D 블록버스터를 최근 1시간 내에 구입했다면 앞으로 인기 상품이 것이다. iBATIS 캐싱을 사용하여 어떻게 이러한 간단한 요구사항을 만족시키면서 성능을 향상시킬 있는지 살펴보자.

시간이 지남에 따라 데이터를 사용하게 된다는 사실과 점점 낡게 되는 특성 때문에 FIFO 캐시를 사용하는 것이 가장 알맞게 느껴진다. 앞에서 FIFO 생존 시간에 기반한 캐시라고 설명 하였다. 캐시에 들어가는 어떤 데이터라도 캐시의 크기 제한을 넘어서면 오래된 것이 나가 된다. 그러므로 시간이 지남에 따라 그리고 캐시에 항목들이 추가 됨에 따라 가장 최근 것이 아닌 제품 리스트는 점점 사용하게 된다. 매시간 새로운 제품 리스트들이 캐시됨에 따라 항목들은 결국에는 삭제되게 것이다. FIFO 캐시의 크기를 24 설정하였다면 캐시 항목은 가장 최근의 24시간 동안의 의미있는 항목들만 유지하고 있을 것이며, 이전 것은 무엇이든 삭제하게 된다. 다음으로 FIFO 캐시로 다른 요구사항들을 충족시키는 방법을 알아보자.

제품 구매 목록이 시간이 지나도 동일하게 남아 있기 때문에 정적인 데이터로 간주할 있다. FIFO 캐시에 정적인 데이터를 저장하는 것도 작동한다. 정적인 데이터는 결국에는 시간이 지나면서 퇴출되고 데이터의 자연스러운 생존 시간을 억지로 바꿔가면서 지속적으로 데이터를 지워줄 필요도 없다. 캐시를 지워야 유일한 경우는 제품에 수정이 가해질 뿐이다. 제품 수정은 어쩌다 발생하기 때문에 단순히 필요한 대로 처리를 하고 해당 항목을 FIFO 캐시에 집어넣으면 된다.

시간마다 저장될 다섯개의 제품은 메모리를 적게 사용해야 한다. FIFO 오직 지정된 크기를 초과해야 캐시를 지우기 때문에 대용량 메모리를 사용하는 객체를 캐시에 저장하는 것은 좋지 못하다. FIFO 캐시는 메모리 제약을 초과해도 캐시를 비우지 않을 것이고 out-of-memory (메모리 초과) 예외를 발생시킬 것이다. 제품 리스트 예제의 경우는 확실히 안전하다.

캐싱에서는 높은 비율의 접근과 낮은 비율의 접근은 게임의 부분이다. 이상 접근할 있다는 사실에서 성능 향상이 확실히 있는 , 개발자가 어떤 경험을 하든 상관없다. 세세하게 캐시 크기를 조절하는 것도 고려할 필요가 있다. 현재 인기 구매 품목과 과거 인기 구매 품목에 접근하는 사람이 얼마나 많으냐에 따라서 캐시의 크기를 적절하게 설정할 있다.

이제 요구 사항목록을 살펴보았고 FIFO 캐시가 요구 사항에 적합한지도 알았으니 캐시 모델을 설정하는 단계로 나아가 보자. 사용할 캐시 모델은 간단하다. 다음에서 있듯이 우리는 이미 구입한 제품들의 제품 정보를 올바르게 보여주기를 원한다. 먼저 적당한 요소를 지정해서 제품 데이터가 변경될 hotProductsCache 지우도록 해야 한다. 개발자는 단지 Product.update 혹은 Product.delete 명시하여 캐시를 지우도록 하면 된다. 여기서는 insert 포함시키지 않았는데 이유는 존재하지 않는 제품은 고려하지 않기 때문이다. 제품이 추가되고 인기구매 품목이 되면 이를 hotProductCache 추가하는 것은 아무 문제될 것이 없다. 그리고 오직 제품이 수정될 때에만 캐시 비우기를 걱정하면 된다. 이상 판매하지 않는 제품을 계속해서 진열하고 싶지는 않으니까 말이다(예를 들면 삭제된 제품). 또한 가격이 수정되었는데 예전 가격을 붙여서 제품을 보여주는 것도 원치 않는다.

id="getPopularProductsByPurchaseDate"

parameterClass="Product"

resultClass="Product" cacheModel="hotProductsCache">

count(productId) countNum, productId

FROM productPurchase

WHERE

purchaseDate

BETWEEN

#startDate#

AND

#endDate#

GROUP BY productId

ORDER BY countNum DESC

LIMIT 5

캐시를 지우는 것에 관한 설정은 미뤄두고 FIFO 캐시의 size 프로퍼티를 설정하는 것으로 넘어가 보자. Size 프로퍼티를 12 설정하면 캐시에 최대 12개의 결과만을 저장할 있게 된다. 일단 12개의 결과가 캐시에 저장되면 가장 오래된 항목이 지워지고 가장 최근 항목이 캐시의 시작 부분에 추가되게 된다. 우리가 사용하는 SQL 매시간 혹은 이상의 시간마다 신규 결과를 캐시에 저장할 것이기 때문에 캐시는 데이터를 적절한 시간동안만 보유하고 있을 것이다. 가장 최신의 쿼리 결과는 항상 캐시의 부분 저장되고 오래된 결과는 삭제될 것이다.

댓글 없음:

댓글 쓰기