grep

Backend

Spring Data Redis: Repository vs RedisTemplate — 실전 성능 비교

Philip Park여기어때

2025년 12월 9일

원문에서 보기 ↗

글. 박성진(Philip) / 전시개발팀

들어가며: Redis Repository 프로덕션 장애에서 배운 교훈

안녕하세요 여기어때 전시 개발 팀에서 백엔드를 담당하고 있는 필립입니다.

전시 개발 팀에서는 Redis 를 운영하는 과정에서 두 번의 심각한 장애를 겪었습니다. 이번 시리즈에서는 그 중 하나인 Redis CPU 100% 이슈를 해결한 경험을 공유하려 합니다.

피크 타임마다 Redis CPU 사용률이 100%에 도달해 서비스 응답이 느려지고 타임아웃이 발생했던 문제를 어떻게 분석하고 해결했는지 실제 코드와 모니터링 데이터를 바탕으로 살펴보겠습니다.

(현재): Repository vs RedisTemplate — Redis CPU 100% 장애 해결기

발생한 문제

**피크 타임(오후 9시~12시)**마다 Redis CPU가 계속해서 85%를 이상을 찍다가 7월 성수기에 100%을 찍었고, 그 결과 전체 서비스 응답 시간이 평소보다 3~5배까지 증가했습니다. 심지어 일부 요청에서는 타임아웃 에러까지 발생하는 상황이었습니다.

모니터링 결과:

원인 분석

해당 API 에서는 성능 향상을 위해 Redis 캐시를 도입했고, **Spring Data Redis의 CrudRepository (@RedisHash)**를 사용했습니다. 초기엔 문제가 없었지만 시간이 지남에 따라 다음과 같은 변화가 있었습니다:

특히 Spring Data Redis의 CrudRepository는 객체를 Hash 자료구조로 변환해 저장하며, 보조 인덱스용 Set도 관리합니다.

데이터가 복잡해질수록 한 번의 저장에 매우 많은 필드가 HMSET으로 전송되고, 부가로 SADD 등의 명령이 추가됩니다. 그 결과 Redis 엔진이 처리해야 할 작업량이 폭증해 CPU가 포화된 것이 근본 원인이었습니다.

해결 방향

CrudRepository에서 RedisTemplate로 전환한 결과:

핵심 결론 먼저 보기

Repository 방식:

RedisTemplate 방식:

이 글에서는 실제 프로덕션 코드와 Redis Monitor 로그를 통해 두 방식의 차이를 명확히 보여드립니다.

1. 도메인 모델 정의

숙박 상품 PDP 데이터를 Redis에 캐싱한다고 가정합니다.

실제 프로덕션에서는 객체 깊이와 필드 수가 훨씬 많습니다. 여기서는 이해를 돕기 위해 숙소 제휴점 정보 + 객실 최저가 정도로 간소화한 예제입니다.

StayPdp 모델

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
@RedisHash("StayPdp")
public record StayPdp(
    @Id Long placeId,
    String placeName,
    Double reviewRate,
    Integer reviewCount,
    PlaceLowestProductPrice placeLowestProductPrice
) {}
public record PlaceLowestProductPrice(
    StayLowestProductPrice stayLowestProductPrice,
    boolean shortStay,
    boolean overnight
) {}
public record StayLowestProductPrice(
    int discountPrice,
    long roomId,
    boolean haveEliteDiscountPolicy
) {}

중첩된 구조의 도메인 객체 — 실제 서비스에서 흔히 볼 수 있는 형태입니다.

실제 프로덕션 객체는 훨씬 더 복잡했습니다..

실제로 캐싱하던 객체는 다음과 같은 구조였습니다:

StayPdp (숙박 상품 상세)
├─ 기본 정보 (id, name, category 등): 10개 필드
├─ roomGroups (14개 객실 그룹): 
│   └─ 각 그룹당:
│       ├─ 기본 정보: 10개
│       ├─ stayInfo: 20개
│       ├─ additionalInfo: 15개
│       ├─ facilityInfo: 10개
│       └─ bedInfoList (14종류): 42개
│       → 그룹당 약 100개 필드
│   → 14개 × 100 = 1,400개 필드
├─ filterRoomGroups (20개): 100개 필드
└─ 기타 (placeUseTime 등): 100개 필드
최대 Hash 필드 개수: 약 1,600~2,000개

Repository로 이 객체를 저장하면:

