distributed lock

레디스 분산 락의 핵심 문제

redlock

redlock 알고리즘의 장점

redlock 알고리즘의 한계

간단한 결론

redlock 대안

redlock을 사용하지 않고 레디스에서 락 처리하기

distributed lock

분산 환경(다중 레디스 노드 환경)에서 락을 제대로 동기화하려면 레디스 노드 간의 데이터 일관성을 보장해야 한다

다중 레디스 노드 환경

락을 하나의 노드에서만 관리하는 경우 레디스 노드가 장애를 겪으면 락 관리를 실패하게 됨(단일 장애 지점, SPOF)

분산 환경에서의 분산 락 필요성

레디스 분산 락의 핵심 문제

다중 노드 환경에서의 일관성

모든 노드가 특정 클라이언트가 락을 소유하고 있음을 동의할 수 있어야 한다

네트워크 지연, 노드 장애, 클라이언트(애플리케이션) 문제 등으로 인해 동기화 문제가 발생할 수 있다

네트워크 파티션 문제

노드 간 연결이 끊기거나 클라이언트와 레디스 노드 간 연결이 끊기면 락 상태가 불확실해질 수 있다

락 만료 처리

락의 소유자가 작업을 완료하지 못했음에도 불구하고 락이 만료될 수 있으며 다른 클라이언트가 락을 획득할 수도 있다

redlock

참고

레디스에서 제공하는 분산 락을 구현하기 위한 알고리즘으로 다수의 레디스 노드를 사용하여 분산 환경에서도 안정적인 락을 제공하는 데에 목적이 있다

N개 이상의 레디스 노드를 활용하여 과반수 이상의 노드에서 락을 확보하는 방식으로 동작한다

락 획득 과정

다수의 레디스 노드에 락 요청

클라이언트는 모든 노드에 동일한 키와 값을 설정하려고 시도한다

SET lock_key lock_value NX PX <TTL>

각 인스턴스에 잠금을 설정할 때 잠금 자동 해제 시간에 비해 작은 타임 아웃을 설정하여 클라이언트가 다운된 레디스 노드와 통신하기 위해 블로킹될 수 있는 문제를 방지한다

시간 제한

클라이언트는 모든 노드에 락을 요청하는 데 소요되는 시간을 측정하며 제한 시간(잠금 유효 시간 또는 ttl 절반) 내 모든 요청을 완료해야 한다

노드 응답 확인, 락 획득 결과 판단

클라이언트는 락 요청에 성공한 노드의 개수를 확인한다

과반수의 노드에서 락 획득에 성공하면 락이 유효하다고 간주한다

만약 과반수가 넘지않았다면 락 획득에 실패한 것으로 간주하고 다른 클라이언트가 키 획득을 위해 대기하지 않도록 모든 인스턴스에서 락 해제를 시도한다

ttl 확인

락 획득에 성공한 노드에서 ttl을 확인하여 락이 만료되기 전에 작업을 완료해야 한다

락 해제

작업 완료 후 소유자 검증을 통해 락을 소유한 노드에서만 DEL 명령어로 락을 해제한다

redlock 알고리즘의 장점

분산 환경에서 락 안전성 보장

여러 레디스 노드에서 락 정보를 분산 저장함으로써 단일 노드에서 장애가 발생해도 락의 안정성을 유지한다

다수결 원칙

과반수 이상의 노드에서 락을 획득에 성공해야 하므로 네트워크 분할 상황에서도 락의 신뢰성을 높인다

ttl로 데드락 방지

락에 ttl을 설정함으로써 클라이언트에서 장애가 발생해도 락을 자동 해제하여 다른 클라이언트에서 무한히 대기하지 않도록 한다

redlock 알고리즘의 한계

레디스 노드 간의 시스템 시간 문제(clock drift)

redlock 알고리즘은 노드 간의 시스템 시간을 동기화하지 않는다

특정 노드에서 clock drift 현상으로 인해 ttl 계산이 부정확해질 경우 서로 다른 클라이언트에서 잘못된 락 처리를 할 수 있다

이 문제로 인해 아래와 같은 중복 락 처리가 발생할 수 있게 된다

