[SB 3기] 코드잇 스프린트 위클리페이퍼 16주차

JHLee·2025년 8월 24일
post-thumbnail

Q1. Spring Cache에서 @Cacheable, @CachePut, @CacheEvict의 차이점과 각각을 어떤 상황에서 사용하는 것이 적절한지 설명해주세요.


✅ Spring Cache란?

  • Spring Cache는 반복적인 DB 조회나 계산 비용이 큰 메서드 실행 결과를 캐시에 저장하고 재활용할 수 있도록 도와주는 캐시 추상화 레이어이다.
  • 어노테이션을 통해 선언적으로 캐시를 관리할 수 있다.
  • 사용 전 @EnableCaching 설정으로 캐시 기능을 활성화해야 한다.
  • 주요 어노테이션으로는 @Cacheable, @CachePut, @CacheEvict가 있다.

💡 Tip

  • Spring Cache는 AOP 프록시 기반이라 같은 클래스 내에서 자기 자신 메서드를 호출할 땐 적용되지 않는다. → 자기 자신을 주입받아 호출하거나, 캐시 적용 메서드를 별도 Bean으로 분리하여 해결
  • Spring Cache는 TTL, 만료 정책, 최대 크기 설정을 직접 제공하지 않는다. → 구현체(Caffeine/Redis 등)에서 설정해야 함

✅ @Cacheable

  • 캐시 조회 후 히트되는게 없으면 메서드를 실행한다. -> 메서드 실행 결과를 캐시에 저장한다.
  • 캐시가 히트되면 메서드를 실행하지 않고 캐시 값을 반환한다.
  • 특징
    • 조건부 캐싱 지원 (condition, unless)
    • 캐시 키 커스터마이징 가능 (key 속성에 SpEL 활용)
    • null 결과는 캐싱하지 않도록 설정 가능 (unless = "#result == null")
@Service
public class MenuService {

    @Cacheable(value = "menus", key = "#id", unless = "#result == null")
    public Menu getMenu(Long id) {
        return menuRepository.findById(id)
            .orElseThrow(() -> new MenuNotFoundException(id));
    }
}

✅ @CachePut

  • 항상 메서드를 실행하고 결과를 캐시에 갱신(덮어쓰기)한다.
  • 조회시 사용한 @Cacheable과 키를 동일하게 지정해야 한다. (캐시 일관성 유지)
  • 기본적으로 트랜잭션이 커밋 후 반영된다.
@Service
public class MenuService {

    @CachePut(value = "menus", key = "#menu.id")
    public Menu updateMenu(Menu menu) {
        Menu updatedMenu = menuRepository.save(menu);
        return updatedMenu;
    }
}

✅ @CacheEvict

  • 특정 캐시 항목을 제거하거나 전체 캐시 무효화 역할을 한다.
  • 기본적으로 트랜잭션이 커밋 후 반영된다.
  • 주요 속성
    • allEntries = true: 캐시 전체 삭제
    • beforeInvocation: 메서드 실행 전 캐시 제거 여부로, true로 설정하면 예외 발생 시에도 캐시가 제거됨 (기본값 : false)
// 특정 캐시 항목 제거
@CacheEvict(value = "menus", key = "#id")
@Transactional
public void deleteMenu(Long id) {
    menuRepository.deleteById(id);
}

// 모든 캐시 항목 제거
@CacheEvict(value = "menus", allEntries = true)
@Transactional
public void clearAllMenuCache() {
    menuRepository.bulkUpdateStatus();
}

// 예외가 발생해도 캐시 무효화
@CacheEvict(value = "menus", allEntries = true, beforeInvocation = true)
public void clearAllMenuCacheForce() {
	...
}

🆚 차이점

어노테이션메서드 실행캐시 동작
@Cacheable캐시 히트 시 생략캐시 미스 시 저장
@CachePut항상 실행항상 캐시를 갱신(덮어쓰기)
@CacheEvict실행 여부와 무관특정 키 또는 전체 캐시 제거

📌 활용 예시

  • @Cacheable : 읽기 비중이 크고 자주 변경되지 않는 데이터
    - ex. 상품 상세 보기, 설정 값 조회
  • @CachePut : 최신 상태를 즉시 캐시에 반영해야 하는 수정/생성 작업
    - ex. 상품 수정, 신규 등록 시 캐시 갱신
  • @CacheEvict : 데이터 삭제/벌크 변경 후 캐시 무효화
    - ex. 상품 삭제, 배치로 대량 상태 변경 후 캐시 초기화

