BackEND/Java

프로젝트에서 Redis 캐시 최적화 방법

mingmingIT 2025. 3. 30. 10:28

이번 포스팅에서는 Redis 캐시를 실제 프로젝트에서 효과적으로 최적화하는 방법을 작성해 보겠습니다.

단순히 캐시를 적용하는 것뿐만 아니라, 성능을 극대화하고 안정성을 확보하는 전략을 설명합니다.


1. 캐시 키(Key) 설계 전략

일관된 키 네이밍 규칙 적용

Redis는 Key-Value 구조이므로 효율적인 키 네이밍이 중요합니다.
잘못된 키 설계는 데이터 충돌 및 캐시 정리를 어렵게 만듭니다.

올바른 키 설계 예제

user:123:profile       → 특정 사용자 프로필
product:456:detail     → 특정 상품 정보
order:789:items        → 특정 주문의 상품 목록

✅ 좋은 키 설계 원칙

  • :(콜론)을 사용하여 계층 구조를 표현
  • 키의 의미를 명확하게 작성
  • 너무 긴 키는 피할 것 (최대 512바이트 이하)

2. 데이터 직렬화(Serialization) 최적화

Spring Boot에서 Redis를 사용할 때 객체를 캐싱할 경우,
데이터 직렬화(Serialization) 방식이 성능에 영향을 미칩니다.

직렬화 성능 비교

직렬화 방식 속도 데이터 크기 특징
JDK 기본 직렬화 느림 Java 객체만 지원
JSON 보통 중간 가독성이 좋음
MessagePack 빠름 작음 이진 포맷, 성능 최적화 가능
Kryo 매우 빠름 매우 작음 가장 효율적

Kryo 직렬화 적용 예제

import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import java.time.Duration;

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10))  // 10분 TTL 설정
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

    return RedisCacheManager.builder(redisConnectionFactory)
        .cacheDefaults(config)
        .build();
}

Kryo 또는 MessagePack을 사용하면 속도가 빨라지고 메모리 사용량이 줄어듭니다.


3. TTL(Time-To-Live) 설정 및 자동 만료 관리

TTL(Time-To-Live)은 캐시 데이터의 유효기간을 설정하는 기능입니다.
적절한 TTL 설정은 불필요한 메모리 사용을 방지하고 성능을 높입니다.

TTL 설정 전략

데이터 유형 TTL 설정 예시
사용자 프로필 24시간
상품 상세 정보 1시간
실시간 인기 상품 10분
이벤트 정보 이벤트 종료 시각

TTL 설정 코드 예제

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(30)); // 기본 TTL 30분 설정

    return RedisCacheManager.builder(redisConnectionFactory)
        .cacheDefaults(config)
        .build();
}

TTL을 너무 길게 설정하면 오래된 데이터 유지 문제,
너무 짧게 설정하면 캐시 적중률(Cache Hit Rate) 저하 문제가 발생할 수 있습니다.


4. 캐시 갱신(Cache Update) 및 무효화(Cache Eviction) 전략

캐시 일관성 유지 전략

전략 설명
Cache Aside DB 조회 후 Redis에 저장
Write Through DB와 Redis 동시에 저장
Read Through Redis에 없으면 DB에서 가져온 후 Redis에 저장
Write Behind Redis에 먼저 저장 후 비동기적으로 DB 저장

Cache Aside 패턴 코드 예제

@Cacheable(value = "product", key = "#id")
public Product getProductById(Long id) {
    return productRepository.findById(id).orElseThrow();
}

캐시 삭제(@CacheEvict) 예제

@CacheEvict(value = "product", key = "#id")
public void updateProduct(Long id, Product updatedProduct) {
    productRepository.save(updatedProduct);
}

이렇게 하면 DB 업데이트 시 캐시가 자동으로 삭제되어 최신 데이터 유지가 가능합니다.


5. Redis 데이터 구조 활용 및 메모리 사용 최적화

Redis는 다양한 데이터 구조를 지원합니다.
각 데이터 구조를 잘 활용하면 메모리를 절약하고 성능을 최적화할 수 있습니다.

Redis 데이터 구조 활용 전략

데이터 구조 활용 예제
String 일반적인 Key-Value 저장
Hash 사용자 프로필, JSON 데이터 저장
List 최근 방문 기록, 채팅 메시지 저장
Set 태그, 추천 상품 목록 저장
Sorted Set 인기 상품 랭킹, 점수 기반 데이터

Hash 자료구조를 활용한 캐싱 예제

redisTemplate.opsForHash().put("user:123", "name", "홍길동");
redisTemplate.opsForHash().put("user:123", "age", "30");

List를 활용한 최근 방문 기록 저장 예제

redisTemplate.opsForList().leftPush("recent:users", "user123");
redisTemplate.opsForList().trim("recent:users", 0, 9); // 최근 10개만 유지

메모리 절약을 위해 String보다 Hash를 사용하고,
필요 없는 데이터는 trim, TTL을 활용하여 정리해야 합니다.


6. Redis 클러스터(Cluster) 및 샤딩(Sharding) 적용

실제 서비스에서는 단일 Redis 인스턴스만으로는 한계가 있을 수 있습니다.
이때 **Redis 클러스터(Cluster) 및 샤딩(Sharding)**을 활용하여 성능을 개선할 수 있습니다.

Redis 클러스터 적용 시 이점

  • 데이터 분산 저장으로 부하 분산
  • 여러 개의 노드가 자동으로 데이터를 복제
  • 특정 노드 장애 시 자동 복구 가능

Spring Boot에서 Redis 클러스터 설정 예제

spring:
  redis:
    cluster:
      nodes: 192.168.1.1:6379,192.168.1.2:6379,192.168.1.3:6379

이렇게 하면 여러 Redis 서버를 하나의 클러스터로 연결하여 대규모 트래픽을 처리할 수 있습니다.


7. Redisson을 활용한 분산 락과 캐시 동기화

멀티 인스턴스 환경에서는 동시에 같은 캐시를 수정하는 문제가 발생할 수 있습니다.
이를 해결하기 위해 Redisson을 활용한 분산 락을 적용할 수 있습니다.

Redisson 분산 락 예제

RLock lock = redissonClient.getLock("lock:product:123");
try {
    if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
        // 안전하게 데이터 수정 가능
    }
} finally {
    lock.unlock();
}

이렇게 하면 동시에 여러 서버에서 같은 데이터를 수정하는 문제를 방지할 수 있습니다.


결론

  • 올바른 키 네이밍TTL 관리를 통해 효율적인 캐시 운영
  • **직렬화 방식 개선(Kryo, MessagePack)**으로 성능 최적화
  • 캐시 일관성 유지 전략 적용
  • Redis 클러스터 및 Redisson 활용으로 확장성과 안정성 확보

이제 최적화된 Redis 캐시 설계로 성능을 높여보세요!