티스토리 뷰
안녕하세요~ 오늘은 서버에서 자주 사용하는 Spring Framework의 캐시에 대한 정의와 Spring Framework에서 제공하는 추상화 어노테이션인 위의 3개의 어노테이션와 관련된 기능에 대해 알아보려고 합니다.
1. Spring이 제공하는 캐시는 어떤 일을 할까요?
캐시는 서버의 부담을 줄이고, 성능을 높이기 위해 사용되는 기술입니다. 같은 파라미터로 넘어오는 요청에 대하여 계산이 복잡하고, 같은 데이터를 여러번 조회하는 것은 서버의 입장으로 큰 손해입니다. 캐시는 값을 저장하고 사용하기 때문에 동일한 응답을 주는 것에 대해 이점을 가지고 있습니다. 그래서 사람들은 동일한 데이터를 서버의 리소스 낭비 없이 전달해주기 위해 캐시를 적용하였습니다.
Spring 도 마찬가지로 이런 점을 반영하여 캐시를 적용한 추상화 클래스가 존재합니다. AOP 방식으로 편하게 메소드에 캐시 서비스를 제공하고 있습니다. 캐시 서비스는 트랜잭션과 마찬가지로 AOP를 이용해서 메소드 실행 과정에 적용되고, 사용성 면에서도 편하게 적용할 수 있습니다. 가장 큰 장점은 추상화 서비스를 제공하기 대문에 환경이 변해도 캐시 기술을 변경할 필요가 없다는 것입니다. 오롯이 코드에만 신경쓰면 됩니다.
2. 물론 캐시도 단점이 있습니다.
일반적으로 사용자들이 알고 있는 일반 메모리와 캐시 메모리는 이름부터가 다르므로 차이가 있습니다. 일반 메모리는 가격이 저렴한 대신 대용량으로 컴퓨터 내에 존재할 수 있고, 데이터의 처리속도가 캐시 메모리에 비하여 느립니다. 물론, 사람이 생각하는 그 정도의 느림은 아닙니다. 일반 메모리도 사람 눈에 안보일 정도로 어마어마하게 빠릅니다.
반대로 캐시 메모리는 일반 메모리에 비하여 가격면으로도 비싸고, 소량만 존재합니다. 경제 원리와 비슷한 이치랄까요. 여튼 캐시 메모리는 보유하고 있는 리소스 양이 적다보니 효율적으로 사용되어야 빛을 봅니다.
이 말은 다시 얘기하자면 다른 일반 메모리를 써야할 곳에 캐시 메모리를 쓰게 되면 막심한 손해를 일으킬 수 있다는 것입니다. 그렇기 때문에 매번 다른 결과를 돌려줘야 하는 응답값이라면 캐시 메모리를 쓰는 것은 의미가 없습니다. 좋은 장비를 사두고 이상한 곳에 굴리는 것과 다를 바가 없습니다.
그리고 가장 치명적인 단점은 바로 메모리의 특성상 휘발성이라는 것입니다. 캐시 메모리는 DB와 다르게 전기공급이 끊기게 되면, 가지고 있던 모든 데이터를 날리게 됩니다. 그렇기 때문에 작동중인 애플리케이션이 종료되면 캐시 메모리는 초기화 됩니다. 그래서 카카오같은 기업에서는 서버 재기동의 경우 다른 사용자들이 들어올 유입을 대비하기 위해 TDD로 엄청난 양의 캐시를 생성하는 서비스를 배포중에 가지고 있다고도 합니다.
3. 캐시 사용법
이제 본격적으로 Spring 에서 제공하는 캐시 관련 설정에 대해 알아보겠습니다. build.gradle 에 다음과 같이 의존성을 추가해줍니다.
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache
implementation 'org.springframework.boot:spring-boot-starter-cache:3.0.0'
가장 대표적으로 CacheConfig 클래스를 만들어 @EnableCaching 을 추가해줍니다.
@EnableCaching
@Configuration
public class CacheConfig {
}
4. 캐시 매니저 빈 추가
어노테이션을 추가한 뒤 캐시를 관리할 CacheManager 를 빈으로 등록해주어야 합니다. Spring은 6개의 Cache Manager 를 제공해주는데 목록은 아래와 같습니다.
- ConcurrentMapCacheManager
- ConcurrentHashMap 을 이용해 캐시 기능을 구현하는 심플한 캐시 매니저, 캐시 정보를 Map으로 저장하여 빠르고 별다른 설정이 필요없지만 실제 서비스에서 사용하기는 다소 어렵습니다.
- SimpleCacheManager
- 기본적으로 제공하는 캐시가 없어 사용할 캐시를 직접 등록하여 사용합니다.
- EhCacheCacheManager (이름이 이상하지만 캐시 프레임워크가 EhCache 입니다.)
- 캐시 프레임워크 중 하나로 자바에서 많이 사용합니다.
- CompositeCacheManger
- 한 개 이상의 캐시 매니저를 사용하도록 지원해주는 혼합 캐시 매니저입니다.
- CaffeineCacheManager
- 자바 8로 Guava 캐시를 재작성한 캐시 매니저입니다. EhCache 보다 더 좋은 성능을 가진다고 하여 많이 사용합니다.
- JCacheCacheManager
- JSR-107 기반의 캐시를 사용합니다.
여기에서 필요한 캐시 매니저는 사용하고자 또는 구현하고자 하는 캐시의 성질에 따라 다릅니다.
https://docs.spring.io/spring-framework/docs/5.0.0.M5/spring-framework-reference/html/cache.html
저는 대표적으로 사용하는 EhCache 에 대해 설정해주려 합니다. 아까 설정해준 CacheConfig 파일을 들여다 보겠습니다.
build.gradle
// https://mvnrepository.com/artifact/net.sf.ehcache/ehcache
implementation 'net.sf.ehcache:ehcache:2.10.6'
CacheConfig.java
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
bean.setConfigLocation(new ClassPathResource("ehcache-config.xml")); // Ehcache setting resource
bean.setShared(true); // Singleton
return bean;
}
@Bean
public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean) {
EhCacheCacheManager manager = new EhCacheCacheManager();
manager.setCacheManager(ehCacheManagerFactoryBean.getObject());
return manager;
}
}
ehcache-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
maxBytesLocalHeap="300M">
<!--<diskStore path="java.io.tmpdir" />-->
<sizeOfPolicy maxDepth="100000"/>
<!-- 필수로 설정해야하며 <cache> 들의 기본 설정 값 -->
<defaultCache
timeToLiveSeconds="180"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
<cache name="guelLocalCache"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU">
</cache>
</ehcache>
5. @Cacheable, @CachePut, @CacheEvict 사용하기
그 다음으로는 캐시를 컨트롤할 어노테이션들에 대해 알아보겠습니다.
5-1. @Cacheable 을 사용하여 캐시를 저장 및 조회
@Cacheable 은 SQL 중 SELECT, INSERT 를 제공하는 역할을 합니다. 캐시 데이터가 없는 경우 입력을 하고 캐시 데이터가 있는 경우 조회하게 됩니다. 코드로 살펴보는 것이 더 편리할 것 입니다.
Ranking.java
@Getter
@NoArgsConstructor
@Entity
public class Ranking {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long ranking;
private String username;
public Ranking(final Long ranking, final String username) {
this.ranking = ranking;
this.username = username;
}
public void changeName(final String newName) {
this.username = newName;
}
}
@Cacheable(value = "myRanking", key = "#id")
public Ranking getMyRanking(final Long id) {
}
1번째 호출: 나의 랭킹이 캐시에 등록이 되어 있지 않으므로 해당 메소드 내에 로직을 수행하게 됩니다.
2번째 호출: id 파라미터에 해당하는 값이 캐시에 저장되어 있으므로 해당 메소드 내 로직을 수행하지 않고 캐시에서 조회한 값을 반환합니다.
3번째 호출: 랭킹이 변경된 경우 해당하는 로직을 다시 수행합니다.
5-2. @CachePut 으로 데이터 저장
@CachePut(value = "ranking", key = "#id")
public void setRanking(final Long id) {
}
단순히 캐시를 저장하는 용도로 사용하게 되므로 반환을 하지 않습니다. 자주 값이 바뀌어 캐시의 값이 갱신될 필요가 있을 떄 주로 사용합니다.
5-3. @CacheEvict 로 데이터 삭제
@CacheEvict(value = "deleteRanking", key = "#id")
public boolean deleteRanking(final Long id) {
}
캐싱된 데이터가 변경될 경우 기존의 캐싱된 데이터가 바뀌어야 하기 때문에 지워주는 작업이 필요합니다.
5-4. @Caching
하나의 메소드를 호출할 때 @Cacheable, @CacheEvict 가 모두 필요한 경우 2개를 같이 선언해서 사용할 수 있습니다.
@Caching(evict = {
@Cacheable("myRanking"),
@CacheEvict(value="remove", key="#id") })
public Ranking getMyRanking(final Long id) {
}
도움이 된 문서들
https://mangkyu.tistory.com/179
https://hwannny.tistory.com/53
https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache
'Server' 카테고리의 다른 글
[Gradle] build.gradle 에 적은 project.version 을 푸터에 표시해보자 (0) | 2022.12.28 |
---|---|
[Spring] Redis 실제 사용해보기 (0) | 2022.12.16 |
[Spring] Redis와 부가 어노테이션 설정하기 (0) | 2022.12.16 |
[AWS] IAM 에서 사용하는 사용자 이메일 변경하기 (4) | 2022.12.09 |
[Nginx] 서버에 요청받는 Request Body Size 늘리기 (2) | 2022.12.09 |
[JPA] Repository 내에서 Join 사용하기 (2) | 2022.12.07 |