oguri's garage

대규모 트래픽을 위한 고성능 캐싱 전략: 1차(L1) & 2차(L2) 캐시 본문

카테고리 없음

대규모 트래픽을 위한 고성능 캐싱 전략: 1차(L1) & 2차(L2) 캐시

oguri 2025. 11. 8. 23:38



개요

게시글 조회나 목록 조회처럼 로직은 단순하지만 많은 사용자가 반복적으로 호출하는 API는 데이터베이스에 큰 부하를 준다. 이런 상황에서 캐싱을 적용하면 응답 속도를 극적으로 개선할 수 있다. 실제로 146ms에서 8ms로, 842ms에서 6ms로 개선된 사례들이 있다.

이번 글에서는 최고의 속도와 확장성을 동시에 확보하기 위한 2-Level 캐싱 전략에 대해 정리한다.






2-Level 캐싱이란?

2-Level 캐싱은 로컬 캐시(L1)분산 캐시(L2)를 조합하는 전략이다. 마치 학생의 책상과 도서관의 관계와 같다.

  • L1 캐시 (책상): 가장 자주 보는 자료를 손만 뻗으면 바로 집을 수 있도록 준비해 두는 곳 (초고속 접근)
  • L2 캐시 (도서관): 책상에 없는 자료를 찾으러 가는 곳. 조금 느리지만 모든 학생이 공유하며, 책상보다 훨씬 많은 자료를 저장할 수 있다 (분산 및 대용량 저장)
  • DB (학교 서고): 도서관에도 없는 자료를 최종적으로 찾는 곳. 가장 느리지만 원본 데이터가 모두 있다

 

 

 




1차 캐시 (L1 Cache): 로컬 캐시

1차 캐시는 애플리케이션 서버 자체의 메모리, 즉 JVM 힙 메모리 내에 위치한다.


주요 특징

구분 설명
대표 도구 Caffeine (Java용 고성능 캐싱 라이브러리)
위치 애플리케이션 인스턴스 내부 (JVM 힙)
속도 초고속 (네트워크 왕복이나 디스크 I/O 없이 getIfPresent() 호출만으로 데이터 반환)
데이터 공유 해당 인스턴스에서만 사용 가능 (분산 공유 불가)



장점

가장 자주 호출되는 핫 데이터(Hot Data)에 대해 최소 지연 시간을 제공하여 다단계 캐시 효과를 극대화한다.



주의사항

JVM 힙에 너무 많은 로컬 캐시를 저장하면 GC(Garbage Collection) 지연이 발생할 수 있다. 이는 메모리 청소 주기마다 응답이 수십~수백 밀리초 멈추는 현상으로 나타날 수 있다.

 

 

 




2차 캐시 (L2 Cache): 분산 캐시

2차 캐시는 애플리케이션 서버 외부에 존재하는 독립적인 캐시 서버다.



주요 특징

구분 설명
대표 도구 Redis (Remote Dictionary Server)
위치 별도의 외부 인메모리 서버
속도 매우 빠름 (메모리 기반, 1ms 미만의 응답 시간)
데이터 공유 여러 애플리케이션 인스턴스 간 데이터 공유 가능



장점

  • JVM 힙 메모리의 부담을 Redis에게 위임
  • 대용량 저장 가능
  • 클러스터링을 통한 확장성 제공
  • 디스크 백업을 통한 지속성 제공



주의사항

Redis 인스턴스가 설정된 메모리 한도를 초과하면 OOM(Out Of Memory) 에러가 발생하여 새로운 데이터 저장이 불가능해질 수 있다. 또한 네트워크 지연도 발생할 수 있다.

 

 

 

 




2-Level 캐싱의 동작 방식

Caffeine(L1)과 Redis(L2)를 함께 사용하는 Composite 전략은 다음과 같이 동작한다.



조회 흐름

