본문 바로가기

Spring/Spring

3가지만 기억하자. 스프링 부트 초간단 캐시 @EnableCaching, @Cacheable, @CacheEvict (spring boot cache example)

반응형

스프링 부트 캐시 적용하는 가장 쉬운 방법

스프링 부트에 캐시를 적용하고 싶으면 3가지만 기억하면 된다.

@EnableCaching, @Cacheable, @CacheEvict

1. "spring-boot-starter-cache" 라이브러리 불러온다.

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>


2. 캐시 기능을 사용하고 싶은 프로젝트에 @EnableCaching을 쓴다.

1
2
3
4
5
6
7
@EnableCaching
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
cs

3. 캐시하고 싶은 메서드에 @Cacheable, 캐시를 제거하고자하는 메서드에는 @CacheEvict를 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
public class TestService {
    private List<String> list;
    
    @PostConstruct
    public void init() {
        list = new ArrayList<>();
    }
    
    @Cacheable(value="test")
    public String getInformation(String info) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return list.stream().filter(x->x.equals(info)).findFirst().get();
    }
    
    @CacheEvict(value="test")
    public void createInformation(String info) {
        list.add(info);
    }
}


끝.


이해를 돕기 위한 부연 설명

위 설명이 전부라 사실 말 그대로 부연 설명이다.

먼저 spring boot에서 캐시 기능을 추상화 시켜놨기 때문에 spring-boot-starter-cache 라이브러리를 불러오기만하면 아주 쉽게 사용이 가능하다.

여기서 추상화 시켰다는 것은 개발자가 캐시 알고리즘을 어떻게 구현할 것이고 어떤 값을 캐시할 것이며 관리할 것인지등의 내용은 숨겨버렸다는 것이다.

즉, Anotation을 적용하는 것만으로도 캐시 기능을 사용할 수 있게 했다.

특히 스프링부트에서 자주 쓰는 캐시엔진(?)에 대해서는 자동으로 적용되게 해놨다. (Redis, Encache, ConcurrentMap, ...)

spring-boot-starter-cache 라이브러리를 불러왔으면 @EnableCaching으로 해당 애플리케이션에서 캐시를 사용할 것임을 알린다.

그 후 캐시가 적용될 메서드에는 @Cacheable(value="XXXX")를 적용한다.

그러면 해당 메서드가 최초 1회 이후 호출될때마다 메서드의 파라미터가 같은 값이면 메서드를 호출하지 않고 결과값을 캐시에서 꺼내 리턴해준다.  

value값은 캐시 아이디라고 보면된다.

데이터가 업데이트되어 기존 캐시를 삭제해야할 필요가 있을 때 @CacheEvict(value="XXXX")를 적용하게 되는데,

이럴 때 value값이 동일한 캐시를 지워버리기 때문에 value는 아이디라고 생각하면 된다.

참고로 default로 메서드의 파라미터를 캐시 값으로 구성한다.

즉 같은 메서드가 불리더라도 파라미터가 다르면 다른 캐시를 생성하는 것이다.

-> getInformation("one"); 과 getInformation("two"); 는 다른 호출로 본다.

파라미터가 여러 개 있을 때 하나만 지정하고 싶거나 다른 것을 지정하고 싶을 때에는 아래와 같이 하면 된다.

@Cacheable(value="xxxx", key="#info")

이렇게 key 속성을 지정하면 해당 파라미터를 캐시 값으로 할 수 있다. 여러 개의 파라미터를 지정하고 싶으면 +로 연결하면 된다.

@Cacheable(key = "'jeongpro'  + #param1 + #param2")

캐시값을 지울 때도 똑같이 @CacheEvict(value="xxxx", key="#info") 이런식으로 적용하면 된다.

실무에서는 Encache나 Redis를 많이 사용하는데 똑같이 사용한다.

다만 캐시 전략같은 것을 설정하는 것만 다르다.

예를들어 Encache의 경우 별도의 xml 파일을 만들어서 임시저장경로, 힙에 생성될 최대 객체 수, 캐시 삭제 여부, 캐시가 유지될 시간, 캐시 삭제 전략(알고리즘)등을 설정하고 사용하는 것만 다르다.

