ChamomileGuides 3.0.4 Help

캐시 모듈(Redis) 사용 가이드

개요

어플리케이션이나 시스템에서 클라이언트에 대한 요청을 처리 할 때 자주 사용되는 데이터가 있을 수 있다. DB 데이터 조회 작업은 비용이 비교적 많이 들기 때문에 DB 조회를 자주 하면 시스템 성능에 영향을 미칠 수 있다.

주로 Update가 자주 일어나지 않으며 Read가 빈번하게 일어나는 데이터를 캐싱하면 어플리케이션의 속도를 개선 할 수 있다.

alt text

사진 출처

스프링은 CacheManager를 통해 추상화를 지원하기 때문에 실제 캐싱한 데이터의 저장이 일어나는 캐시 구현체는 별도의 설정을 통해 변경 가능하며, 캐시 구현체는 Ehcache, Redis, Hazelcast 등 다양한 캐시 매니저를 선정할 수 있다.

Cache Clustering

캐시 클러스터링은 이중화 된 서버간의 캐시 동기화를 의미한다.

캐시 클러스터링을 구성하면 연결된 모든 서버간의 캐시 및 엘리먼트가 동기화 되며 요건에 따라 보다 효율적으로 시스템을 구성 할 수 있다.

Redis

메모리 위에서 동작하는 Key/value 저장소인 Redis는 NoSQL DBMS로 분류되며 동시에 Memcached와 같은 인메모리 솔루션으로 분리된다.

  • 다중 서버 구성 가능(샤딩, 레플리케이션 등)

  • 데이터베이스로 사용될 수 있으며, Cache 로도 사용 가능

  • 성능은 머신 스펙에 따라 다르나 초당 2만 ~ 10만회 수행 가능

  • 상대적으로 큰 데이터 캐싱 가능

  • 네트워크 트래픽이 발생해 속도가 저하될 수 있으나 이중화를 통해 장애에 유연하게 대응 가능

모듈 설명

chamomile-cache-redis 모듈은 다음과 같은 특징을 갖는다.

  1. 레디스 의존성 자동 포함

  2. 레디스 관련 옵션 제공

기본 사용법

  1. chamomile-sample-boot-basic 프로젝트 생성

  2. dependency 등록(pom.xml)

    <dependency> <groupId>net.lotte.chamomile.module</groupId> <artifactId>chamomile-cache-redis</artifactId> </dependency>
  3. yaml 파일 설정(application.yml)

    chmm: cache: instance: redis #none/redis/hazelcast mode: standalone host: 127.0.0.1 port: 6379
  4. 예제 코드 작성

    @Service @RequiredArgsConstructor @Slf4j public class BoardCacheServiceImpl implements BoardService { private final BoardMapper boardMapper; @Override @Transactional(readOnly = true) @Cacheable(value = "boards") public List<Board> getBoardsList() { return boardMapper.getAllBoardsList(); } @Override @Transactional(readOnly = true) public Slice<Board> getBoardsSlice(Pageable pageable) { return boardMapper.getAllBoardsSlice(pageable); } @Override @Transactional(readOnly = true) public Page<Board> getBoardsPage(Pageable pageable) { return boardMapper.getAllBoardsPage(pageable); } @Override @Transactional(readOnly = true) @Cacheable(value = "board") public Board getBoard(Long boardId) { return boardMapper.getBoardById(boardId) .orElseThrow(() -> new NoSuchElementException("There is no Board.")); } @Override @Transactional public Board createBoard(BoardRequest request) { Board requestEntity = request.toEntity(); requestEntity.onCreate(); boardMapper.insertBoard(requestEntity); return boardMapper.getBoardById(requestEntity.getId()) .orElseThrow(() -> new NoSuchElementException("There is no Board.")); } @Override @Transactional public Board updateBoard(Long boardId, BoardRequest request) { Board requestEntity = request.toEntity(boardId); requestEntity.onUpdate(); boardMapper.updateBoard(requestEntity); return boardMapper.getBoardById(boardId) .orElseThrow(() -> new NoSuchElementException("There is no Board.")); } @Override @Transactional public Long deleteBoard(Long boardId) { boardMapper.deleteBoard(boardId); return boardId; } }

레디스 클러스터 적용

해당 내용은 레디스클러스터 를 참조한다.

스프링 캐시 활용법(애노테이션)

어노테이션

주요 기능

@Cacheable

