Computer >> 컴퓨터 >  >> 프로그래밍 >> Redis

Redis 분산 잠금 완벽 가이드: 검증된 패턴과 흔한 함정 피하기

마이크로서비스 환경에서는 여러 서버 인스턴스가 동시에 같은 자원에 접근할 수 있어 데이터 정합성 문제가 발생하기 쉽습니다. 이때 필수적인 기술이 바로 분산 잠금(Distributed Lock)입니다. 이 글에서는 Redis를 활용한 분산 잠금의 핵심 원리, 실전에서 검증된 구현 패턴, 그리고 많은 개발자가 빠지기 쉬운 함정까지 체계적으로 살펴봅니다.

Redis 분산 잠금 완벽 가이드: 검증된 패턴과 흔한 함정 피하기

분산 잠금이 필요한 이유

단일 서버 환경에서는 스레드 간 동기화만으로 충분하지만, 여러 인스턴스로 수평 확장된 환경에서는 서버 간 동기화 메커니즘이 필요합니다. 대표적인 사용 사례는 다음과 같습니다.

  • 중복 작업 방지: 스케줄러가 여러 인스턴스에서 동시에 실행되는 것을 차단
  • 재고 관리: 한정된 재고에 대한 동시 주문 처리 시 초과 판매 방지
  • 캐시 갱신 제어: 캐시 미스 발생 시 하나의 요청만 DB에 접근하도록 하여 썬더링 헤드(thundering herd) 방지

기본 패턴: SET NX EX

Redis로 분산 잠금을 구현하는 가장 기본적인 방법은 SET key value NX EX seconds 명령입니다.

  • NX: 키가 존재하지 않을 때만 설정 → 잠금 획득
  • EX: 만료 시간을 초 단위로 지정 → 데드락 방지

명령이 성공하면 잠금을 획득한 것이고, 실패하면 다른 클라이언트가 이미 잠금을 보유하고 있는 것입니다. 특히 SETNXEXPIRE를 두 단계로 나눠 실행하면 원자성이 깨져 장애 상황에서 잠금이 영구히 남을 수 있으므로, 반드시 단일 SET 명령으로 처리해야 합니다.

안전한 잠금 해제: Lua 스크립트

잠금을 해제할 때는 자신이 획득한 잠금인지 반드시 확인해야 합니다. 값에 고유 식별자(UUID 등)를 저장하고 삭제 전에 비교하는 과정이 필요한데, GET과 DEL을 나눠 실행하면 경합 조건(race condition)이 발생할 수 있습니다. 이를 방지하려면 다음과 같은 Lua 스크립트로 원자적으로 처리해야 합니다.

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Redlock 알고리즘

단일 Redis 인스턴스는 장애 발생 시 잠금 정보를 잃을 수 있습니다. 이를 보완하기 위해 고안된 것이 Redlock 알고리즘입니다. 서로 독립된 N개(일반적으로 5개)의 Redis 노드에 순차적으로 잠금을 시도하고, 과반수(N/2+1) 이상에서 성공했으며 소요 시간이 잠금 유효 시간보다 짧을 때 잠금이 유효한 것으로 판단합니다.

흔히 빠지는 함정과 해결책

1. 작업 시간이 만료 시간을 초과하는 경우

GC 일시 정지나 네트워크 지연으로 작업이 잠금 유효 시간보다 오래 걸리면, 다른 클라이언트가 잠금을 획득해 두 클라이언트가 동시에 임계 영역에 진입할 수 있습니다. 이를 완화하려면 워치독(Watchdog) 메커니즘으로 작업이 진행되는 동안 만료 시간을 주기적으로 연장해야 합니다. Redisson의 watchdog이 대표적인 구현 예입니다.

2. 잠금 해제 누락

예외 발생 시 finally 블록에서 잠금을 해제하지 않으면 데드락이 발생할 수 있습니다. TTL을 반드시 설정하고, 코드상에서도 예외와 무관하게 해제 로직이 실행되도록 보장해야 합니다.

3. 페일오버로 인한 잠금 손실

복제 구조에서 마스터에 잠금을 설정한 직후 마스터가 다운되고, 아직 복제되지 않은 상태로 슬레이브가 승격되면 잠금이 사라질 수 있습니다. 강한 일관성이 필요하다면 Redlock을 적용하거나 ZooKeeper, etcd 같은 합의 기반 시스템을 고려해야 합니다.

4. 재시도 시 백오프 미적용

잠금 획득 실패 후 즉시 재시도하면 Redis에 불필요한 부하를 주고 경합만 심화됩니다. 지수 백오프(exponential backoff)와 지터(jitter)를 적용해 재시도 간격을 조절하는 것이 좋습니다.

모범 사례 요약

  • 항상 TTL을 설정해 데드락을 방지한다
  • SET NX EX처럼 원자적인 명령을 사용한다
  • 잠금 값에 고유 ID를 넣고 Lua 스크립트로 안전하게 해제한다
  • 긴 작업에는 워치독으로 만료 시간을 연장한다
  • 강한 일관성이 필요하면 Redlock 또는 합의 기반 시스템을 검토한다

맺음말

Redis 분산 잠금은 구현이 간단해 보이지만, 원자성·만료·페일오버 같은 세부 사항을 놓치면 심각한 버그로 이어집니다. 이 글에서 소개한 패턴과 함정을 숙지하면 더 안전하고 신뢰할 수 있는 분산 시스템을 구축할 수 있습니다.


저자 소개

Baibhav는 Android, iOS, 데스크톱 플랫폼 전반에서 웹 및 모바일 애플리케이션 테스트 경험을 갖춘 품질 보증(QA) 엔지니어입니다. 기능 테스트, UI/UX 테스트, API 테스트, 회귀 테스트를 전문으로 하며 Postman, JMeter, Azure Boards, Jira 등의 도구를 활용합니다. Sharp Rewards, C# Corner, Hackindia 등 실제 프로젝트에 참여해 최적의 성능, 보안, 사용자 경험 확보에 기여했습니다. Lovely Professional University에서 MCA 학위를 취득했으며, 고품질 소프트웨어 제공에 열정을 가지고 C# Corner 같은 플랫폼을 통해 기술 커뮤니티에 활발히 참여하고 있습니다.