# HMSET 명령어에 1,600~2,000개 필드를 한번에 전송
HMSET "StayPdp:72244"
  "_class" "com.example.StayPdp"
  "id" "72244"
  "roomGroups[0].roomId" "454425"
  "roomGroups[0].roomName" "[20시 레이트 체크인] 랜덤 배정"
  "roomGroups[0].stayInfo.checkInHour" "20"
  "roomGroups[0].stayInfo.checkOutHour" "12"
  "roomGroups[0].stayInfo.strikePrice" "40000"
  "roomGroups[0].stayInfo.discountPrice" "40000"
  "roomGroups[0].facilityInfo.bedInfoList[0].type" "1"
  "roomGroups[0].facilityInfo.bedInfoList[0].count" "0"
  "roomGroups[0].facilityInfo.bedInfoList[1].type" "2"
  ... (1,600개 필드 계속)
  
# 그 다음 SADD
SADD "StayPdp" "72244"

위 명령어들은 단일 객체 저장 시 실행되는 것입니다. 실제 운영 환경에서 피크 타임에 초당 수백 건의 캐시 저장이 발생하면서, Redis CPU가 100%까지 치솟았던 이유를 알 수 있었습니다.

2. Repository 방식 구현

Repository 인터페이스

import com.example.rediscompare.model.StayPdp;
import org.springframework.data.repository.CrudRepository;
public interface StayPdpRepository extends CrudRepository<StayPdp, Long> {}

코드는 간결하지만, 내부적으로는 복잡한 작업이 수행됩니다.

실제 Redis Monitor 로그

1765191739.585064 [0 127.0.0.1:63700] "HMSET" "StayPdp:88146" 
  "_class" "com.example.demo.rediscompare.model.StayPdp" 
  "placeId" "88146" 
  "placeLowestProductPrice.overnight" "1"
  "placeLowestProductPrice.shortStay" "0"
  "placeLowestProductPrice.stayLowestProductPrice.discountPrice" "16290" 
  "placeLowestProductPrice.stayLowestProductPrice.haveEliteDiscountPolicy" "0"
  "placeLowestProductPrice.stayLowestProductPrice.roomId" "752543" 
  "placeName" "\xed\x99\x8d\xeb\x8c\x80..."
  "reviewCount" "26" 
  "reviewRate" "90.0"
1765191739.588857 [0 127.0.0.1:63700] "SADD" "StayPdp" "88146"

위는 간단한 예제 객체 기준입니다.

실제 프로덕션 객체(roomGroups 14개 포함)는:

HMSET "StayPdp:72244"
  "_class" "..."
  "id" "72244"
  "categoryTypeValue" "HOTEL"
  "roomGroups[0].objectId" "m_room_group_454425"
  "roomGroups[0].roomId" "454425"
  "roomGroups[0].roomName" "[20시 레이트 체크인] 랜덤 배정"
  "roomGroups[0].roomServiceType" "1"
  "roomGroups[0].stayInfo.checkInHour" "20"
  "roomGroups[0].stayInfo.checkOutHour" "12"
  "roomGroups[0].stayInfo.strikePrice" "40000"
  "roomGroups[0].stayInfo.discountPrice" "40000"
  "roomGroups[0].stayInfo.stockCount" "3"
  "roomGroups[0].additionalInfo.basePersonalCount" "2"
  "roomGroups[0].additionalInfo.maxPersonalCount" "2"
  "roomGroups[0].facilityInfo.bedInfoList[0].type" "1"
  "roomGroups[0].facilityInfo.bedInfoList[0].count" "0"
  "roomGroups[0].facilityInfo.bedInfoList[1].type" "2"
  "roomGroups[0].facilityInfo.bedInfoList[1].count" "1"
  ... (계속해서 1,600개 필드)
  "roomGroups[13].facilityInfo.bedInfoList[13].count" "0"
  "filterRoomGroups[0].roomGroupId" "454437"
  ... (계속)

→ 단 1개의 HMSET 명령에 1,600~2,000개 필드

한 번의 save() 호출에 최소 2개 이상의 Redis 명령이 실행됩니다:

  1. HMSET — 1,600개 필드 저장 (상당한 데이터 전송량)
  2. SADD — 키 목록에 추가

@Indexed 필드를 사용하면 추가 명령이 더 발생합니다.

3. RedisTemplate 방식 구현

Cache 클래스

