replicatePuts=false
, replicateUpdatesViaCopy=false
를 통해 네트워크 부하를 낮춘다. 이는 또한 여러 서버의 순차 배포시에 Class 버전이 서로 안 맞는 상황에서 직렬화 오류나는 것을 방어해준다. 비동기로 한다.org.hibernate.cache.internal.StandardQueryCache
는 완전 local 캐시로만 적용한다.org.hibernate.cache.spi.UpdateTimestampsCache
는 replicatePuts=true
, replicateUpdatesViaCopy=true
및 비동기방식을 통해 모든 서버가 일관성있고 동일한 값을 유지하게 한다. 또한 서버가 뜰때 다른 서버의 UpdateTimestampsCache
들을 복제해 와서 사용해야 한다.동시성 전략 4가지가 있는데 hibernate 문서 설명이 부족해서 로그를 찍어보며 확인해봄.
기본적으로 evict
와 evictAll
은 기본 흐름상에서 Hibernate에 의해 직접 호출되지는 않는다. 다른 메소드들에 의해 필요해 의해 호출된다. 이 두 메소드는 javax.persistence.Cache의 evict
, evictAll
메소드가 실행될 때 호출된다.
테스트시에 쿼리 삭제, 수정 등을 하고서 flush후에 rollback 하면 어떻게 되는지 테스트
읽기 전용. 우편 번호처럼 잘 안 변하는 것들에 적합할 듯. Read Only의 의미가 수정만 못하게 하는 것인지 수정과 삭제를 둘 다 못하게 하는 것인지에 대해서는 확실치 않다. 그래서 삭제는 가능한 것으로 가정하고 스토리를 짜면 아래 방식으로 했을 때 잘 작동한다.
삭제도 못하게 하려 한다면 lockItem
에서도 예외를 던져야 하는 것이 맞아 보인다.
@org.hibernate.annotations.Immutable
로 지정된 엔티티와 컬렉션에 사용하는 것이 좋을 듯 하다.
get
: 캐시에서 읽어서 있으면 반환 한다.putFromLoad
: minimalPutOverride == true일 경우에는 이미 캐시에 값이 존재하면 현재 것을 버린다. 아니면 항상 값을 캐시에 쓴다. 캐시에 썼으면 return true;
아니면 return false;
. putFromLoad
는 데이터는 가져왔으나 캐시가 없었을 때 호출되는 것인데, 그 사이에 다른 쓰레드에서 캐시에 데이터를 썼다면 굳이 다시 쓸 필요는 없다.insert/afterInsert
: 객체를 persist해도 호출하지 않음. 즉, persist시 캐싱 불가. return false;
lockItem
: 객체 수정 전에 호출, READ_ONLY이므로 수정이 말이 안되니 return null;
. 여기서 예외를 던지면 삭제 기능이 작동할 수 없다.update
: 객체 수정 쿼리 직후 호출. READ_ONLY인 상황에서 객체 수정은 오류이므로 UnsupportedOperationException
을 던진다.afterUpdate
: update
에서 예외가 발생하지 않으면 호출된다. 그러나 위에서 이미 예외를 발생시켰으므로 원칙적으로는 호출돼서는 안된다. 하지만 아무튼 UnsupportedOperationException
을 던지는 것이 권고인 듯.unlockItem
: update
에서 예외를 발생 시켰으면 호출된다. 따라서 이것은 항상 호출돼야 한다. 여기서 캐시를 evict
시킨다.lockItem
: 객체 삭제 전에 호출된다. 아무것도 하지 않고 return null;
remove
: 삭제 쿼리 후 실행된다. 여기서 evict
를 호출하여 캐시를 삭제한다.unlockItem
: remove
후에 호출된다. 아무것도 하지 않는다.lockRegion
: 쿼리 실행 전에 호출된다. 아무것도 하지 않고 retur null;
removeAll
: 쿼리 실행 전에 호출된다. 아무것도 하지 않는다. 실제 쿼리가 날라가기 전이므로 이 시기에 캐시를 삭제하면 쿼리 결과가 Rollback시에 불필요한 부하만 증가할 것으로 보인다.unlockRegion
: 쿼리 실행 후에 호출된다. 여기서 evictAll
을 호출한다. evictAll
에서 실질적인 eviction을 수행한다.엄격하지 않은 읽기/쓰기. 객체 동시 수정 등에 대한 고려를 하지 않고 캐싱을 한다. 이 방식은 하나의 객체가 동시에 수정될 가능성이 거의 없을 때 사용한다.
동시 수정이 적은 데이터에 대한 분산 캐시에서는 이것이 적합해 보인다.
get
: 캐시에서 읽어서 있으면 반환 한다.putFromLoad
: minimalPutOverride == true일 경우에는 이미 캐시에 값이 존재하면 현재 것을 버린다. 아니면 항상 값을 캐시에 쓴다. 캐시에 썼으면 return true;
아니면 return false;
insert/afterInsert
: 객체를 persist해도 호출하지 않음. 즉, persist시 캐싱 불가. return false;
lockItem
: 객체 수정 전에 호출, 캐시를 삭제할 것이기 때문에 Lock 불필요. return null;
update
: 객체 수정 쿼리 직후 호출. 아무것도 하지 않고 수정이 없었다는 의미로 return false;
.afterUpdate
: update
호출 직후 호출되며 여기서 캐시를 수정하지 말고 즉시 삭제(evict)해 버릴 것. 그리고 수정이 없었다는 의미로 return false;
unlockItem
: 호출 안됨.lockItem
: 객체 삭제 전에 호출된다. 아무것도 하지 않고 return null;
remove
: 삭제 쿼리 후 실행된다. 여기서 evict
를 호출하여 캐시를 삭제한다.unlockItem
: remove
후에 호출된다. 아무것도 하지 않는다.lockRegion
: 쿼리 실행 전에 호출된다. 아무것도 하지 않고 retur null;
removeAll
: 쿼리 실행 전에 호출된다. 아무것도 하지 않는다. 실제 쿼리가 날라가기 전이므로 이 시기에 캐시를 삭제하면 쿼리 결과가 Rollback시에 불필요한 부하만 증가할 것으로 보인다.unlockRegion
: 쿼리 실행 후에 호출된다. 여기서 evictAll
을 호출한다. evictAll
에서 실질적인 eviction을 수행한다.엄격한 읽기/쓰기로 두개 이상의 쓰레드에서 동시 수정할 가능성에 대해 고려하고 만들어야 한다. READ_WRITE + 클러스터링 캐시 구현체를 사용할 경우 캐시 구현체는 Lock 기능을 제공해야만 한다. 객체 생성, 수정시에 캐시 서버에 대한 접근이 매우 빈번하여 분산 캐시에서는 성능이 떨어질 수 있다. 아래는 Memcached기준으로 생각해본 구현전략.
get
: 캐시에서 읽어서 있으면 반환 한다. txTimestamp로 작업필요할 듯.putFromLoad
: 캐시에서 값을 읽어 존재하지 않으면 무조건 쓰고, 이미 캐시가 존재하며 현재 가진 것이 최신이면 덮어쓰고, 이미 있는 캐시가 더 최신 데이터이면 현재 것을 버린다.lockItem
insert
afterInsert
: 보통은 이것만 구현해도 될 듯. 객체를 캐시에서 가져와보고 이미 캐시가 존재하면 DB insert 직후 누군가가 이미 읽어가서 putFromLoad
로 인해 캐시가 생성 됐다는 뜻이므로 cache에 다시 저장하지 말고 빠져나옴.unlockItem
lockItem
update
afterUpdate
: 보통은 이것만 구현해도 될 듯. 객체를 cache에서 get해보고 객체 version등을 체크해서 현재 가진것보다 최신이면 수정하지 말고, 현재가 더 최신이면 캐시 갱신unlockItem
JTA에서만 사용한다.
Map<String,Object> props = new HashMap<String,Object>(); // hibernate.cache.use_second_level_cache props.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, true); // hibernate.cache.use_query_cache props.put(AvailableSettings.USE_QUERY_CACHE, true); // hibernate.cache.region.factory_class - 캐시 구현체 지정 props.put(AvailableSettings.CACHE_REGION_FACTORY, CachingRegionFactory.class.getName()); // hibernate.cache.region_prefix props.put(AvailableSettings.CACHE_REGION_PREFIX, "cachetest"); // hibernate.cache.default_cache_concurrency_strategy props.put(AvailableSettings.DEFAULT_CACHE_CONCURRENCY_STRATEGY, CacheConcurrencyStrategy.READ_WRITE); // ... EntityManagerFactory emf = Persistence.createEntityManagerFactory("cachetest", props);
@Cache
애노테이션이 있으면 캐시를 무조건 수행했다. 즉, 이런 상황에서 캐시를 명시적으로 끄려면 hibernate.cache.use_second_level_cache=false
를 명시적으로 지정해야만 한다.<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
등의 shared-cache-mode
설정이 필요하지만, Hibernate Cache Annotaion과 Hint를 사용할 때는 이 값을 완전히 무시하는 것으로 보인다.ENABLE_SELECTIVE
: 명시적으로 @Cacheable(true)
인 엔티티만 캐시DISABLE_SELECTIVE
: 모든 엔티티를 캐시하되 @Cacheable(false)
제외NONE
: 캐시 안함javax.persistence.sharedCache.mode
프라퍼티로 설정해도 된다.@Cache( CacheConcurrencyStrategy usage(); (1) String region() default ""; (2) String include() default "all"; (3) )
usage
: NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL
region
: Region. 안적으면 FQCN 기준으로 자동 생성include
all
: 모든 필드non-lazy
: non-lazy인 필드들만@Cacheable
애노테이션을 사실상 무시했고, @Cache
애노테이션만으로 작동하였다.@Entity @Table(name = "books") @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "books") public class Book implements Serializable { .... }
엔티티의 컬렉션을 캐시하려면 해당 컬렉션에 @Cache
애노테이션을 붙여야 한다.
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) @JoinColumn(name="CUST_ID") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public SortedSet<Ticket> getTickets() { return tickets; }
양방향 관계의 경우 명시적으로 Collection 쪽에 수정이 발생해야만 캐시가 갱신된다.
org.hibernate.cache.internal.StandardQueryCache
region에 캐시 결과를 저장한다.org.hibernate.cache.spi.UpdateTimestampsCache
region을 통해 특정 Entity의 timestamp를 저장한다.setHint
를 통해 캐시하도록 지정TypedQuery<Book> query = em.createNamedQuery("Book.byEdition", Book.class); query.setParameter("edition", 3); query.setHint("org.hibernate.cacheable", true); query.setHint("org.hibernate.cacheMode", CacheMode.NORMAL); query.setHint("org.hibernate.cacheRegion", "book-by-edition"); // region 지정
NamedQuery에 지정
@Entity @Table(name = "books") @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "books") @NamedQuery(name = "Book.byEdition", query = "from Book where edition=:edition", hints = { @QueryHint(name = "org.hibernate.cacheable", value = "true"), @QueryHint(name = "org.hibernate.cacheRegion", value = "book-by-edition") } ) public class Book implements Serializable { .... }
public interface UserRepository extends Repository<User, Long> { @QueryHints(value = { @QueryHint(name = "org.hibernate.cacheable", value = "true"), @QueryHint(name = "org.hibernate.cacheRegion", value = "user-by-lastname") }) Page<User> findByLastname(String lastname, Pageable pageable); }
현재 Hibernate는 모든 리젼(Region)을 evict
하려면 다음을 호출해야 한다.
org.hibernate.Cache cache = sessionFactory.getCache(); cache.evictEntityRegions(); cache.evictQueryRegions(); cache.evictDefaultQueryRegion(); cache.evictCollectionRegions();
org.hibernate.cacheable
Whether or not a query is cacheable ( eg. new Boolean(true) ), defaults to falseorg.hibernate.cacheMode
Override the cache mode for this query ( eg. CacheMode.REFRESH ) - CacheModeorg.hibernate.cacheRegion
Cache region of this query ( eg. new String(“regionName”) )putFromLoad
).minimalPutOverride=true
가 이를 방지하는 역할을 한다. 캐시 구현체가 minimalPutOverride
를 구현하지 않았을 수도 있다.minimalPutOverride
를 켜지 않았을 경우에는 org.hibernate.cacheMode=CacheMode.GET
혹은 그 외 값으로 통해 해당 쿼리에 대한 캐시 사용을 꺼버린다.