스프링의 캐싱

geon·2023년 8월 11일
0

https://docs.spring.io/spring-framework/reference/integration/cache/strategies.html
https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html

우테캠 쇼핑 미션 5단계인 외부 API 호출을 어떻게 처리할지 고민하다가 캐싱 적용을 고려하게 되었다. 다음은 스프링에서 캐싱을 어떻게 처리하는지 공식 문서를 읽고 필요한 부분만 정리한 내용이다.

캐시와 버퍼는 무엇이 같고 다른가


버퍼와 캐시는 모두 속도가 빠른 장치와 느린 장치 간의 처리 속도 차이로 인한 비효율을 완화하는 임시적인 저장소이다. 그렇다면 어떤 점이 다른가?

  • 버퍼
    • 전체 블록을 한 번에 이동시킴으로써 속도가 빠른 장치가 기다리는 상황을 방지함
    • 데이터는 한 번만 쓰고 읽음
    • 두 장치 중 적어도 한 쪽은 버퍼의 존재에 대해 알고 있음
  • 캐시
    • 같은 데이터를 여러 번 읽을 수 있고 이를 통해 성능을 개선함
    • 두 장치 모두 캐시의 존재에 대해 알지 못함

스프링의 캐시 추상화


스프링의 캐시 추상화는 메서드 단위로 적용된다. 메서드가 호출된 적이 있으면 캐시된 데이터가 반환되고 실제 호출은 이루어지지 않는다. 메서드가 호출된 적이 없으면 메서드가 호출되고 반환값이 캐시된다. 이 값은 나중에 재사용된다.

이를 통해 같은 파라미터로 메서드를 다시 호출하는 경우 이전의 결과를 재사용할 수 있다. CPU나 IO 자원을 많이 이용하는(추가적으로, 같은 인자에 대해 같은 결과를 반환하는) 메서드의 경우 캐싱을 이용하면 좋을 것이다.

스프링은 캐싱 로직에 대한 추상화만 제공하고 실제로 데이터를 저장할 저장소를 필요로 하는데 이는 Cache, CacheManager 인터페이스로 구체화된다. 스프링은 ConcurrentHashMap, Gemfire, Caffine 등 몇 가지 구현체를 제공한다.

캐시 추상화를 사용하기 위해 두 가지 측면을 고려해야 한다.

  • 캐싱 대상 메소드를 정하고 어떤 상황에서 캐싱할 것인지 정책을 정해야 한다. (캐싱 선언)
  • 캐시 데이터를 실제로 어디에 저장할지 정해야 한다. (캐싱 설정)

Declarative Annotation-based Caching (선언적 어노테이션 기반 캐싱)


@Cachable 어노테이션

  • 캐시 가능한 메서드를 식별하기 위한 어노테이션이다.
  • 어노테이션 value 필드에는 메서드와 연관되는 캐시 이름을 지정한다.
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
  • 캐시는 키-값 저장소이므로 메서드 반환값을 매핑할 키가 필요한데, 기본 KeyGenerator는 다음과 같이 키를 생성한다.
    • 메서드 파라미터가 없다면 SimpleKey.EMPTY를 반환
    • 하나의 파라미터가 주어지면 그 객체를 반환
    • 둘 이상의 파라미터가 주어지면 모든 파라미터를 포함하는 SimpleKey 객체를 반환
      • 이때 파라미터들은 자연키를 가지고 equals, hashcode를 구현해야 함
    • 다른 KeyGenerator를 사용하고 싶다면 해당 인터페이스를 구현하면 된다.
  • 파라미터 중 적당한 몇 가지만 골라서 키로 사용하고 싶은 경우 SpEL(Spring Expression Language)를 사용할 수 있다.
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
  • cacheManager 필드에 사용할 CacheManager를 지정할 수 있다.
  • 메서드가 동시에 호출되어 여러 번 실행되는 것을 막기 위해서 sync 필드를 true로 설정할 수 있다. 이를 사용하면 한 스레드가 값을 계산 중일 때 다른 스레드들을 블록할 수 있다.
  • condition 필드와 unless 필드를 사용하면 조건적인 캐싱이 가능하다.

@CachePut 어노테이션

  • 해당 어노테이션이 붙은 메서드는 항상 호출되고 반환값은 캐시에 저장 혹은 업데이트된다.
  • @CachePut@Cachable을 함께 사용하면 안 된다. 예상하지 못한 동작을 유발할 수 있다.

@CacheEvict 어노테이션

  • 캐시에서 오래되거나 사용하지 않는 제거하는 데 사용한다.
    • 제거 동작을 유발(trigger)하는 메서드에 붙인다.
  • allEntries 필드를 true로 지정하면 캐시 데이터가 전부 삭제된다. 이때는 키를 지정해도 무시된다.
  • beforeInvocation 필드를 true로 지정하면 메서드가 실행되기 전에 eviction이 일어난다.
  • @Cacheable과 다르게 메서드 반환 타입이 void여도 상관없다.

@Caching 어노테이션

  • 같은 타입 어노테이션을 그룹핑하기 위해 사용
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

@CacheConfig 어노테이션

  • 클래스 단위 설정에 사용
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {

	@Cacheable
	public Book findBook(ISBN isbn) {...}
}
profile
뭐라도 적기

1개의 댓글

comment-user-thumbnail
2023년 8월 11일

유익한 글이었습니다.

답글 달기