레디스 노드: A, B, C, D, E

클라이언트: X, Y

  1. 클라이언트 X가 레디스 노드 A, B, C에서 락 획득 성공 -> 유효한 락이라고 판단
  2. 레디스 노드 A에서 부정확한 ttl 계산 현상이 발생
  3. 클라이언트 X의 락의 ttl이 만료되지 않았음에도 레디스 노드 A에선 락이 유효하지 않다고 판단 -> 새로 락을 획득할 수 있게 됨
  4. 클라이언트 Y가 레디스 노드 A, D, E에서 락 획득 성공 -> 유효한 락이라고 판단
  5. 클라이언트 X와 Y는 각자 유효한 락을 가지고 있다고 판단하게 됨

네트워크 분할로 인한 중복 락 획득 문제

네트워크 분할 상황에서 클라이언트가 서로 다른 레디스 노드 그룹에서 동시에 락을 획득할 수 있다

7개의 노드 중 4개의 노드가 네트워크 분할로 분리되면 각각의 클라이언트가 두 그룹에서 독립적으로 락을 획득할 수 있다

클라이언트(애플리케이션)의 중단으로 인한 경쟁 상태 문제

클라이언트 X에서 락을 정상적으로 획득한 후 작업을 진행하는 도중, 시스템 상 문제가 발생하여 잠시 중단됐다

해당 애플리케이션을 복구하기 전에 락이 자동해제되어 다른 클라이언트 Y에서 락을 획득하고 동일한 작업을 수행하게 된다면

재가동 후 작업을 이어가는 클라이언트 X와 클라이언트 Y의 작업 내용이 겹치게 된다 (경쟁 상태 문제 발생)

이러한 문제를 해결하기 위해 각 클라이언트에서 획득한 락에 대해 버전 또는 펜싱 토큰을 부여할 수도 있지만 (낙관적 락처럼)

분산 환경에서 여러 레디스 노드들에 대해 락의 버전 값을 동기화해야 하는 또 다른 문제가 발생한다

간단한 결론

redlock 알고리즘은 단일 레디스 노드보다 높은 신뢰성을 보장하지만 완벽히 안전한 분산 락 알고리즘이 아니다

redlock 대안

zookeeper

분산 락 특화 시스템

etcd

분산 키-값 스토어로 redlock보다 일관성과 안정성이 높은 락 서비스를 제공한다

redis cluster

레디스 클러스터를 사용하여 데이터 분산 및 고가용성을 유지하면서 단일 노드에서 락을 처리하도록 구성할 수 있다

redlock을 사용하지 않고 레디스에서 락 처리하기

단순 명령어 처리

락 설정

SET lock_key "lock" NX EX 10 
-- NX: key가 없을 때만 설정
-- EX: ttl 10초 설정

락 해제

DEL lock_key

문제

lua 스크립트를 사용한 락 처리

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

KEYS[1]: 락 키

ARGV[1]: 락 소유자 식별 값

lua 스크립트를 사용하여 락 키와 클라이언트의 값이 일치하는 경우에만 락을 삭제하도록 제한하여 다른 클라이언트에서 DEL 명령어로 락을 삭제할 수 있는 문제를 방지한다

또 다른 문제

master-slave 모드 락 처리

master-slave 모드에서 redlock을 사용하지 않으면 다음과 같은 문제가 발생할 수 있다

master 장애 또는 replication delay

master가 장애로 다운되고 slave가 새로운 master로 승격되면 기존 락 정보가 손실될 수 있다

승격된 master가 기존 락을 무시하고 새로운 락을 생성할 수 있으므로 데이터 일관성이 깨진다

또는 master-slave 간 데이터 동기화 지연으로 인해 master에서 생성된 락이 slave에 반영되기 전에 다른 클라이언트가 slave를 통해 락을 잘못 획득할 수 있다

네트워크 분할 문제

네트워크를 분할한 상황에서 두 개의 master 노드가 동시에 존재할 수 있다

락이 두 master 노드에서 각각 생성되면 동일 리소스에 대해 여러 클라이언트가 동시에 접근하게 되어 데이터 충돌이 발생할 수 있다