서론
특정 데이터들을 반복적으로 호출하고 있는 코드가 있었다. 현재까지는 2천 개보다 조금 많은 수준의 데이터를 가지고 있었지만, 앞으로 훨씬 더 많은 데이터가 들어갈 예정인데 단순히 조회를 위한 데이터임에도 여러 곳에서 데이터를 호출해야하기 때문에 캐시 도입을 결정하였다. 캐시를 설정하는데에는 여러 방법이 있지만 스프링부트로 백엔드를 개발하고 있기 때문에 스프링에서 제공해주는 스프링 캐시를 사용하기로 하였다.
본론
1. 캐시 메모리에 저장할 데이터 정의
@Data
public class SomeDataCache {
private T someData;
private LocalDateTime expirationDate;
}
2. 캐시 설정 파일 정의
@EnableCaching
@Configuration
public class LocalCacheConfig {
private final String CACHE_STORE_NAME = "SomeDataCacheStore";
@Bean
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(List.of(new ConcurrentMapCache(CACHE_STORE_NAME)));
return simpleCacheManager;
}
}
3. 캐시 서비스
@Slf4j
@Service
public class CacheService {
private final String CACHE_STORE_NAME = "SomeDataCacheStore";
private static final SomeDataCache EMPTY_DATA = new SomeDataCache();
/**
* [CACHE_STORE_NAME] 캐시 조회
*/
@Cacheable(cacheNames = CACHE_STORE_NAME, key = "#key")
public SomeDataCache getCacheData(final int key) {
log.info("[CacheService] SomeDataCache key: " + key + "에 대한 캐시가 없습니다.");
return EMPTY_DATA;
}
/**
* [CACHE_STORE_NAME] 캐시 저장 / 업데이트
*/
@CachePut(cacheNames = CACHE_STORE_NAME, key = "#key")
public SomeDataCache updateCacheData(final int key, final SomeDataCache value) {
log.info("[CacheService] SomeDataCache key: " + key + "에 대한 캐시를 업데이트 합니다.");
SomeDataCache cacheData = new SomeDataCache();
cacheData.setSomeData(value);
cacheData.setExpirationDate(LocalDateTime.now().plusDays(1));
return cacheData;
}
/**
* [CACHE_STORE_NAME] 캐시 삭제
*/
@CacheEvict(cacheNames = CACHE_STORE_NAME, key = "#key")
public boolean deleteCacheData(final int key) {
log.info("[CacheService] SomeDataCache key: " + key + "에 대한 캐시를 삭제합니다.");
return true;
}
/**
* [CACHE_STORE_NAME] 캐시 데이터 유효 검증성 확인
*/
public boolean isValidation(final SomeDataCache cacheData) {
return ObjectUtils.isNotEmpty(cacheData)
&& ObjectUtils.isNotEmpty(cacheData.getExpirationDate())
&& StringUtils.isNotEmpty(cacheData.getSomeData().getId())
&& cacheData.getExpirationDate().isAfter(LocalDateTime.now());
}
}
4. 속도 비교
@Test
public void getCacheCompareTest(){
StopWatch stopWatch1 = new StopWatch();
System.out.println("=======[Database]=======");
stopWatch1.start();
// 데이터베이스에서 데이터 조회
// 데이터 캐시에 저장
stopWatch1.stop();
System.out.println(stopWatch1.prettyPrint());
System.out.println("코드 실행 시간 (s): " + stopWatch1.getTotalTimeSeconds());
StopWatch stopWatch2 = new StopWatch();
System.out.println("=======[Cache]=======");
stopWatch2.start();
// 캐시 스토어에서 데이터 조회
stopWatch2.stop();
System.out.println(stopWatch2.prettyPrint());
System.out.println("코드 실행 시간 (s): " + stopWatch2.getTotalTimeSeconds());
}
=======[Database]=======
StopWatch '': running time = 405840750 ns
---------------------------------------------
ns % Task name
---------------------------------------------
405840750 100%
코드 실행 시간 (s): 0.40584075
=======[Cache]=======
StopWatch '': running time = 909917 ns
---------------------------------------------
ns % Task name
---------------------------------------------
000909917 100%
코드 실행 시간 (s): 9.09917E-4
결론
캐시 적용 굿이에요.
반성
- 스프링 캐시는 애플리케이션이 돌아가고 있는 상태에서만 돌아간다. 당연한 말인데 별도의 캐시 메모리를 사용한다고 착각하여 테스트 코드를 작성할 때 캐시 저장과 조회를 각기 다른 테스트에 작성했다가 조회에서 발생하는 에러에 대해 고민하는 시간 낭비를 했다.
'웹, 앱' 카테고리의 다른 글
[NestJS] Strategy Pattern (전략 패턴) (1) | 2024.11.13 |
---|---|
[안드로이드] 웹뷰(Webview)에서 캐시 컨트롤(feat. 웹 버전) (0) | 2023.05.01 |
[스프링부트] 코드 실행 시간 측정 (0) | 2023.03.27 |
[Jenkins] 깃허브와 연동하여 엔진엑스-리액트 자동배포 하기 (0) | 2022.09.30 |
[Jenkins] Linux-centOS7에 젠킨스 설치 및 초기 설정 (0) | 2022.08.29 |