Skip to the content.

Persistence Layer — Redis (Lettuce + Redisson)

이 문서는 persistence-layerRedis 파트에 대한 요약 가이드

Lettuce (캐싱) + Redisson (분산락) 이원화 전략을 기반으로 합니다.


1) 라이브러리 전략

Lettuce vs Redisson 역할 분리

라이브러리 용도 이유
Lettuce 캐싱, 세션, 단순 K-V Spring Boot 기본, 가벼움, 빠름
Redisson 분산락, 분산 자료구조 Pub/Sub 기반, Watchdog, 검증됨
┌─────────────────────────────────────────────────────┐
│                    Redis Server                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│   [Lettuce]                    [Redisson]           │
│   Spring Boot 기본              별도 추가            │
│                                                     │
│   • @Cacheable                 • RLock (분산락)     │
│   • RedisTemplate              • RSemaphore         │
│   • Spring Session             • RCountDownLatch    │
│   • 단순 K-V 저장              • RAtomicLong        │
│                                                     │
└─────────────────────────────────────────────────────┘

왜 분산락에 Redisson인가?

Lettuce 분산락 문제점:

Redisson 분산락 장점:


2) 핵심 원칙 (한눈에)

캐싱 (Lettuce)

분산락 (Redisson)

금지사항

금지 이유
Lombok Plain Java 원칙
비즈니스 로직 (Adapter 내) Application/Domain에서 처리
@Transactional 내 Cache 무효화 Rollback 시 불일치 → @TransactionalEventListener 사용
String key 파라미터 타입 안전성 없음 → CacheKey, LockKey VO 사용
Null 캐싱 메모리 낭비 → Cache Miss 시 Empty 반환
Prod에서 KEYS 명령어 Redis 블로킹 → SCAN 사용
대용량 데이터 단일 키 저장 (10MB 이상) 네트워크 병목
Lettuce로 분산락 구현 스핀락 문제 → Redisson 사용

3) 패키징 구조

persistence-redis/
├─ config/
│  ├─ LettuceConfig.java          # Lettuce 연결 설정
│  ├─ CacheConfig.java            # Spring Cache + RedisCacheManager
│  └─ RedissonConfig.java         # Redisson 연결 설정
│
├─ common/
│  ├─ serializer/
│  │  └─ RedisObjectSerializer.java
│  └─ key/
│     └─ RedisKeyGenerator.java
│
└─ {bc}/                          # ← Bounded Context
   ├─ adapter/
   │  ├─ {Bc}CacheAdapter.java    # ✅ 캐싱 (Lettuce)
   │  └─ {Bc}LockAdapter.java     # ✅ 분산락 (Redisson)
   └─ dto/                        # ← 선택
      └─ {Bc}CacheDto.java

4) 디렉터리별 상세 가이드 링크

config/

adapter/ (캐싱)

lock/ (분산락)


5) 주요 패턴

Cache-Aside (Lettuce + CacheKey VO)

// CacheKey 구현 (Domain Layer)
public record OrderCacheKey(Long orderId) implements CacheKey {
    private static final String PREFIX = "cache:order:";

    public OrderCacheKey {
        if (orderId == null || orderId <= 0) {
            throw new IllegalArgumentException("orderId must be positive");
        }
    }

    @Override
    public String value() {
        return PREFIX + orderId;
    }
}

// 조회 (Application Layer)
public Order getOrder(Long orderId) {
    OrderCacheKey cacheKey = new OrderCacheKey(orderId);

    // 1. Cache 조회
    Optional<Order> cached = cachePort.get(cacheKey);
    if (cached.isPresent()) {
        return cached.get();
    }

    // 2. DB 조회
    Order order = queryPort.findById(OrderId.of(orderId));

    // 3. Cache 저장
    cachePort.set(cacheKey, order, Duration.ofMinutes(10));
    return order;
}

// 수정 (Cache 무효화는 @TransactionalEventListener에서 처리)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderUpdated(OrderUpdatedEvent event) {
    OrderCacheKey cacheKey = new OrderCacheKey(event.orderId());
    cachePort.evict(cacheKey);
}

분산락 (Redisson + LockKey VO)

// LockKey 구현 (Domain Layer)
public record OrderLockKey(Long orderId) implements LockKey {
    private static final String PREFIX = "lock:order:";

    public OrderLockKey {
        if (orderId == null || orderId <= 0) {
            throw new IllegalArgumentException("orderId must be positive");
        }
    }

    @Override
    public String value() {
        return PREFIX + orderId;
    }
}

// 사용 (Application Layer)
public void processWithLock(Long orderId) {
    OrderLockKey lockKey = new OrderLockKey(orderId);

    // 최대 10초 대기, 30초 유지
    boolean acquired = lockPort.tryLock(lockKey, 10, 30, TimeUnit.SECONDS);

    if (!acquired) {
        throw new LockAcquisitionException(lockKey.value(), "Lock 획득 실패");
    }

    try {
        // 비즈니스 로직
        processOrder(orderId);
    } finally {
        lockPort.unlock(lockKey);
    }
}

Key Naming (VO에서 관리)

// CacheKey 구현체에서 정의
"cache:order:123"        // OrderCacheKey
"cache:user:profile:456" // UserProfileCacheKey
"cache:product:789"      // ProductCacheKey

// LockKey 구현체에서 정의
"lock:order:123"         // OrderLockKey
"lock:stock:789"         // StockLockKey

6) 설정 체크리스트

Lettuce (캐싱)

Redisson (분산락)

모니터링


7) 의존성 설정

build.gradle

dependencies {
    // Lettuce (Spring Boot 기본 포함)
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'

    // Redisson (분산락용)
    implementation 'org.redisson:redisson-spring-boot-starter:3.27.0'
}

application.yml

spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      lettuce:
        pool:
          max-active: 16
          max-idle: 8
          min-idle: 4
      timeout: 3000ms

# Redisson은 별도 설정 파일 또는 Java Config 사용

8) 참고 문서


작성자: Development Team 최종 수정일: 2025-12-08 버전: 3.0.0

변경 이력

버전 날짜 변경 내용
3.0.0 2025-12-08 CacheKey/LockKey VO 적용, 문서 링크 정리
2.0.0 2025-12-04 Lettuce + Redisson 이원화 전략
1.0.0 2025-11-01 초기 작성