캐시 저장 및 조회

@CacheEvict

캐시 삭제

@CachePut

메서드 실행을 방해하지 않고 캐시를 업데이트

@Caching

여러개의 캐시 어노테이션을 함께 사용하고 싶을 때 사용

@Cacheable

@Cacheable 어노테이션은 캐시 할 메서드를 지정하는데 사용한다. @Cacheable 어노테이션이 있는 메서드가 호출되면, 이미 실행된 메서드라면 여러 번 실행하지 않고 캐시 된 스토리지에서 결과를 반환한다. 아래의 코드는 가장 간단한 형식 @Cacheable을 통한 캐시 사용 방법이다. 메서드에 어노테이션과 함께 연결한 캐시의 이름을 지정한다.

@Cacheable(value = "samplecache") Public Sample getById(int id) {...}

위의 코드에서 메서드 getById는 samplecache 캐시와 연결된다. 메서드가 호출 될 때마다 캐시를 검사하여 이미 실행되었고 반복 될 필요가 없는지 확인한다.

하나의 캐시만 선언하지 않고 어노테이션에서는 여러 개의 이름을 지정하여 두 개 이상의 캐시를 사용하면 메서드를 실행하기 전에 각 캐시를 검사한다. 적어도 하나의 캐시에 히트가 발생하면 연결된 캐시의 값이 반환된다.

@Cacheable(value = "samplecache1, samplecache2") public Sample getById(int id) {...}

캐시는 기본적으로 key – value 저장소이기 때문에 캐시 된 메서드를 호출 할 때마다 캐시 액세스에 적합한 key로 변환 해야한다.

key의 명시적 선언은 SpEL 사용하여 가능하며, 명시적으로 key를 지정하지 않았다면 메서드의 인자를 key로 사용한다.

아래의 코드는 SpEL를 사용하여 명시적인 key 선언의 예이다.

@Cacheable(value = "samplecache", key = "#sample") public Sample getById(Sample sample) {...} @Cacheable(value = "samplecache", key = "#sample.id") public Sample getById(Sample sample) {...} @Cacheable(value = "samplecache", key = "#sample.name") public Sample getById(Sample sample) {...}

개발 요건에 따라 메서드에 항상 캐싱되는 것이 적합하지 않을 수 있다. 이럴 경우 condition 속성을 통해 true나 false가 되는 SpEL 표현식을 받는 conditional 파라미터로 캐시 적용 유무를 선택 할 수 있다. SpEL 표현식에 의해 true이면 메서드를 캐시하고 false이면 메서드가 캐시 되지 않는다.

아래의 코드는 파라미터인 id가 200일 경우 캐시하지 않는다.

@Cacheable(value = "samplecache", key = "#id", condition="#id!=200") public Sample getById(int id) {...}

아래의 코드는 파라미터인 id가 300 이상일 경우 캐시한다.

@Cacheable(value = "samplecache", key = "#id", condition="#id>=300") public Sample getById(int id) {...}

@CachePut

메서드의 실행에 영향을 주지 않고 캐시를 업데이트해야하는 경우 @CachePut 어노테이션을 사용할 수 있다.

메서드는 항상 실행(조건이 맞으면)되며 그 결과는 캐시에 저장한다.

@Cacheable와 같은 옵션을 지원하며 메서드 흐름 최적화가 아닌 캐시 생성에 사용한다.

아래의 코드는 id를 키로 메서드의 결과를 캐시에 저장하며, 같은 key(id)로 호출되더라도 @Cacheable과는 다르게 메서드가 실행된다.

