캐시와 캐싱이란?
캐시는 본래 CPU 내부의 작은 영역으로, 정말 빈번히 접근하게 되는 데이터를 저장해두는 임시 기억장치이다.
캐시의 목적과 방식을 웹 개발에 적용하면, 빈번하게 접근하게 되는 데이터베이스의 데이터를 Redis 등의 인메모리 데이터베이스에 저장을 함으로써 데이터를 조회하는데 걸리는 시간과 자원을 감소시키는 기술을 캐싱이라고 한다.
캐싱 전략
기본적으로 캐시는 본래 저장된 곳이 아닌 다른곳에 데이터를 저장하는 행위이며, 언제든 사라질 수 있는 데이터가 있는 곳이고 너무 크지 않게 관리 되어야 한다.
- 캐시 적중 (Cache Hit) : 캐시에 접근했을 때 찾고 있는 데이터가 있는 경우.
- 캐시 누락 (Cache Miss) : 캐시에 접근했을 때 찾고 있는 데이터가 없는 경우.
- 삭제 정책 (Eviction Policy) : 캐시에 공간이 부족할 때 어떻게 공간을 확보하는지에 대한 정책.
어떤 데이터를 얼마나 오래 캐시에 보관할지에 대한 전략을 잘 세워, 적중률을 높이고 누락을 최대한 줄여야 한다.
Cache-Aside
Lazy Loading 이라고도 하며, 데이터를 조회할 때 항상 캐시를 먼저 확인하는 전략이다.
캐시에 데이터가 있으면 캐시에서 데이터를, 없으면 원본에서 데이터를 가져온 뒤 캐시에 저장한다.

- 필요한 데이터만 캐시에 보관.
- 최초 조회할 때 캐시를 확인하기 때문에 최초 요청은 상대적으로 오래 걸림.
- 캐시 데이터가 항상 최신이라는 보장이 없음.
Write-Through
데이터를 작성할 때 항상 캐시에 작성하고, 원본에도 작성하는 전략이다.

- 캐시 데이터가 항상 최신임이 보장된다.
- 자주 사용하지 않는 데이터도 캐시에 중복으로 작성하는 경우가 있을 수 있다.
Write-Behind
캐시에만 데이터를 작성하고, 일정 주기로 원본을 갱신하는 전략이다.

- 쓰기가 잦은 상황에 데이터베이스의 부하를 줄일 수 있다.
- 캐시 데이터에 문제가 발생하면 데이터 소실의 위험성이 존재한다.
SpringBoot에 캐싱 적용하기 실습
- CacheConfig에 Redis를 사용하는 캐싱 매니저 등록해야 함.
일단 그 전에 새로운 프로젝트에 Redis를 연결해야하는데, 로컬에서 Docker로 Redis 를 띄워주었다.
docker run --name redis-local -d -p 6379:6379 redis
@Cacheable() : Cache Aside 전략
- cacheNames : 이 메서드로 인해서 만들어질 캐시를 지칭하는 이름
- key : 캐시 데이터를 구분하기 위해 활용하는 값
@Cacheable(cacheNames = "itemCache", key = "args[0]")
public ItemDto readOne(Long id) {
return itemRepository.findById(id)
.map(ItemDto::fromEntity)
.orElseThrow(() ->
new ResponseStatusException(HttpStatus.NOT_FOUND));
}
이제 직접 postman으로 Get 테스트를 해보자.
처음 id 1, 2, 3의 메서드를 실행했을 경우 실제 메서드 실행 후 db에서 직접 데이터를 가져오지만,
반환값을 캐시에 저장한 뒤 캐시가 삭제되기 전까진 같은 id의 메서드를 실행했을 경우 메서드를 실제로 실행하지 않고 캐시에서 데이터만 반환한다.
즉, 캐싱이 되는 것을 확인할 수 있다.
이 캐싱 전략은 위에서 설명한 Lazy Loading 이라고도 부르는 Cache Aside 전략이다.

@CachePut() : Write Through 전략
데이터를 작성할 때 항상 캐시에 먼저 작성하고, 원본 데이터베이스에도 작성하는 전략이다.

추가로 update가 일어날 때 기존에 있던 캐시를 지우고, 내용을 새롭게 작성하고 싶을 수 있다.
@CacheEvict() : 캐시를 제거함
위 어노테이션을 사용하면 cacheNames 에 지정한 캐시를 삭제한다.
@CachePut(cacheNames = "itemCache", key = "#result.id")
@CacheEvict(cacheNames = "itemAllCache", allEntries = true)
public ItemDto update(Long id, ItemDto dto) {
Item item = itemRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
item.setName(dto.getName());
item.setDescription(dto.getDescription());
item.setPrice(dto.getPrice());
return ItemDto.fromEntity(itemRepository.save(item));
}
검색 결과 캐싱
검색 결과 캐싱 구현 전 Application에 특정 어노테이션을 붙여줘야 한다.
@SpringBootApplication
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)
public class RedisApplication {
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
}
이름을 기준으로 검색을 하는 기능을 구현해보자. 먼저 JPA Query Method를 만들어두자.
public interface ItemRepository extends JpaRepository<Item, Long> {
Page<Item> findAllByNameContains(String name, Pageable pageable);
// ...
}
그리고 해당 메서드를 Service에서 호출한다.
pageNumber와 pageSize를 param으로 받아야 하기 때문에 key에도 설정해주었다.
@Cacheable(cacheNames = "itemSearchCache", key = "{ args[0], args[1].pageNumber, args[1].pageSize }")
public Page<ItemDto> searchByName(String query, Pageable pageable) {
return itemRepository.findAllByNameContains(query, pageable)
.map(ItemDto::fromEntity);
}
Controller는 아래와 같이 작성했다.
@GetMapping("search")
public Page<ItemDto> search(@RequestParam(name = "q") String query, Pageable pageable) {
return itemService.searchByName(query, pageable);
}



'데이터베이스 > DB' 카테고리의 다른 글
| SSH 터널링과 인텔리제이에 적용하는 법 (0) | 2025.05.16 |
|---|---|
| 241224 DB Lock 개념과 실습 TIL (1) | 2024.12.24 |