Caffeine
- 자바 8을 기반으로 개발된 고성능 캐시 라이브러리인 카페인을 사용해 보겠다.
- 카페인은 자바의 표준 캐싱 API인 JCache 기반이다. 즉, JSR-107과 함께 스프링을 포함해서 다양한 자바 생태계의 프레임워크를 지원하고 있다.
implementation 'com.github.ben-manes.caffeine:caffeine:2.8.0'
- 위와 같이 의존성을 추가해 준다.
@Test
void useCache() throws InterruptedException {
// 캐시를 200ms 동안 유지하고, 최대 100개까지 저장해 둘 수 있는 카페인 캐시 객체 생성하기
Cache<String, Object> cache = Caffeine.newBuilder()
.expireAfterWrite(200, TimeUnit.MILLISECONDS)
.maximumSize(100)
.build();
String key = "springrunner";
Object value = new Object();
Assertions.assertNull(cache.getIfPresent(key));
cache.put(key, value);
Assertions.assertEquals(value, cache.getIfPresent(key));
TimeUnit.MILLISECONDS.sleep(100);
Assertions.assertEquals(value, cache.getIfPresent(key));
// 200ms 가 지났으므로 만료돼서 null 이 반환된다.
TimeUnit.MILLISECONDS.sleep(100);
Assertions.assertNull(cache.getIfPresent(key));
}
- 하지만, 캐시를 사용할 때 주의할 점은 메모리의 크기가 무한하지 않다는 점이다.
- 따라서 모든 객체를 메모리에 저장할 수는 없다.
- 잘못사용하면 캐시로 사용하는 메모리의 크기가 너무 커서 애플리케이션이 사용해야 하는 메모리에 영향을 줄 수 있기 때문이다.
- 따라서 캐시를 사용할 때에는 최대 개수라든가, 만료 시간과 같은 것들을 사용해서 효율적으로 캐시를 사용하는 것이 중요하다.
- 위의 코드에서 expireAfterWrite, maximumSize 통해서 만료시간과 최대로 수용할 수 있는 객체의 수를 제한함으로써 메모리를 관리한다.
읽기 속도 최적화
- 로컬에 데이터가 이미 있다면 원격지에 변경이 일어나지 않는 이상 새로 내려받지 않는식으로 최적화할 수 있다.
public class CsvMovieReader implements MovieReader {
@Override
public List<Movie> loadMovies() {
try {
final InputStream content = getMetadataResource().getInputStream();
return new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8))
.lines()
.skip(1)
.collect(Collectors.toList());
} catch(IOException error) {
throw new ApplicationException("failed to load movies data.", error);
}
}
}
- 위와 같은 코드를 아래와 같이 캐시를 적용시킬 수 있다.
public class CsvMovieReader implements MovieReader {
private final Cache<String. List<Movie>> cache;
public CsvMovieReader(Cache<String, List<Movie> cache>) {
this.cache = Objects.requireNonNull(cache);
}
@Override
public List<Movie> loadMovies() {
/**
* 캐시에 저장된 데이터가 있다면, 즉시 반환
*/
List<Movie> movies = cache.getIfPresent("csv.movies");
if (Objects.nonNull(movies) && movies.size() > 0){
return movies;
}
try {
final InputStream content = getMetadataResource().getInputStream();
return new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8))
.lines()
.skip(1)
.collect(Collectors.toList());
} catch(IOException error) {
throw new ApplicationException("failed to load movies data.", error);
}
/**
* 획득한 데이터를 캐시에 저장하고, 반환한다.
*/
cache.put("csv.movies", movies);
return movies;
}
}
@Bean
public CsvMovieReader csvMovieReader() {
Cache<String, List<Movie>> cache = Caffeine.newBuilder()
.expireAfterWrite(200, TimeUnit.MILLISECONDS)
.maximumSize(100)
.build();
}
- 위와 같이 빈으로 CsvMovieReader 를 등록하면서 캐시의 기본 설정을 해줄 수 있다.
- 위의 코드로 만족하는가? -> 만약 카페인이 아닌 다른 라이브러리를 사용하면 어떻게 되는가? -> 변경을 수용할 수 있도록 추상화 해야한다.
- 캐시 매니저 인터페이스를 통해서 캐시를 생성하거, 구성하고, 사용을 관리할 수 있다.
public class CsvMovieReader implements MovieReader {
private final CacheManager cacheManager;
public CsvMovieReader(CacheManager cacheManager) {
this.cacheManager = Objects.requireNonNull(cacheManager);
}
@Override
public List<Movie> loadMovies() {
Cache cache = cacheManager.getCache(getClass().getName());
/**
* 캐시에 저장된 데이터가 있다면, 즉시 반환
*/
List<Movie> movies = cache.get("csv.movies", List.class);
if (Objects.nonNull(movies) && movies.size() > 0){
return movies;
}
try {
final InputStream content = getMetadataResource().getInputStream();
return new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8))
.lines()
.skip(1)
.collect(Collectors.toList());
} catch(IOException error) {
throw new ApplicationException("failed to load movies data.", error);
}
/**
* 획득한 데이터를 캐시에 저장하고, 반환한다.
*/
cache.put("csv.movies", movies);
return movies;
}
}
- 위와 같이 CacheManager 를 통해서 캐시에 접근하는 식으로 코드를 수정한다.
@Bean
public CaffeineCacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS));
return cacheManager;
}
- 이렇게 카페인 매니저를 캐시 매니저로 설정해서 빈으로 등록한다.
- 만약 카페인이 아니고 레디스를 사용하고 싶다면? -> 캐시 매니저를 레디스 매니저로만 바꿔주면 된다. -> 추상화가 적용되었다.
REFERENCES
- 박용권님의 스프링러너 스프링 아카데미
'Spring' 카테고리의 다른 글
프락시 팩토리빈과 @AspectJ (0) | 2022.03.11 |
---|---|
프로파일과 Resource 인터페이스 (0) | 2022.03.10 |
자바코드로 의존관게 주입하기 (0) | 2022.03.10 |
필터와 인터셉터 (0) | 2022.02.27 |
파일 업로드 (0) | 2022.02.27 |