@CachePut이니 condition 속성을 이용한 조건부 캐시 생성이니 하는 것은 더 찾아 공부해보자.


캐시에 대한 고찰

- 캐시는 언제 써야할까?

* 반복적으로 동일한 결과를 리턴해야하는 작업

* 서버 자원을 많이 사용하는 작업 또는 시간이 오래걸리는 작업 (API호출, 데이터베이스 조회 쿼리, ...)

-> 데이터가 자주 변경되어 결과도 자주 변경되거나 애초에 소요되는 시간이 짧은 작업들은 굳이 할 필요가 없거나 오히려 역효과가 날 수 있다.


- 캐시의 필요성

캐시는 성능과 관련해서 가장 큰 영향력을 갖는다.

-> 데이터베이스 캐시

같은 쿼리를 10000번 날릴 때 같은 응답을 주는 상황에서 쿼리를 10000번 수행할 필요가 없다.

1번 수행하고 9999번은 이미 얻어낸 값을 캐시로 저장해두었다가 리턴해준다면 훨씬 효율적일 것이다.

데이터베이스에서는 쿼리를 최적화하고 DBMS 성능 튜닝 작업을 통해서 캐시 적중률을 높이는 방법으로 오래걸리는 작업을 최소화 시킨다. 아주 근본적인 해결책으로 애초에 실행을 적게할 수 있게 하는 방법으로 꼭 필요하다.

-> 브라우저 캐시

이미지 같은 리소스를 계속 클라이언트로 보내면 통신비용이 어마어마하게 들 것이다.

이런 경우 조금 다른데 서버가 아닌 브라우저가 캐시로 가지고 있어서 동일한 리소스는 다시 받아오지 않는 방법이다.

웹개발에서 이미지파일 이름은 그대로인 대신 다른 이미지로 교체하고 다시 로드하면 교체되지 않는 것을 보았을 것이다.

캐시를 지우고 다시 로드하면 교체한 이미지가 나왔을 것이다.

이런 작업 역시 브라우저가 해주지만 캐시는 꼭 필요하다.

-> 메서드 캐시

 앞서 설명한 이 글의 주제와 같다.

데이터베이스를 통해 가져오라는 명령자체를 타지않게 즉, 메서드 호출이 되지않고 결과를 리턴할 수 있기 때문에 적절하게 사용하면 아주 좋은 효과를 얻을 수 있다.

그리고 굳이 데이터베이스 조회 쿼리같은 것이 아니어도 데이터를 반복적으로 가공한다든지 수행시간이 긴 메서드를 호출한다든지 하는 작업이 있을 때는 서버(Business Layer)에서 캐시를 써야할 필요가 있다.

반응형
  • Favicon of https://escapefromcoding.tistory.com BlogIcon 코동이 2020.06.11 14:12 신고

    쉽게 설명해주셔서 감사합니다!! 다른 글들은 설정이 많은데 여기는 엄청 간단하게 설명이 되어있네요! 질문 하나만 해도 될까요? ㅎㅎ @Cacheable을 Controller.java의 GetMapping 메소드에 사용해도 될까요? 그게 가능하다면, 혹시 매개변수가 String이 아니라 (PostDto postDto)처럼 클래스의 객체값이 들어와도 자동으로 캐시를 수행할까요?

    • Favicon of https://jeong-pro.tistory.com BlogIcon JEONG_AMATEUR 2020.06.12 10:14 신고

      안녕하세요 코동이님!

      말씀하신대로 @Controller에서 사용해도 잘 적용됩니다! @GetMapping으로도 사용가능합니다.
      (테스트에서 확인)

      마찬가지로 ResponseDto를 만들어도 자동으로 캐시를 해줍니다.

      그럴일은 거의 없으시겠지만 객체 레퍼런스를 캐시하기 때문에 캐시한 객체를 저장해놓고, 그 객체의 멤버변수와 같은 데이터를 변경하면 캐시된 객체가 변화되는 것이므로 캐시는 잘 되지만 변경된 데이터가 노출될 수 있습니다.