import com.example.rediscompare.model.StayPdp;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import java.time.Duration;
@Repository
@RequiredArgsConstructor
public class StayPdpCache {
    private final RedisTemplate<String, StayPdp> redisTemplate;
    public void save(StayPdp pdp) {
        redisTemplate.opsForValue().set(
            "staypdp:" + pdp.placeId(), 
            pdp, 
            Duration.ofMinutes(10)
        );
    }
    public StayPdp get(Long id) {
        return redisTemplate.opsForValue().get("staypdp:" + id);
    }
}

실제 Redis Monitor 로그

1765191789.358857 [0 127.0.0.1:63700] "SET" "staypdp:88146" 
  "{\"placeId\":88146,
    \"placeName\":\"홍대...\",
    \"reviewRate\":90.0,
    \"reviewCount\":26,
    \"placeLowestProductPrice\":{
      \"stayLowestProductPrice\":{
        \"discountPrice\":16290,
        \"roomId\":752543,
        \"haveEliteDiscountPolicy\":false
      },
      \"shortStay\":false,
      \"overnight\":true
    }
  }" 
  "EX" "600"

단 1개의 SET 명령으로 저장과 TTL 설정이 동시에 처리됩니다.

4. 성능 비교: 실제로 얼마나 차이날까?

두 방식의 성능 차이를 확인하기 위해 간단한 JMH 벤치마크 테스트를 작성했습니다.

Redis 연산은 기본적으로 빠르지만, Spring Data Redis Repository는 내부적으로 보조 인덱스 관리, Hash 변환, Reflection 기반 직렬화 등 여러 추가 작업을 수행하기 때문에 실제로 어느 정도의 오버헤드가 발생하는지 수치로 검증해보고자 했습니다.

이번 벤치마크에서는 다음과 같은 세 가지 시나리오를 측정했습니다.

✔ 1) 저장(Save)

객체 하나를 Redis에 저장하는 연산을 측정했습니다.

Repository는 Hash 저장 + Set 인덱스 업데이트가 추가로 발생하고,

RedisTemplate은 단순 set(key, value) 방식으로 저장합니다.

✔ 2) 조회(Get)

Redis에서 key로 객체를 읽어오는 연산을 측정했습니다.

Repository는 역직렬화 후 매핑 과정이 추가되며,

RedisTemplate은 JSON 역직렬화만 수행합니다.

✔ 3) 저장 + 조회(Save + Get)

실제 서비스 캐싱에서 발생하는 패턴이라 두 연산을 연달아 수행하는 비용도 함께 측정했습니다.

두 방식의 오버헤드 차이가 누적될 때 어떤 차이가 나타나는지 판단하기 위한 항목입니다.

JMH 벤치마크 설정

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 1, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 3, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Fork(1)
public class RedisBenchmark {

    private ConfigurableApplicationContext context;
    private StayPdpRepository repository;
    private StayPdpCache cache;
    
    private StayPdp testData;

    @Setup(Level.Trial)
    public void setup() {
        // Spring Boot 애플리케이션 컨텍스트 시작
        context = SpringApplication.run(DemoApplication.class, 
            "--spring.profiles.active=test",
            "--spring.data.redis.host=localhost",
            "--spring.data.redis.port=6379");
        
        repository = context.getBean(StayPdpRepository.class);
        cache = context.getBean(StayPdpCache.class);
        
        // 테스트 데이터 생성
        testData = new StayPdp(
            999L,
            "벤치마크 테스트 호텔",
            4.5,
            100,
            new PlaceLowestProductPrice(
                new StayLowestProductPrice(89000, 12345L, false),
                false,
                true
            )
        );
    }

    @TearDown(Level.Trial)
    public void tearDown() {
        if (context != null) {
            context.close();
        }
    }

    @Benchmark
    public void saveViaRepository() {
        repository.save(testData);
    }

    @Benchmark
    public void saveViaTemplate() {
        cache.save(testData);
    }

    @Benchmark
    public StayPdp getViaRepository() {
        return repository.findById(999L).orElse(null);
    }

    @Benchmark
    public StayPdp getViaTemplate() {
        return cache.get(999L);
    }

    @Benchmark
    public void saveAndGetViaRepository() {
        repository.save(testData);
        repository.findById(999L);
    }

