2013년 3월 25일 월요일

Spring 3.1 + MongoDB 를 이용한 Cache

앞서 EhCache, Memcached 는 일반적으로 메모리를 저장소로 사용합니다만, MongoDB 와 Couchbase 는 정보를 Document 로 관리하는 NoSQL DB라 볼 수 있습니다.

이런 의미로 단순 캐시로 쓰기에는 너무 많은 기능을 가지고 있다고 봐야합니다. 그런만큼 메모리에서만 작동하는 캐시시스템보다는 성능은 느립니다.
하지만 대용량 데이터나 캐시해야 할 양이 엄청 많다면? 그리고 한번 캐시한 거 영구 저장이 되면 좋겠다면? (물론 삭제 기능은 있고...)

에이 그런거라면 차라리 주 저장소를 Document DB로 바꾸는게 낫죠..

아 그렇긴 하네요... 근데, 개발자들이 NoSQL을 아주 단순히 쓰는 큰 이유는 RDBMS 처럼 관계형 데이터 처리에 익숙해서 key-value나 Document, grid 에 대한 처리에 난감해 한다는 거지요.  이 문제는 hibernate-ogm 으로 어떻게 해결하는지 향후 글을 써 보겠습니다.

우선 MongoDB 로도 가능하다는 보여 드리죠.

우선 제가 사용한 라이브러리는 spring-data-mongo, mongo-java-driver 입니다.

1. MongoCacheManager

@Slf4j
public class MongoCacheManager extends AbstractTransactionSupportingCacheManager {
private MongoTemplate mongoTemplate;
public MongoCacheManager(MongoTemplate mongoTemplate) {
Guard.shouldNotBeNull(mongoTemplate, "mongoTemplate");
this.mongoTemplate = mongoTemplate;
}
@Override
protected Collection<? extends Cache> loadCaches() {
Collection<Cache> caches = Lists.newArrayList();
for (String name : getCacheNames()) {
caches.add(new MongoCache(name, mongoTemplate));
}
return caches;
}
@Override
public Cache getCache(String name) {
synchronized (this) {
Cache cache = super.getCache(name);
if (cache == null) {
cache = new MongoCache(name, mongoTemplate);
addCache(cache);
}
return cache;
}
}
}

2. MongoCache

@Slf4j
public class MongoCache implements Cache {
private String name;
private MongoTemplate mongoTemplate;
public MongoCache(String name, MongoTemplate mongoTemplate) {
Guard.shouldNotBeEmpty(name, "name");
Guard.shouldNotBeNull(mongoTemplate, "mongoTemplate");
this.name = name;
this.mongoTemplate = mongoTemplate;
if (log.isDebugEnabled())
log.debug("MongoCache를 생성합니다. name=[{}], mongodb=[{}]", name, mongoTemplate.getDb().getName());
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return mongoTemplate;
}
@Override
public ValueWrapper get(Object key) {
Guard.shouldNotBeNull(key, "key");
CacheItem item = mongoTemplate.findOne(new Query(Criteria.where("key").is(key)), CacheItem.class, name);
Object result = null;
if (item != null) {
result = item.getValue();
if (log.isDebugEnabled())
log.debug("캐시 값을 로드했습니다. key=[{}], value=[{}]", key, result);
}
SimpleValueWrapper wrapper = null;
if (result != null)
wrapper = new SimpleValueWrapper(result);
return wrapper;
}
@Override
public void put(Object key, Object value) {
Guard.shouldNotBeNull(key, "key");
if (!mongoTemplate.collectionExists(name))
mongoTemplate.createCollection(name);
if (log.isDebugEnabled())
log.debug("캐시에 값을 저장합니다. key=[{}], value=[{}]", key, value);
if (get(key) == null)
mongoTemplate.insert(new CacheItem(key, value), name);
else
mongoTemplate.upsert(new Query(Criteria.where("key").is(key)),
Update.update("value", value),
name);
}
@Override
public void evict(Object key) {
Guard.shouldNotBeNull(key, "key");
mongoTemplate.remove(new Query(Criteria.where("key").is(key)), name);
}
@Override
public void clear() {
mongoTemplate.dropCollection(name);
}
@Getter
@Setter
public static class CacheItem implements Serializable {
private Object key;
private Object value;
public CacheItem() { this(null, null); }
public CacheItem(Object key, Object value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return "CacheItem@{key=" + key + ", value=" + value + "}";
}
}
}
view raw MongoCache.java hosted with ❤ by GitHub


MongoCacheManager 는 별 내용 없구요... MongCache는 Spring 의 MongoTemplate 를 적극 이용하여, 저장/검색을 수행하였습니다.
상세 내용은 Spring Data Mongo 매뉴얼을 보시면 되겠습니다.

3. MongoCacheConfiguration

@Configuration
@EnableCaching
@ComponentScan(basePackageClasses = {UserRepository.class})
@Slf4j
public class MongoCacheConfiguration extends AbstractMongoConfiguration {
@Override
protected String getDatabaseName() {
return "debop4j_core_cache";
}
@Override
@Bean
public Mongo mongo() throws Exception {
return new Mongo("localhost");
}
@Bean
public MongoCacheManager mongoCacheManager() throws Exception {
return new MongoCacheManager(mongoTemplate());
}
}

너무 간단하죠? 나머지 테스트 코드는 앞 EhCache, Memcached 의 예와 똑같아서 생략하겠습니다.
MongoDB의 경우 Database를 지정해주고, 각 캐시명을 Collection 명으로 매핑합니다.
그럼 이만...

댓글 없음: