[Spring] Spring Cache를 활용한 캐싱 전략과 성능 최적화

눈치없어·2025년 2월 3일

캐시(Cache)

캐시(Cache)는 자주 사용하는 데이터를 빠르게 제공하기 위해 메모리(RAM) 등에 저장해두는 기술
일반적으로 DB에서 데이터를 가져오는 것은 비용이 크고 속도가 느리다. 따라서, 캐시를 활용하면 불필요한 DB 접근을 줄이고 성능을 크게 향상시킬 수 있음


Spring Cache

Spring Boot에서는 spring-boot-starter-cache를 사용하여 간단하게 캐싱 기능을 적용할 수 있다.
Spring Cache는 추상화된 캐싱 기능을 제공하여, 여러 가지 캐시 구현체(EhCache, Caffeine, Redis 등)를 쉽게 적용할 수 있도록 돕는다.

핵심 개념: Spring Cache는 DB에서 가져온 데이터를 일정 기간 메모리에 저장하여, 같은 요청이 들어오면 DB를 조회하지 않고 캐시에서 데이터를 제공

- Spring Cache 주요 어노테이션

어노테이션설명
@EnableCachingSpring 캐시 기능 활성화
@Cacheable캐시를 사용할 메서드에 적용. 기존 캐시 데이터가 있으면 DB 조회를 생략
@CachePut캐시를 갱신할 때 사용. 항상 실행 후 새로운 데이터 저장
@CacheEvict캐시 데이터를 삭제할 때 사용
@Caching여러 개의 캐시 어노테이션을 함께 사용할 때 적용

Spring Boot 프로젝트에서 캐시 적용

- 프로젝트 세팅

Spring Cache를 사용하려면 Spring Boot Cache Starter를 추가해야 함

(1) 의존성 추가 (build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'

위에서 Caffeine 캐시 라이브러리도 추가했다. Caffeine은 성능이 뛰어난 JVM 기반 캐싱 라이브러리다.

(2) 캐시 기능 활성화

Spring Boot에서 캐시를 사용하려면 @EnableCaching을 추가해야 함

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {
    // 캐시 설정을 적용하는 설정 파일
}

이제 프로젝트에서 캐시 기능을 사용할 준비 끝


@Cacheable을 활용한 캐싱 적용

Spring Cache를 활용하면 자주 호출되는 메서드의 결과를 캐시에 저장하여 성능을 최적화할 수 있음

- 캐싱 적용 전 (DB 조회 방식)

아래 코드는 캐시를 적용하지 않은 일반적인 DB 조회 코드다.

import org.springframework.stereotype.Service;
import java.util.Optional;

@Service
public class ProductService {
    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public Product getProductById(Long productId) {
        System.out.println("DB 조회 수행: " + productId);
        return productRepository.findById(productId).orElseThrow(() -> new RuntimeException("상품을 찾을 수 없습니다."));
    }
}

문제점
같은 productId로 여러 번 조회하면 DB에 계속 접근해야 함
성능 저하와 DB 부하 증가가 발생

- @Cacheable 적용 후 (캐싱 활용)

이제 @Cacheable을 적용하여 캐시에서 데이터를 가져오도록 수정 해봄

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Cacheable(value = "products", key = "#productId")
    public Product getProductById(Long productId) {
        System.out.println("DB 조회 수행: " + productId);
        return productRepository.findById(productId).orElseThrow(() -> new RuntimeException("상품을 찾을 수 없습니다."));
    }
}

- @Cacheable의 동작 방식

getProductById() 메서드가 호출될 때, 먼저 캐시에서 해당 데이터가 있는지 확인

캐시에 데이터가 있으면, DB를 조회하지 않고 캐시 값을 반환
캐시에 데이터가 없으면, DB에서 조회 후 결과를 캐시에 저장

실행 예제

productService.getProductById(1L); // 첫 번째 호출 -> DB 조회
productService.getProductById(1L); // 두 번째 호출 -> 캐시에서 가져옴 (DB 조회 X)

이제 같은 ID로 여러 번 조회해도 DB 부하가 발생하지 않음


캐시 갱신과 삭제: @CachePut, @CacheEvict 활용

- @CachePut: 캐시 갱신

@CachePut을 사용하면 항상 실행되고, 결과를 캐시에 저장함
업데이트 이후 최신 데이터를 캐시에 반영해야 할 때 사용

@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
    System.out.println("DB 업데이트 수행: " + product.getId());
    return productRepository.save(product);
}

- @CacheEvict: 캐시 삭제

@CacheEvict은 특정 키 또는 전체 캐시를 삭제
데이터가 삭제되거나 변경될 때, 캐시를 삭제하여 최신 데이터를 보장할 수 있음

@CacheEvict(value = "products", key = "#productId")
public void deleteProduct(Long productId) {
    System.out.println("DB 삭제 수행: " + productId);
    productRepository.deleteById(productId);
}

캐시 저장소 선택 (Caffeine vs Redis)

Spring Cache는 다양한 캐시 저장소를 지원

Caffeine: JVM 내에서 동작하는 고성능 캐시 (단일 서버에서 빠름)
Redis: 분산 환경에서 사용 가능한 인메모리 캐시 (대규모 트래픽 대응 가능)

Caffeine 설정 예제 (application.yml)

spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=500,expireAfterWrite=10m

Redis 설정 예제 (application.yml)

spring:
  cache:
    type: redis

서비스 환경에 맞게 캐시 저장소를 선택


결론

Spring Cache를 활용하면 DB 부하를 줄이고, 애플리케이션의 성능을 크게 향상시킬 수 있음

@Cacheable로 DB 조회를 줄이고

@CachePut, @CacheEvict로 데이터 일관성을 유지하며

Caffeine, Redis 같은 캐시 저장소를 적절히 선택하면 됨

이제 실무에서도 캐시를 적극 활용해서 빠르고 효율적인 시스템을 만들어보자.

profile
dock 사이즈 다르잖아

0개의 댓글