    @Benchmark
    public void saveAndGetViaTemplate() {
        cache.save(testData);
        cache.get(999L);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(RedisBenchmark.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }
}

벤치마크 결과

Benchmark                               Mode  Cnt     Score      Error  Units
RedisBenchmark.saveViaRepository        avgt    5  2271.554 ± 2129.441  us/op
RedisBenchmark.saveViaTemplate          avgt    5   925.581 ±  916.906  us/op
RedisBenchmark.getViaRepository         avgt    5  2034.616 ± 2634.211  us/op
RedisBenchmark.getViaTemplate           avgt    5  1508.530 ± 1935.112  us/op
RedisBenchmark.saveAndGetViaRepository  avgt    5  3290.914 ± 2300.012  us/op
RedisBenchmark.saveAndGetViaTemplate    avgt    5  3191.825 ± 1834.103  us/op

성능 비교 (마이크로초, 낮을수록 좋음)

저장 (Save) 작업:

조회 (Get) 작업:

저장+조회 작업:

결과 분석

1. 저장 작업에서 가장 큰 차이 (2.45배)

이유:

# Repository: 최소 2개 명령
HMSET StayPdp:999 ...    # ~1000μs
SADD StayPdp 999         # ~100μs
+ Spring Data 오버헤드   # ~1000μs
# RedisTemplate: 1개 명령
SET staypdp:999 ...      # ~900μs

Repository는 여러 Redis 명령 + 추상화 레이어 오버헤드로 인해 훨씬 느립니다.

2. 조회 작업도 차이 존재 (1.35배)

# Repository
HGETALL StayPdp:999      # Hash 전체 조회
+ 역직렬화 오버헤드
# RedisTemplate  
GET staypdp:999          # 단일 값 조회
+ 역직렬화

조회는 둘 다 1개 명령이지만, Hash 전체를 가져오는 HGETALL이 GET보다 무겁습니다.

3. 복합 작업에서는 차이 감소 (1.03배)

저장+조회를 함께 하면 네트워크 왕복 시간(RTT)이 전체 시간의 큰 비중을 차지하기 때문에, Repository의 추가 명령어 영향이 상대적으로 줄어듭니다.

측정 결론

명확한 결론:

실제 프로덕션 환경에서:

메모리 사용 비교

Repository 방식

# Redis CLI에서 확인
127.0.0.1:6379> KEYS StayPdp*
1) "StayPdp:999"      # Hash (실제 데이터)
2) "StayPdp"          # Set (키 목록)
3) "StayPdp:999:idx"  # Set (인덱스 관리)
127.0.0.1:6379> MEMORY USAGE StayPdp:999
(integer) 1248
127.0.0.1:6379> MEMORY USAGE StayPdp
(integer) 96

총 메모리: 데이터 + 부가 구조

RedisTemplate 방식

bash

127.0.0.1:6379> KEYS staypdp:*
1) "staypdp:999"      # String (데이터만)
127.0.0.1:6379> MEMORY USAGE staypdp:999
(integer) 856

총 메모리: 데이터만

차이:

수십만 건의 데이터를 캐싱하면 수 GB의 메모리 차이 발생

실제 프로덕션 영향

Before (Repository)

After (RedisTemplate)

개선율:

5. 내부 동작 원리 분석

Spring Data Redis Repository의 편의성과 그 대가

Spring Data Redis는 RDB의 JPA처럼 Redis에서도 Repository 형태로 데이터를 다룰 수 있도록 CrudRepository 인터페이스를 제공합니다.

public interface StayPdpRepository extends CrudRepository<StayPdp, Long> {
    // 메서드 정의 없이도 기본 CRUD 사용 가능
}

이를 사용하면 도메인 객체에 @RedisHash 어노테이션만 달면 손쉽게 save(), findById(), delete() 등의 CRUD 연산을 수행할 수 있습니다.

하지만 이러한 편의 뒤에는 Redis의 한계를 보완하기 위한 여러 부가 작업이 숨어있습니다.

🔍 부가 작업 1: 보조 Set 및 인덱스 생성

Key-Value DB인 Redis는 기본적으로 Primary Key(키)로만 접근이 가능 합니다. count()(전체 개수)나 특정 필드로 조회하는 기능이 없죠.

Spring Data Redis Repository는 이러한 한계를 극복하기 위해, 모든 키를 보관하는 Set 과 필드 인덱스를 추가로 활용합니다.

Redis에 생성되는 구조

# 1. 실제 데이터 (Hash)
StayPdp:88146
# 2. 전체 키 목록 (Set)
StayPdp → [60931, 88147, 88148, ...]
# 3. @Indexed 필드 사용 시 인덱스 (Set/Sorted Set)
StayPdp:placeName:조이 → [60931]
StayPdp:60931:idx → [StayPdp:placeName:조이하우스]

