레디스 분산락
해당 글에서는 Redis의 분산락에 대한 개념을 설명한다.
글을 쓰게 된 이유
현재 진행하고 있는 프로젝트에서 선착순 이벤트를 진행하는데 현재 소프트웨어 아키텍쳐로는 선착순에 대해서 보장할 방법이 없었다. MySQL에 부담을 주기에는 사이드 이펙트가 크고 DB를 하나의 백엔드만 사용하고 있는 것이 아니고 다른 서버에서도 사용하고 잇기에 만약 DB에 문제가 생길 경우, 이펙트가 큰 상황이라서 DB 부담하는 것은 보기에서 제거를 했다.
앞으로의 기획 로드맵과 현재의 기획 부분에서 Redis가 적합한 선택이 될 것 같아 Redis를 선택하여 도입하였다. 도입과 동시에 선착순 이벤트에 대해서 정상적인 처리가 이루어졌지만 급하게 적용한 탓에 레디스의 분산락에 대한 이해가 부족하여 해당 개념에 대해서 정리를 해보고자 한다.
분산락(Distributed Lock)은 여러 컴퓨터 시스템이 네트워크를 통해 연결된 분산 시스템 환경에서 자원에 대한 동시 접근을 조절하기 위해 사용되는 동기화 메커니즘이다. 이는 여러 노드가 동일한 데이터나 자원에 접근하고자 할 때 발생할 수 있는 충돌을 방지하는 데 필요하다.
Point. 레디스는 싱글 스레드로 동작한다.
[Redis에서 분산락을 제공하는 이유]
단일 레디스 노드는 단일 장애 지점(Single Point Of Failure)이 될 가능성이 있다. 해당 문제를 해결하기 위해서는 Master-Slave 복제(replication) 레디스 서버를 구축하기도 하나, 해당 아키텍쳐는 경쟁상태(race condition)이 발생할 수 있다.
단일 장애 지점(Single Point Of Failure) : 시스템 구성 요소 중에서 동작하지 않으면 전체 시스템이 중단되는 요소
경쟁 상태(Race Condition) : 여러 개의 프로세스가 공유 자원에 동시 접근할 때 실행 순서에 따라 결과값이 달라질 수 있는 현상이다.
경쟁 상태가 발생하는 시나리오
- 클라이언트 A는 마스터에서 잠금을 획득한다.
- 키에 대한 쓰기가 복제본으로 전송되기 전에 마스터가 충돌이 발생한다..
- 복제본이 마스터로 승격됩니다.
- 클라이언트 B는 A가 이미 잠금을 보유하고 있는 동일한 리소스에 대한 잠금을 획득합니다. SAFETY VIOLATION!
[RedLock]
레디스는 위의 단일 장애 지점과 경쟁 상태를 해결하기 위해 Red Lock 알고리즘을 제안한다. Red Lock은 Redis Lock이라고도 불린다. 레드락은 N개의 단일 레디스 노드들을 이용하여, Quorum 이상의 노드에서 잠금을 획득하면 락을 획득하는 매커니즘이다.
Redlock은 아래의 3가지 특성을 지킨다고 이야기한다.
- 상호배제 : 오직 한 순간에 하나의 워커 락을 획득한다.
- 데드락 방지 : 락 이후 어떠한 문제로 인해 락을 풀지 못하고 종료된 경우라도 다른 워커가 락을 획득할 수 있어야한다.
- 내결함성 : Redis 노드가 작동하는 한 모든 워커는 락을 걸고 해제할 수 있어야한다.
[Red Lock 작업 수행]
- 현재 시간을 ms단위로 가져온다.
- 모든 인스턴스에서 동일한 키 이름과 임의의 값을 사용하여 모든 N 인스턴스에서 순차적으로 잠금을 획득하려고 시도한다. 2단계에서 각 인스턴스에 잠금을 설정할 때 클라이언트는 잠금을 획득하기 위해 총 잠금 자동 해제 시간에 비해 작은 제한 시간을 사용한다. 예를들어, 자동 해제 시간이 10초인 경우 제한 시간은 5~50ms 범위 일 수도 있다. 이렇게 하면 클라이언트가 다운된 Redis 노드와 통신하려고 오랫동안 차단되는 것을 방지할 수 있다.
- 클라이언트는 1단계에서 얻은 타임 스탬프를 현재 시간에서 빼서 잠금을 획득하는 데 경과된 시간을 계산하여 클라이언트가 대부분의 인스턴스(최소 3개)에서 잠금을 획득할 수 있었던 경우에만 해당된다. 잠금을 획득하는데, 소요된 총 시간이 잠금 유효 시간보다 작을 경우 잠금을 획득한 것으로 간주된다.
- 잠금이 획득된 경우 유효 시간은 3단계에서 계산된 대로 초기 유효 시간에서 경과 시간을 뺀 것으로 간주한다.
- 클라이언트가 어떠한 이유로 잠금 획득에 실패한 경우(N/2+1 인스턴스를 잠글 수 없거나 유효 시간이 음수인 경우) 모든 인스턴스의 잠금을 해제하려고 시도한다.(그렇지 않다고 생각되는 인스턴스도 포함)
[RedLock의 한계]
- Clock Drift
RedLock은 노드간의 동기화된 시계는 없지만, 로컬 시간에 기반하여 작동하기에 현실에서 클럭이 정확한 속도 동작하지 않으면 클럭 드리프트(Clock Drift) 현상으로 인해 알고리즘이 보장이 안될 수 있다.
- 클라이언트 1이 분산락을 획득한다.
- 클라이언트 1에서 애플리케이션이 중지가 발생하고, 그 사이에 분산락이 만료된다.
- 클라이언트 2는 분산락을 획득하고 파일에 접근한다.
- 클라이언트 1의 애플리케이션이 복구되고 파일을 접근한다.
- 동시성 문제가 발생한다.
일반적으로 GC 시간이 짧으나, Stop-The-World GC는 잠금이 만료될 수 있을 만큼 충분히 오래 지속될 수 있기에 GC 일시 중지는 때때로 몇 분 동안 지속되는 것으로 알려져 있다. 따라서, 락 점유의 만료 기간을 가지는 것은 선택이 아닌 필수다. 하지만 프로세스를 멈추는 이유는 GC외에도 다양한데 이 중 문제가 될 만한 것은 네트워크 지연 등에 의해서도 RedLock 알고리즘을 보장 못받을 수 있다.
위의 네트워크 문제를 해결하기 위해 모든 펜싱 토큰 또는 버전을 포함하여 아래와 같이 처리할 수 있다.
- Network에 기반한 운영
RedLock에는 기본으로 제공하는 펜싱 토큰 기능이 없으므로, 클라이언트에서 네트워크 문제로 인해 경쟁 상태(race condition)가 발생하여 동시성 문제가 발생할 수 있다. 해당 문제를 해결하기 위해 Redis의 단일 노드에 카운터를 관리하며 단일 장애 지점이 되어 해당 레디스가 사이드 이펙트가 매우 커지는 경우가 있을수 있다. 여러 노드에 카운터를 유지하면서 카운터가 동기화되지 않을 수 있다.
따라서, 위의 그림과 같이 펜싱 토큰 또는 버전이 필요할 텐데 이는 레드락에서 기본으로 제공하지 않기에 불안정한 부분이 있다.
Reference.