💡 @Caching : 한 메서드에서 여러 캐시 동작을 조합하여 사용할 수 있다.

@Caching(
    put = { @CachePut(value = "menus", key = "#menu.id") },
    evict = { @CacheEvict(value = "menuList", allEntries = true) }
)
public Menu updateAndInvalidateList(Menu menu) {
    return menuRepository.save(menu);
}

Q2. 로컬 캐시와 분산 캐시의 개념 차이와 각각의 장단점, 그리고 실무에서 어떤 기준으로 선택해야 하는지 설명해주세요.


✅ 캐시(Cache)란?

  • 자주 사용하는 데이터를 빠르게 가져오기 위해 임시 저장소에 저장하는 기술이다.
  • 핵심 원리
    • 시간 지역성(Temporal Locality) : 최근 접근한 데이터는 다시 접근될 가능성이 높다.
    • 공간 지역성(Spatial Locality) : 접근한 데이터 근처의 데이터도 접근될 가능성이 높다.

✅ 로컬 캐시 (Local Cache)

  • 애플리케이션과 같은 JVM 메모리 공간에서 동작하는 캐시이다.
  • 대표적으로 Caffeine, Guava 등이 있다.
  • 특징
    • 네트워크를 거치지 않고 메모리에 직접 접근 -> 가장 빠름
    • 네트워크 장애와 무관하게 동작
    • JVM 힙 메모리에 의존 -> 메모리 크기 제약
    • 다중 서버 환경에서 서버 간 캐시 불일치 발생
    • 애플리케이션 재시작 시 캐시 데이터 손실

✅ 분산 캐시 (Distributed Cache)

  • 별도의 캐시 서버에 데이터를 저장하고 여러 서버 인스턴스가 공유한다.
  • 대표적으로 Redis, Memcached 등이 있다.
    • Redis는 영속화 옵션 제공 -> 보조 데이터 저장소로 활용 가능
  • 특징
    • 네트워크를 통해 접근 -> 로컬 캐시보다 상대적으로 느림
    • 여러 서버가 동일한 데이터 공유 -> 다중 서버 환경에 적합
    • 캐시 서버는 독립적으로 확장/클러스터링 가능
    • 네트워크 지연추가 인프라 비용 발생

💡 클러스터링(Clustering) : 여러 캐시 서버를 하나의 클러스터(집합)로 묶어 부하 분산과 고가용성을 제공하는 기술


🆚 장단점

구분로컬 캐시(Local Cache)분산 캐시(Distributed Cache)
속도매우 빠름상대적으로 느림
구현 난이도단순 (라이브러리 추가)별도 캐시 서버 구축 및 운영 필요
일관성서버별 불일치 발생 가능여러 서버가 데이터 공유 -> 일관성 ↑
확장성서버 인스턴스마다 캐시 중복캐시 서버 확장/클러스터링으로 처리량 증가
장애 대응앱 재시작 시 데이터 손실복제/클러스터링으로 장애 대응 가능
비용별도 인프라 불필요추가 인프라 운영 비용 발생

💡 실무 선택 기준

  • 로컬 캐시가 적합한 경우
    • 단일 서버 / 소규모 애플리케이션
    • 읽기 중심, 데이터 변경이 드문 경우
    • TTL 짧은 데이터 (ex. 1~2초 주기)
    • 네트워크 비용/복잡도를 줄이고 싶은 경우
  • 분산 캐시가 적합한 경우
    • 다중 서버 / 대규모 트래픽 환경
    • 쓰기/갱신 작업이 빈번하고, 데이터 일관성이 중요한 경우
    • 고가용성, 확장성이 요구되는 클라우드 환경

💡 혼합 전략 (다중 레벨 캐시, L1 + L2)

  • L1(로컬) : 애플리케이션 내부 초고속 응답
  • L2(분산) : 서버 간 데이터 일관성 확보
  • ex. Spring Boot : Caffeine(로컬, L1) + Redis(분산, L2)
  • 실무에서는 혼합전략을 가장 많이 활용함

📄 참고 문서

profile
개발자로 성장하기

0개의 댓글