Lettuce VS Redisson 분산 락

2026. 2. 13. 15:54·Spring Boot

https://kimfishes.tistory.com/13

 

🚚 허브 간 최단 경로 탐색 캐시 스탬피드 (Cache Stampede) 발생+해결

이전 문제 상황출발지와 목적지 사이 허브 간 경로를 다익스트라 알고리즘으로 선정 후, Kakao Api를 호출해 허브 간 소요 시간 산출이때 Kakao Api 응답을 기다리는 방식의 경우 16 ~ 20 초의 많은 시

kimfishes.tistory.com

 

 

 

 

나의 상황

  1. 1명만 refresh lock → Kakao Api를 호출하여 값 갱신 시도
  2. 나머지는 Redis 재조회 2~3회 후 없으면 TTL 시간이 지난 값이라도 반환
  3. 성공 시 DB upsert + Redis set

- 허브 간 소요시간 저장 시 Redis에 분산락을 사용하여 하나의 요청만 Kakao에 요청하여 값이 갱신되도록 해야 함

- 동일 키에 대해 갱신자 1명만 선출하면 되고, 나머지는 락 대기 없이 값을 랜덤하게 2 ~ 3회 기다리다가 그래도 값을 받지 못하면  TTL 시간이 지난 값이라도 반환 

 

 

 

 

Lettuce이란

  • Lettuce는 Redis와 비동기로 통신할 수 있는 자바 클라이언트
  • Redis 서버에 명령을 안전하고 효율적으로 전달
  • 비동기 / 논블로킹 기반
  • Redis polling + jitter 구조에 안정적
  • 분산락을 “자동으로” 제공하진 않음

단점

  • “락 자체”를 제공하지 않는다 → 직접 설계/검증 필요
  • 재시도/폴링 설계를 잘못하면 결국 “스핀락”이 된다
  • TTL 만료 시간에 Lock 획득으로 동시 갱신자 가능성
    • A가 갱신 중인데 TTL 만료 -> B가 락 획득 → Kakao API를 또 호출하여 결과적으로 “중복 호출” 발생

 

 

 

Redisson ( 고수준 라이브러리 분산락 ) 이란

  • Redis 위에 “분산 객체”를 제공하는 라이브러리
  • RLock, RMap, RSemaphore 같은 고수준 API 제공
  • 내부적으로 Lua + watchdog + TTL 자동 연장

단점

  • 락이 무거움 (watchdog 스레드)
  • “외부 API 1번만 호출” 같은 정밀 제어엔 과함

 

 

 

Lettuce 선택 이유

  • Redis의 Lettuce 락은 Lock를 얻지 못한 스레드가 Lock 획득 시도를 반복하는 스핀락(spin lock) 문제가 있으므로 무조건 사용하면 안된다라는 블로그들에 글이 꽤 있다.
  • 하지만 지금의 경우 Lock 획득하기 위해 싸우는게 목적이 아닌 "갱신자 한 명을 선출하는 게 목적"
  • 즉 락을 기다리지 않는 single-flight 캐시 갱신 정책을 사용하므로 최소 기능(SET NX PX + 토큰 기반 해제)만으로 투명하고 가볍게 구현 가능한 Lettuce 방식을 선택
  • 혹여나 중복 호출이 발생하더라도 허브 간 소요 시간 결과에 악 영향을 끼치지 않음

Lettuce을 언제 사용하는지

  • 락 보유 시간이 짧다 (수백 ms ~ 수 초)
  • “대기”가 아니라 선출이 목적
  • 실패해도 치명적이지 않다 (503/캐시미스 허용, 재시도 가능)
  • 구현을 투명하게 유지하고 싶다 (키/TTL/해제 조건 직접 제어)

 

 

언제 Redisson을 사용하는지

  • 락을 잡고 하는 작업이 수십 초~분 단위인 경우
  • 락이 중간에 풀리면 치명적인 중복 실행이 되는 경우 ( 금전/정산/재고 오류 )
  • 동일 자원(좌석, 쿠폰, 포인트) 업데이트를 순서대로 처리가 필요한 경우
  • 임계구역 작업이 꼭 성공해야 해서 대기 후 획득이 필요한 경우

 

 

 

예시 코드

@Component
@RequiredArgsConstructor
public class RedisHubEdgeCache {

    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper om;

    public Optional<CacheHit<HubInfo>> get(String key){
        try {
            String value = redisTemplate.opsForValue().get(key);
            if(value == null) { return Optional.empty(); }

            long sec = redisTemplate.getExpire(key);
            Duration ttl = sec < 0 ? Duration.ZERO : Duration.ofSeconds(sec);

            return Optional.of(new CacheHit<>(om.readValue(value, HubInfo.class), ttl));

        } catch (Exception e) {
            // 역직렬화/redis 오류면 캐시 제거 후 miss 처리
            redisTemplate.delete(key);

            return Optional.empty();
        }
    }

    public void add(String key, HubInfo hubInfo, Duration ttl){
        try {
            redisTemplate.opsForValue().set(key, om.writeValueAsString(hubInfo), ttl);
        } catch (Exception ignored) {
            // 캐시 저장 실패는 복구 불가능 오류가 아니므로 실패를 시키지 않음
        }
    }

    // DB 조회/갱신은 1명만 가능하도록 Lock
    public boolean tryLock(String key, Duration ttl){
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "1", ttl);
        return Boolean.TRUE.equals(result);
    }

    public void unlock(String key){
        try { redisTemplate.delete(key); } catch (Exception ignored) {}
    }
}

 

 

 

 

결론

- 이번 문제는 “분산락으로 모든 요청을 직렬화”하는 문제가 아니라,
캐시 갱신자 1명을 선출해 외부 API 호출을 1회로 제한하는 single-flight 문제다.

 

- 따라서 무거운 락 추상화보다 SET NX PX + 토큰 기반 해제로 충분하며,
운영 중 갱신 시간이 길어질 수 있는 경우에만 Redisson(Watchdog)로 확장하는 전략이 합리적이다.

 

 

 

'Spring Boot' 카테고리의 다른 글

Spring Boot 4에서 외부 API 호출 무엇을 선택해야 할까?  (0) 2026.03.27
왜 UUID v7을 선택했는가? (Snowflake와 비교까지)  (0) 2026.02.26
🚚 허브 간 최단 경로 탐색 캐시 스탬피드 (Cache Stampede) 발생+해결  (1) 2026.01.19
🚚 허브 간 최단 경로 탐색 알고리즘 구현 + Kakao Map Api 연동 (Spring Boot)  (0) 2025.12.06
DDD의 페이징 로직 (Spring Boot)  (0) 2025.12.02
'Spring Boot' 카테고리의 다른 글
  • Spring Boot 4에서 외부 API 호출 무엇을 선택해야 할까?
  • 왜 UUID v7을 선택했는가? (Snowflake와 비교까지)
  • 🚚 허브 간 최단 경로 탐색 캐시 스탬피드 (Cache Stampede) 발생+해결
  • 🚚 허브 간 최단 경로 탐색 알고리즘 구현 + Kakao Map Api 연동 (Spring Boot)
kimfishes
kimfishes
kimfishes 님의 블로그 입니다.
  • kimfishes
    kimfishes 님의 블로그
    kimfishes
  • 전체
    오늘
    어제
    • 전체 (18) N
      • Infra (5)
        • AWS (0)
        • LogBack (4)
      • Spring Boot (13) N
        • LLM (4) N
      • 일상 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    실시간 알림 시스템
    Discord 알림 연동
    pgvector
    Qdrant
    promtail
    ELK
    UUID v7
    스프링 알림 시스템
    Redis
    spring ai
    LLM
    Pre-Signed URL
    Spring boot
    cache stampede
    loging
    분산 락
    traceId
    캐시 스탬피드
    ollama
    로깅
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
kimfishes
Lettuce VS Redisson 분산 락
상단으로

티스토리툴바