@CachePut(value = "samplecache", key = “3_4_5") public Sample updateSample(Sample sample) {...}

@CacheEvict

@CacheEvict 어노테이션은 캐시를 제거한다. 즉, 캐시에서 데이터를 제거하는 트리거로 동작하는 메서드다.

다른 캐시 어노테이션과 마찬가지로 @CacheEvict 또한 동작할 때 영향을 미칠 하나 이상의 캐시를 지정해야 한다.

또한 @CacheEvict에서도 key나 condition을 지정해야 할 수 있지만 key에 매핑된 하나의 엔트리 가 아니라 전체를 제거하기 위해서는 allEntries 속성을 추가로 사용할 수 있다.

아래의 코드는 samplecache 캐시의 모든 엔트리를 삭제의 예이다

@CacheEvict(value = "samplecache", allEntries = true) public void clearCache() {...}

@CacheEvict를 다른 어노테이션과 다르게 void를 메서드에 적용 할 수 있다. 메서드가 트리거로 동작하므로 리턴값은 무시한다. 이는 캐시에 데이터를 넣거나 갱신해서 그 결과가 필요한 @Cacheable과 다른 점이다.

@Caching

동일한 메소드에서 @CacheEvict 또는 @CachePut과 같은 유형의 여러 어노테이션을 지정하려는 경우 유용하게 사용 할 수 있다.

같은 key를 사용하여 같은 아이템을 포함하는 두 개의 캐시가 있다고 가정 해 보자. 두 캐시에서 모두 아이템을 제거하려는 경우는 간단하다.

@CacheEvict(value = {"cache1", "cache2"}, key = "#sample.id") public void refreshSample(Sample sample) {...}

만약 다른 key를 사용한다면 아래와 같이 @Caching 어노테이션을 통해서 여러유형의 어노테이션을 지정할 수 있다.

@Caching(evict = { @CacheEvict(value = "cache1", key = "#sample.id"), @CacheEvict(value = "cache2", key = "#sample.name") }) public void refreshSample(Sample sample) {...}

스프링 캐시 활용법(CacheManager)

CacheManager을 직접 사용하면 일반적인 컬렉션(HashMap 등)과 같이 개발이 가능하다.

CacheManager와 Cache 클래스에서 제공하는 주요 API는 아래와 같다.

API

주요 기능

CacheManager.getCache

지정한 캐시를 리턴

CacheManager.getCacheNames

등록된 모든 캐시의 이름을 리턴

Cache.put

지정한 키로 Object를 캐시에 적재

Cache.get

지정한 키에 매핑하는 캐시아이템을 리턴

Cache.evict

지정한 키에 매핑된 캐시아이템을 삭제

Cache.clear

적재된 캐시아이템을 모두 삭제

Cache.getName

캐시명을 리턴

import org.springframework.cache.CacheManager; import org.springframework.cache.Cache;

CacheManager를 직접 사용하기 위해서는 빈으로 등록된 CacheManager 를 가져와야 한다.

CacheManager.getCache(String name)

cacheManager의 getCache 메서드를 통해 캐시를 가져온다.

CacheManager의 직접 사용을 위해 반드시 호출해야 하는 API이다.

Cache cache = cacheManager.getCache("캐시 이름");

CacheManager.getCacheNames()

저장된 모든 캐시 이름을 조회한다.

cacheManager.getCacheNames();

Cache.put(Object key, Object value)

앞서 호출한 getCache를 통해 리턴된 Cache 객체로 캐시아이템을 캐시에 저장 할 수 있다.

아래의 코드는 put을 통한 캐시아이템 저장 예시 이다.

// String “key1”를 키로 Sample 객체를 캐시에 저장 cache.put("key1", new Sample(100, "Developer_1", "java")); // String “key2”를 키로 “Devleoper_2”를 캐시에 저장 cache.put("key2", "Developer_2"); // Integer 100을 키로 현재 시간을 캐시에 저장 cache.put(100, new Date().toString());

Cache.get(Object key)

캐시에 저장된 캐시아이템을 key를 통해 가져올 수 있다.

get을 통해 호출되는 리턴 타입은 ValueWrapper클래스이며,get을 한번 더 호출하여 실제 반환 값을 리턴 받는다.

아래의 코드는 get을 통한 캐시아이템 호출 예시이다.

cache.get("key1").get(); cache.get("key2").get(); cache.get(100).get();

만약 호출하는 key에 대해 저장된 캐시가 없다면 NullPointerException이 발생한다.

Cache.evict(Object key)

키에 매핑된 캐시아이템을 삭제한다.

해당 키에 저장된 캐시아이템이 없더라도 에러를 발생하지 않는다.

아래의 코드는 evict을 통한 특정 키에 대한 캐시아이템 삭제 예시이다.

cache.evict("key1"); cache.evict("key2"); cache.evict(100);

Cache.clear()

캐시에 있는 모든 캐시아이템을 삭제한다.

아래의 코드는 clear를 통한 캐시아이템 전체 삭제 예시이다.

cache.clear();

Cache.getName()

현재 연결된 캐시의 이름을 가져온다.

아래의 코드는 getName을 통한 캐시 이름 호출 예시이다.

cache.getName();
Last modified: 10 1월 2025