분산락은 도대체 무엇일까?

반응형

이전에도 Lock에 대해 학습을 진행한적이 있었습니다. DB락 부터, 낙관 락, 비관 락에 대해 학습을 진행하였죠.

 

RDB vs Nosql

크게 DB에는 2가지 종류가 존재합니다. SQL을 사용하는 RDB와 SQL도 사용하는 NoSql(NoSql이 왜 Nosql인지에 대해 sql을 안써서 noSql이다 아니다 Not only sql의 약자라 그렇다라는 말들이 존재합니다. 안 중

b-programmer.tistory.com

 

당신의 동시성 테스트가 원하는 결과가 나오지 않는 이유

TL;DR: 낙관락과 비관락을 고르는 기준에 대해 설명합니다.배경그 전에도 낙관락, 비관락을 해봤기때문에 금방할 줄 알았다.하지만 아니었다. 어디서 부터 문제 였을까?생각하기에는 비관락을 사

b-programmer.tistory.com

하지만 곰곰이 생각해보니 실제 실행 환경은 인스턴스 하나로만 구성되지 않습니다. 대부분의 서비스는 여러 인스턴스가 동시에 동작하는 분산 환경에서 운영되어집니다. 그럼에도 불구하고 기존 글들에서는 이러한 분산 환경을 충분히 전제로 설명하고 있지 않다고 느꼈습니다.
그렇다면 분산락과 DB 락의 관계를 먼저 정리해보고, 이를 바탕으로 분산락에 대해 조금 더 깊이 있게 살펴봅시다.

일단 분산락은 어떤것을 말하는 걸까?

흔히들 분산락이라고 하면 레디스를 써야 한다고 생각합니다. 저 역시 처음에는 그렇게 알고 있었습니다.

그런데 조금 더 찾아보니, 분산락이라는 건 특정 기술을 의미하는 개념이 아니었습니다.
단순히 레디스를 사용해서 락을 거는 것이 아니라, 분산된 환경에서 동시성 문제를 제어하는 방식을 의미하는 것이었습니다.
즉, 레디스 락을 분산 환경에서 사용하면 그것이 분산락이 되는 것이지, 레디스 락 자체가 곧 분산락이라고 말할 수는 없습니다.
그렇다면 결국 궁금해집니다. 분산락의 본질은 무엇일까요?

분산락의 본질

분산락의 본질은 "" 그 자체가 아닙니다.
조금 더 정확히 말하면, 분산된 환경에서 하나의 자원에 대해 동시에 작업하지 못하도록 만드는 합의 메커니즘입니다.
핵심은 기술이 아닙니다. 레디스를 쓰느냐, DB를 쓰느냐, ZooKeeper를 쓰느냐의 문제가 아닙니다.
본질은 다음과 같습니다.

상호 배제 (Mutual Exclusion)

상호 배제란, 같은 공유 자원에 동시에 두 명이 접근하지 못하도록 막는 것을 의미합니다.
여기서 핵심은 "같은 공유 자원"이라는 점입니다.

예를 들어 집 하나를 계약하는 상황을 생각해봅시다. 이 집은 공유 자원이며, 계약은 단 한 번만 가능합니다.

A와 B가 동시에 계약을 시도한다면,
두 사람 모두 "아직 계약되지 않음"이라는 동일한 상태를 확인하게 됩니다.
그리고 동시에 계약을 진행한다면 이중 계약이 발생할 수 있습니다.

상호 배제는 바로 이 상황을 방지하기 위한 개념입니다.
한 사람이 계약 절차를 시작하는 순간, 다른 사람은 해당 자원에 접근하지 못하도록 막습니다.

즉, 하나의 공유 자원에 대해 동시에 두 작업이 실행되지 않도록 보장하는 것,
그것이 바로 상호 배제입니다.

공유된 상태

모든 인스턴스는 동일한 락 상태를 볼 수 있어야 합니다.
즉, 누가 지금 작업 중인지에 대해 모두가 같은 판단을 내려야 합니다.

앞서 집 계약 상황을 예시로 들었습니다.

이 예시로 다시 생각해보면, 만약 집이 이미 계약되었다는 사실을 다른 사람들이 알지 못한다면 어떻게 될까요?

계약이 완료된 사실을 모른 채 계속해서 계약을 시도하게 될 것입니다.
그 결과, 이미 끝난 계약에 대해 불필요한 요청이 반복되거나, 상태 불일치 문제가 발생할 수 있습니다.
하지만 집이 이미 계약되었다는 사실을 모두가 알고 있다면, 더 이상 계약은 진행되지 않습니다.
결국 공유된 상태란, 모든 참여자가 동일한 자원 상태를 인지하고, 그에 따라 동일한 판단을 내릴 수 있는 환경을 의미합니다.

합의

A 서버가 락을 획득했다는 사실을 B 서버도 동일하게 인정해야 합니다.

즉, 한 서버가 "내가 지금 작업 중이다"라고 판단했을 때, 다른 서버 역시 그 상태를 같은 사실로 받아들여야 합니다.

앞서 예로 들었던 집 계약 상황을 다시 생각해보면, 한 사람이 집 계약을 완료했다면 그 사실을 다른 사람들도 알고 있어야 합니다.
만약 A는 “계약 완료”라고 알고 있는데, B는 여전히 "계약 가능"이라고 생각한다면 문제가 발생합니다.
결국 합의란, 하나의 자원 상태에 대해 모든 참여자가 동일하게 인식하고 동의하는 것을 의미합니다.
이 합의가 깨지는 순간, 분산 환경에서는 정합성 문제가 발생하게 됩니다.

정리해보면, 분산락은 단순히 분산 환경에서 사용하는 락 개념이 아니었습니다.

조금 더 정확히 말하자면, 여러 인스턴스가 동시에 동작하는 분산 환경에서
특정 자원에 대해 누가 작업할 것인지에 대해 합의를 만들고, 그 합의를 기반으로 상호 배제를 보장하는 메커니즘이라고 생각합니다.
즉, 분산락은 단순한 기술이나 도구의 이름이 아니라, 분산 환경에서 동일한 자원 상태를 공유하고,
그 상태에 대해 모두가 동일하게 인정하도록 만들어 결과적으로 하나의 작업만 수행되도록 보장하는 방식입니다.

분산락은 어떻게 사용할 수 있을까?

분산락은 외부의 공통된 저장소에 상태를 기록하고, 그 상태를 기준으로 모든 인스턴스가 판단하도록 만들면 됩니다.
즉, "누가 작업 중인지"에 대한 정보를 각 인스턴스의 메모리가 아니라 공유된 외부 시스템에 저장하는 방식입니다.

대표적으로 다음과 같은 방법이 있습니다.

  • DB를 이용한 락
  • Redis를 이용한 락

이들은 모두 외부에서 상태를 기억하고, 그 상태를 기반으로 여러 인스턴스가 동일한 판단을 내릴 수 있도록 해줍니다.
결국 분산락의 핵심은 어떤 기술을 쓰느냐가 아니라, 합의된 상태를 어디에 저장하고 어떻게 신뢰할 것인가의 문제라고 볼 수 있습니다.

DB를 이용한 락

DB 락에는 크게 두 가지 방식이 있습니다. 낙관적 락과 비관적 락입니다.
간단히 설명하면,

  • 낙관적 락은 테이블에 version 컬럼을 두고,
    업데이트 시점에 버전을 비교하여 충돌을 감지하는 방식입니다.
    즉, 동시에 수정이 발생했는지를 검사하고, 충돌 시 실패하도록 만듭니다.

  • 비관적 락은 SELECT ... FOR UPDATE와 같이
    DB 자체에서 해당 데이터에 락을 걸어 다른 트랜잭션의 접근을 차단하는 방식입니다.

여기서 중요한 차이는 "어디에서 동시성을 제어하느냐"입니다.

낙관적 락은 충돌을 감지하는 로직이 애플리케이션 레벨에서 해석됩니다.
다만, 실제 버전 비교와 조건 검증은 결국 DB가 수행합니다. 즉, 완전히 내부 메모리에서만 동작하는 것은 아닙니다.
반면, 비관적 락은 DB가 직접 락을 관리합니다. 데이터 접근 자체를 차단하므로 상호 배제가 명확하게 성립됩니다.
이 관점에서 보면, 분산 환경에서 상호 배제를 명확히 보장하는 방식은 비관적 락에 더 가깝다고 볼 수 있습니다.

낙관적 락은 "동시 실행을 허용한 뒤 충돌을 감지하는 방식"이고,
비관적 락은 "동시 실행 자체를 차단하는 방식"이기 때문입니다.
정리하자면, 분산락의 핵심이 상호 배제라면, DB 비관적 락은 그 성격에 더 가깝다고 볼 수 있습니다.

Redis를 이용한 락

Redis를 이용한 락은 공유된 외부 저장소에 "락 상태"를 기록하는 방식입니다.
즉, 특정 자원에 대해 작업을 시작하기 전에 Redis에 먼저 락을 요청하고, 성공한 인스턴스만 작업을 수행하도록 만드는 구조입니다.
가장 기본적인 방식은 다음과 같습니다.

SET key value NX PX 3000
  • NX : 해당 키가 없을 때만 설정
  • PX : 만료 시간(TTL) 설정

이 명령이 성공하면 락을 획득한 것이고, 실패하면 이미 다른 인스턴스가 락을 잡고 있다는 의미입니다.

만약 락을 획득한 인스턴스가 작업 중에 장애가 발생하면, 락이 해제되지 않은 채로 남을 수 있습니다.
이를 방지하기 위해 락에는 반드시 만료 시간(TTL) 을 설정해야 합니다.
TTL이 지나면 자동으로 락이 해제되므로, 영구적인 교착 상태를 막을 수 있습니다.

Redis를 사용했다고 해서 자동으로 완벽한 분산락이 되는 것은 아닙니다.

중요한 것은

  • 동일한 키를 기준으로 모든 인스턴스가 판단하는가
  • 락의 소유자만 해제하도록 보장하는가
  • TTL과 재시도를 어떻게 설계했는가

결국 Redis는 도구일 뿐이고, 분산락의 본질은 여전히 합의와 상호 배제입니다.

결론

정리해보면, DB 락은 상호 배제를 DB가 직접 보장한다는 점에서 직관적인 방식이라고 생각합니다.
특히 비관적 락은 데이터 접근 자체를 차단하기 때문에, 상호 배제가 비교적 명확하게 드러납니다.
반면 Redis를 이용한 락은 공유된 외부 저장소를 통해 여러 인스턴스가 동일한 상태를 기준으로 판단하도록 만드는 방식입니다.
즉, 특정 기술이 분산락 그 자체라기보다는, 공유된 상태를 이용해 상호 배제를 구현하는 하나의 방법이라고 보는 것이 더 적절하다고 생각합니다. 물론 상호 배제, 공유된 상태, 동일한 상태에 대한 공통된 판단 이 세 가지 중
하나만 만족한다고 해서 분산락이라고 할 수는 없습니다. 이 요소들이 함께 충족될 때 비로소 분산 환경에서 의미 있는 락이 성립합니다.

특히 Redis의 경우, 여기서 끝이라고 생각하지 않습니다. 낙관적 락과 비관적 락을 통해 동시성 제어를 경험해봤다면,
이제는 Redis를 이용한 방식도 한 번쯤 직접 적용해보는 것도 충분히 의미 있다고 생각합니다.

이후에는 DB 락과 Redis 락을 단순히 비교하는 수준을 넘어서, 각 방식이 어떤 환경에서 더 적합한지에 대해
조금 더 깊이 고민해보는 과정이 필요하다고 생각합니다.

반응형

댓글

Designed by JB FACTORY