새로운 객체를 저장하면:

  1. 해당 객체의 키(ID)가 StayPdp Set에 추가
  2. 삭제 시 Set에서 제거

이렇게 하면 SMEMBERS나 SCARD 등을 통해 전체 키 목록이나 개수를 빠르게 조회할 수 있습니다.

@Indexed를 사용하면?

@RedisHash("Product")
public class Product {
    @Id
    private Long id;
    
    @Indexed  // 추가 Set 생성
    private String category;
    
    private String name;
}

도메인 객체 필드에 @Indexed를 지정하면:

  1. 미리 구축된 인덱스(Set 또는 Sorted Set)에서 ID 목록 조회
  2. 다시 실제 해시를 조회

요컨대, Repository를 사용할 때 Redis에는 우리의 데이터 이외에 부가기능을 위한 자료구조들이 더 생기게 됩니다.

부가 작업 2: 저장 시 다중 Redis 명령 실행

CrudRepository.save()를 호출하여 객체를 저장하는 내부 과정을 살펴봅시다.

실제 실행되는 명령어 순서

# 1. 기존 데이터가 있다면 삭제
DEL "StayPdp:60931"
# 2. Hash에 객체 필드들 저장
HMSET "StayPdp:60931"
  "_class" "com.example.demo.rediscompare.model.StayPdp"
  "placeId" "88146"
  "placeName" "조이하우스"
  "reviewRate" "90.0"
  "reviewCount" "26"
  ...
# 3. 키 목록 Set에 ID 추가
SADD "StayPdp" "60931"
# 4. @Indexed 필드가 있다면 인덱스 Set에 ID 추가
SADD "StayPdp:placeName:조이하우스" "60931"
# 5. 인덱스 키 목록 관리 (삭제 대비)
SADD "StayPdp:60931:idx" "StayPdp:placeName:조이하우스"

위와 같이 5개 이상의 Redis 명령이 연쇄적으로 실행되어야 객체 한 개 저장이 완료됩니다.

반면, 같은 객체를 Redis에 단순 캐시하는 경우에는 SET 한 번이면 끝납니다.

조회(findById) 연산도 복잡합니다

# Repository 방식
HGETALL "StayPdp:88146"  # Hash 전체 조회
# + 필요에 따라 존재 여부 확인, TTL 체크 등 추가
# RedisTemplate 방식
GET "staypdp:88146"  # 단일 조회

Repository 추상화의 편의성 대가로 많은 네트워크 왕복과 Redis 연산이 발생합니다.

명령어 수 비교

저장 작업:

조회 작업:

삭제 작업:

카운트:

RedisTemplate의 단순함

Key → Value (직렬화된 객체)

RedisTemplate은 필요한 데이터만 저장하고 조회합니다. 추가적인 자료구조나 부가 명령어 없이 단순하게 동작합니다.

핵심 요약

Repository 방식:

RedisTemplate 방식:

6. 실무 가이드

🤔 실제로 언제 무엇을 써야 할까?

일반적으로 캐싱 목적이라면 Spring Data Redis Repository 대신 **RedisTemplate 또는 Spring Cache (@Cacheable)**를 쓰는 것이 권장됩니다. Repository는 편리하지만 앞서 본 대로 추가 부하와 오버헤드가 큽니다. 아래에 각 방식을 사용해야 하는 경우를 정리합니다.

나쁜 예: 단순 캐시에 Repository 사용

@RedisHash("ProductCache")
public class Product {
    @Id private Long id;
    private String name;
    // ...
}

좋은 예: RedisTemplate 또는 @Cacheable

@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
    return productService.findById(id);
}

이런 경우 Repository는 불필요한 오버헤드를 발생시킵니다:

Repository를 써도 되는 특수한 경우

1. Redis를 실제 데이터베이스로 사용하는 경우

캐시가 아니라 Redis가 Primary Storage일 때:

// 예: 실시간 투표 시스템
@RedisHash("Vote")
public class Vote {
    @Id 
    private String id;
    
    @Indexed  // 사용자별 투표 조회
    private Long userId;
    
    @Indexed  // 투표 항목별 집계
    private String pollId;
    
