ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 시리즈 16편 – 고급 캐시 전략: 멀티 레벨 캐시(L1+L2)와 데이터 일관성 관리
    개발/Spring Boot 2025. 4. 27. 15:30
    Spring Boot에서 멀티 레벨 캐시(L1+L2) 전략을 구현하고, 데이터 일관성 문제를 해결하는 방법을 설명합니다. 실전 아키텍처와 보완 설계까지 포함했습니다.

     


     

    Spring Boot 시리즈 16편 – 고급 캐시 전략: 멀티 레벨 캐시(L1+L2)와 데이터 일관성 관리

     

    API 응답 속도를 높이고, 서버 부하를 줄이는 데에 **캐시(Cache)**는 필수적인 존재입니다.

    하지만 단순한 캐시 적용만으로는 변경된 데이터가 반영되지 않거나,

    동시성 문제로 인해 잘못된 정보가 노출되는 문제를 막을 수 없습니다.

     

    이번 글에서는 Spring Boot 환경에서 멀티 레벨 캐시를 구축하고,

    속도 + 일관성 + 복원력을 동시에 확보하는 방법을 실무 사례 기반으로 설명합니다.

     


     

    📌 1. 기본 캐시 구조와 한계

    항목설명

    1단계(L1) 로컬 메모리(Heap) 캐시 (예: Caffeine)
    2단계(L2) 분산 캐시(예: Redis)
    한계점 변경/갱신 시점 문제, 캐시-DB 간 불일치 발생 가능

     

     


     

    ✅ 2. 멀티 레벨 캐시(Multi-Level Cache) 개념

     

     

    ✅ 기본 구성

    [요청]
      ↓
    [L1 캐시] (Heap 메모리) → MISS → [L2 캐시] (Redis) → MISS → [DB 조회]
      ↓                                    ↓
    응답 저장 (L1 + L2 업데이트)

     

    • 1차 캐시(L1): 초고속 응답 (Heap 메모리, 단일 서버 범위)
    • 2차 캐시(L2): 분산 시스템에서 데이터 공유 (Redis 등)

     


     

    🛠️ 3. Spring Boot 환경에서 멀티 레벨 캐시 구현

     

     

    1️⃣ 의존성 추가 (Gradle)

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

     

     


     

    2️⃣ 캐시 설정 (application.yml)

    spring:
      cache:
        type: caffeine
      redis:
        host: localhost
        port: 6379

     

    • type: caffeine 설정은 기본 CacheManager를 Caffeine으로 설정
    • Redis 연동은 별도로 구성 (L2 캐시 용도)

     


     

    3️⃣ L1: Caffeine 캐시 설정

    @Configuration
    public class CacheConfig {
    
        @Bean
        public CaffeineCacheManager caffeineCacheManager() {
            CaffeineCacheManager manager = new CaffeineCacheManager();
            manager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(5000));
            return manager;
        }
    }

     

    • L1 캐시: 읽기 속도 극대화, JVM 메모리 기반

     


     

    4️⃣ L2: Redis 캐시 설정

    @Configuration
    @EnableCaching
    public class RedisCacheConfig {
    
        @Bean
        public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30)) // L2는 더 긴 TTL 가능
                .disableCachingNullValues();
    
            return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .build();
        }
    }

     

     


     

    🔁 4. 멀티 레벨 캐시 연동 전략

     

    Spring Boot 기본 구조만으로는 L1과 L2를 자동으로 병합하지 못합니다.

    Custom CacheManager를 만들어 L1(Caffeine) → L2(Redis) 순서로 조회하도록 조정해야 합니다.

     


     

    1️⃣ 커스텀 MultiCacheManager 작성

    public class MultiCacheManager implements CacheManager {
    
        private final CacheManager caffeineCacheManager;
        private final CacheManager redisCacheManager;
    
        public MultiCacheManager(CacheManager caffeine, CacheManager redis) {
            this.caffeineCacheManager = caffeine;
            this.redisCacheManager = redis;
        }
    
        @Override
        public Cache getCache(String name) {
            Cache caffeineCache = caffeineCacheManager.getCache(name);
            Cache redisCache = redisCacheManager.getCache(name);
            return new MultiLevelCache(caffeineCache, redisCache);
        }
    }

     

     


     

    2️⃣ MultiLevelCache 작성

    public class MultiLevelCache implements Cache {
    
        private final Cache l1;
        private final Cache l2;
    
        public MultiLevelCache(Cache l1, Cache l2) {
            this.l1 = l1;
            this.l2 = l2;
        }
    
        @Override
        public ValueWrapper get(Object key) {
            ValueWrapper value = l1.get(key);
            if (value == null) {
                value = l2.get(key);
                if (value != null) {
                    l1.put(key, value.get());
                }
            }
            return value;
        }
    
        @Override
        public void put(Object key, Object value) {
            l1.put(key, value);
            l2.put(key, value);
        }
    
        @Override
        public void evict(Object key) {
            l1.evict(key);
            l2.evict(key);
        }
    
        @Override
        public void clear() {
            l1.clear();
            l2.clear();
        }
    }

     

    • 조회 시 → L1 → L2 순서로 조회
    • 저장 시 → L1 + L2 모두 저장
    • 삭제 시 → L1 + L2 모두 삭제

     


     

    🧠 실무 캐시 일관성 관리 전략

    항목전략

    TTL 관리 L1은 짧게 (10분), L2는 길게 (30분) 설정
    변경 시점 동기화 데이터 변경 API에서는 반드시 캐시 삭제/갱신
    대량 갱신 대응 pub/sub or cache invalidate 기능 활용
    장애 대비 L2(Redis) 장애 시에도 L1만으로 임시 대응 가능성 확보
    업데이트 알림 L2 업데이트 시 L1에 invalidation push (Advanced)

     

     


     

    ✅ 마무리 요약

    항목정리

    기본 구조 L1(Caffeine) + L2(Redis) 멀티 레벨 구성
    읽기 흐름 L1 → L2 → DB 순서 조회
    쓰기 흐름 L1 + L2 모두 동시 저장
    실무 주의 데이터 일관성, TTL 관리, 장애 복원력 고려 필수
    고급 설계 Redis pub/sub 기반 캐시 동기화 가능

     

     


     

    📌 다음 편 예고

     

    Spring Boot 시리즈 17편: 트랜잭션 관리 고급 전략 – 선언적, 프로그래밍 방식, 전파/고립 수준까지 완벽 정리

     

Designed by Tistory.