요청 → L1 캐시 조회 → (Miss) → L2 캐시 조회 → (Miss) → DB 조회 → L1, L2 저장 → 응답
         ↓ (Hit)              ↓ (Hit)
         응답            L1 저장 → 응답
단계 동작 목적
1단계 L1 캐시 조회 (Caffeine) 가장 빠른 속도로 데이터를 찾아본다
2단계 L2 캐시 조회 (Redis) L1에서 미스가 발생하면 네트워크를 통해 L2 캐시를 확인한다
3단계 L1 저장 및 반환 L2에서 히트하면 데이터를 반환함과 동시에 L1에도 저장한다 (Write-Through)
4단계 DB 조회 및 저장 L2에서도 미스하면 DB에서 데이터를 불러와 L1과 L2 두 곳 모두에 저장한다



Composite 전략의 이점


다단계 캐시 효과 극대화

  • 트래픽이 빈번한 페이지 요청에서 로컬 캐시(L1)의 초고속 조회 성능을 활용한다


장애 방지 (Fault Tolerance)

  • Redis 서버(L2)에 장애가 발생하더라도 로컬 캐시(L1)에 남아 있는 데이터를 이용해 어느 정도 임시 서빙이 가능하다

 

 

 




운영 시 고려사항

캐싱은 성능 최적화의 강력한 도구이지만, 잘못 설계하면 오히려 시스템에 부정적인 영향을 줄 수 있다.



1. GC 지연 및 OOM 조율

JVM 힙(L1)과 Redis 메모리(L2) 사용량을 적절히 조율하여 GC 딜레이나 Redis OOM 같은 부정적 영향을 피해야 한다.


권장사항:

  • L1 캐시 크기는 JVM 힙의 10-20% 이내로 제한
  • Redis 메모리는 maxmemory-policy 설정으로 관리 (예: allkeys-lru)



2. TTL 설정 및 Staleness

캐시의 만료 정책(TTL)이 너무 길면 캐시 데이터가 최신 데이터와 차이가 나는 'staleness' 상태가 될 위험이 있다. 반대로 너무 짧으면 캐시 미스율이 올라간다.


권장사항:

  • 데이터 특성에 맞는 TTL 설정 (자주 변경되는 데이터는 짧게, 정적 데이터는 길게)
  • 중요 데이터는 업데이트 시 명시적으로 캐시 무효화(Cache Invalidation) 수행



3. 모니터링

캐시 계층이 복잡해질수록 모니터링이 필수적이다.


주요 메트릭:

  • cache_hits: 캐시 히트 횟수
  • cache_misses: 캐시 미스 횟수
  • evictions: 캐시 제거(Eviction) 횟수
  • hit_rate: 캐시 히트율 (hits / (hits + misses))


목표:

  • L1 캐시 히트율: 80% 이상
  • L2 캐시 히트율: 95% 이상

 

 

 




실전 구현 예시

@Configuration
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // L1: Caffeine 캐시 설정
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(10_000)
            .expireAfterWrite(Duration.ofMinutes(5))
            .recordStats());

        // L2: Redis 캐시 설정
        RedisCacheConfiguration redisCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(
                    new GenericJackson2JsonRedisSerializer()
                )
            );

        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
            .cacheDefaults(redisCacheConfig)
            .build();

        // L1 + L2 Composite 캐시 매니저
        return new CompositeCacheManager(caffeineCacheManager, redisCacheManager);
    }
}

 

 

 

 




마치며

2-Level 캐싱 전략은 대규모 트래픽 환경에서 성능과 안정성을 동시에 확보할 수 있는 효과적인 방법이다. 하지만 단순히 도입하는 것만으로는 부족하고, 적절한 모니터링과 튜닝이 반드시 필요하다.

핵심은 L1의 초고속 성능L2의 확장성 및 공유성을 적절히 조합하여, 각 계층이 서로의 단점을 보완하도록 설계하는 것이다.


참고 자료