    private LocalDateTime votedAt;
}
// 필요한 이유: 복잡한 쿼리가 필수
List<Vote> findByUserId(Long userId);
List<Vote> findByPollIdAndVotedAtAfter(String pollId, LocalDateTime since);
long countByPollId(String pollId);

특징:

2. 메타데이터/설정 관리 시스템

// 예: Feature Flag 관리
@RedisHash("FeatureFlag")
public class FeatureFlag {
    @Id 
    private String flagName;
    
    @Indexed
    private String environment;  // dev, staging, prod
    
    private boolean enabled;
    private String description;
}
// 필요한 이유: 환경별로 그룹 조회
List<FeatureFlag> findByEnvironment(String env);
boolean existsByFlagName(String name);

특징:

3. 임시 작업 큐/상태 관리

// 예: 배치 작업 상태 추적
@RedisHash(timeToLive = 86400)  // 24시간
public class BatchJob {
    @Id 
    private String jobId;
    
    @Indexed
    private String status;  // PENDING, RUNNING, COMPLETED, FAILED
    
    @Indexed
    private LocalDateTime createdAt;
    
    private int progress;
}
// 필요한 이유: 상태별 작업 목록 조회
List<BatchJob> findByStatus(String status);
List<BatchJob> findByCreatedAtAfter(LocalDateTime since);
long countByStatus(String status);  // 대시보드용

특징:

RedisTemplate/Cacheable을 써야 하는 경우 (대부분)

1. 모든 캐싱 시나리오

// API 응답 캐싱
@Cacheable(value = "pdp", key = "#placeId")
public StayPdp getStayPdp(Long placeId) {
    return stayService.getDetail(placeId);
}
// 계산 결과 캐싱
@Cacheable(value = "recommendations", key = "#userId")
public List<Product> getRecommendations(Long userId) {
    return recommendationEngine.calculate(userId);
}
// 외부 API 캐싱
@Cacheable(value = "weather", key = "#city", ttl = 1800)
public WeatherInfo getWeather(String city) {
    return externalWeatherApi.fetch(city);
}

2. 대량 데이터 캐싱

// 수십만 ~ 수백만 건의 캐시 데이터
public class ProductCacheRepository {
    private final RedisTemplate<String, Product> redisTemplate;
    
    public void cache(Product product) {
        redisTemplate.opsForValue().set(
            "product:" + product.getId(),
            product,
            Duration.ofHours(1)
        );
    }
}

Repository 사용 시 문제:

3. 고성능이 중요한 경우

// 피크 타임 트래픽 처리
@Service
public class HighTrafficCacheService {
    private final RedisTemplate<String, Object> redisTemplate;
    
    // 1ms 이내 응답 필요
    public Product getProduct(Long id) {
        return (Product) redisTemplate.opsForValue()
            .get("product:" + id);
    }
}

우리 팀의 경험:

핵심 원칙

캐싱이 목적이라면 Spring Cacheable을, 복잡한 쿼리가 필요한 특수한 경우에만 Repository를 사용하는 것이 권장됩니다.

Repository 사용 전 체크리스트:

  1. ❓ 정말로 count(), findAll()이 필요한가?
  2. ❓ @Indexed 검색이 필수 기능인가?
  3. ❓ 부가 메모리 사용과 성능 저하를 감수할 가치가 있는가?
  4. ❓ 데이터 규모가 관리 가능한 수준인가?

하나라도 NO라면 RedisTemplate 사용을 권장합니다.

7. 모니터링과 성능 최적화

Redis Monitor로 실제 명령어 확인하기

# Redis CLI 접속
redis-cli

# Monitor 모드 실행
MONITOR

# 애플리케이션에서 저장 작업 수행 후 로그 확인

확인 포인트:

Redis 키 구조 확인하기

# Repository 방식
KEYS StayPdp*
# 결과: StayPdp:88146, StayPdp, StayPdp:88147, ...
TYPE StayPdp:88146  # → hash
TYPE StayPdp        # → set
# RedisTemplate 방식
KEYS staypdp:*
# 결과: staypdp:88146, staypdp:88147, ...
TYPE staypdp:88146  # → string

성능 최적화 체크리스트

현재 Repository를 사용 중이라면 다음을 확인하세요:

위 항목 중 하나라도 문제가 있다면 RedisTemplate 전환을 고려해볼 필요가 있습니다.

마치며

핵심 정리

Redis 명령:

메모리:

성능:

사용 난이도:

추천 상황:

참고 사례

예제 코드

긴 글 읽어주셔서 감사합니다 :)