분산 락이란?
분산 락은 여러 개의 클라이언트가 특정 리소스에 접근할 때, 한번에 하나의 클라이언트만 접근(mutual exclusive) 할 수 있도록 하는 메커니즘이다.
분산락의 조건
분산 락은 최소한 아래의 조건을 만족시켜야한다.
- safety: 하나의 클라이언트만 리소스에 접근할 수 있어야한다. (mutual exclusive)
- deadlock free: 특정 클라이언트가 락 획득 후 죽는다고 해도, 결국에 다른 클라이언트가 락을 획득 할 수 있어야한다.
- fault tolerance: 일부 레디스 노드가 죽는다고해도, 대다수가 살아있다면 락 획득을 할 수 있어야한다.
redlock 이전 라이브러리들의 문제?
레디스 공식문서에 의하면, redlock 이전의 라이브러리들은 문제가 있었다고 한다.
그것은 redis replication 이 비동기적으로 이루어져서 발생한다.
- client A 가 master 노드로 부터 락을 획득한다.
- replication 에 복제하기 전에, master 가 죽는다.
- 기존의 replication 이 master로 승격한다.
- client B 가 승격된 master 에게 락을 획득한다.
이 경우, client A 가 lock 을 해제하기 전에, client B 가 lock 을 획득함으로써,
앞서 언급한 safety 속성 (mutual exclusive) 를 지키지 못하게 된다.
레디스가 싱글 인스턴스 경우 올바른 구현
레디스가 싱글 인스턴스일 때는 위와 같은 문제가 발생한다.
싱글 인스턴스에 대한 올바른 구현에 알아야 하는 이유는 어쨋든 꽤 많은 시스템은 이 정도의 race condition 에 대해서는 용납가능하고, 레디스가 여러 개일때 단일 노드를 잠그는 방법에 대해서는 같은 방식을 사용하기 때문이다.
아래 방식으로 레디스 락을 구현하는 것은 문제가 있다.
그것은 바로, client A 가 lock 을 걸고나서 lock key 에 타임아웃이 발생한 다음
해당 키를 client B가 이후에 락을 획득하였는데, client A 가 락을 해제하는 문제가 발생한다.
따라서, 아래와 같이 client A 가 생성시 넘겨준 random_value 가 lock key에 저장된 값과 같은지 확인해야한다. 일반적인 경우, random_value 는 timestamp 를 기준으로 설정하면 문제가 없으나 중복되는 경우에는
uuid 와 같은 값을 사용하는 것도 고려해볼만 하다.
분산 버전에서의 구현
앞서 설명한 버전은 레디스가 싱글 인스턴스일 때로,
N 개의 redis 마스터를 가정하면 얘기가 달라진다.
아래의 그림은 N=5 인 환경이다.
이러한 환경에서 분산락은 어떻게 구현되는가?
- client 는 timestamp(startTimestamp) 를 기록한다.
- N 개의 redis 노드에 lock 요청을 보낸다.
- client 와 redis node 사이의 통신 타임아웃은 짧게 잡는 것이 좋다. 그렇게 해야, 다른 node 에 락을 빠르게 걸 수 있다. 그렇지 않은 경우 split brain condition 이 발생할 수 있다.
- currentTimestamp - startTimestamp < lock 을 걸려는 시간 (lock validity time) 인지 확인
- lock validity time 을 3번에서 계산된 lock validity time 에서 일정시간을 뺀 것으로 업데이트한다.
- 일정 시간을 빼는 이유는 분산환경에서 process 마다 clock 동기화가 안되어있을 수 있기 때문이다.
- 만약 정족수 ( N/2 + 1) 이상 락 점유에 실패하거나 lock validity time 이 음수인 경우 모두 락 해제해야한다.
그리고 분산된 시스템에서 mutual exclusive 에 대해서는 lock validity time 내에 작업이 끝나는 경우에만 실현된다. 따라서 lock 점유 시간을 정할 때 이를 고려해야한다. → 락 점유 시간을 너무 짧게 잡으면 문제가 될 수 있다.
잠금 실패시 고려사항
만약 잠금 획득에 실패한다면, 재시도를 해야한다.
이때 주의해야할 것이 두가지가 있다.
- 고정 delay 를 준다면?
고정 값으로 delay 를 준다면, 동시에 같은 요청이 들어오는 경우 영원히 락 획득을 못할 가능성이 생긴다.
왜냐하면 레디스가 N 개의 마스터를 가진다면 정족수 이상 락을 획득해야하는데, 동시에 여러 client 가 락을 획득하려고 하면 이는 락 획득 시도→ 실패 를 반복하게 될 것이기 때문이다.
- 각 레디스 노드에는 병렬적으로 락 획득 시도를 해야한다.
만약 하나씩 시도한다면, 락을 획득하기 어려워진다.
순차적으로 진행하여 아래 처럼 락 획득을 한다면, 락 획득을 하기 어려워질 것이기 때문이다.
client A → node1, node2
client B → node3, node4,
client C → node5
- 락 획득에 실패하면 최대한 빠르게 일부 획득한 락을 반환해야한다.
이것도 마찬가지로, 락 획득에 실패하면 일부 노드에 획득한 락을 빠르게 반환하지 않는 경우,
다른 클라이언트가 해당 노드에 대한 락 획득을 하지 못하여, 다른 클라이언트의 락 획득도 실패하게 되기 때문이다.
참고자료
'데이터베이스' 카테고리의 다른 글
redis - pub/sub 동작 원리 (0) | 2024.11.18 |
---|---|
MySQL(InnoDB)이 잠금할 레코드를 고르는 방법 (1) | 2024.06.05 |
왜 많은 회사들은 READ COMMITTED를 사용할까? (0) | 2023.07.07 |