Post

데이터 무결성 관점에서의 lock 아키텍처 설계

lock 아키텍처 설계

데이터 무결성 관점에서의 lock 아키텍처 설계

데이터 무결성 아키텍처 설계

데이터 무결성은 데이터가 정확하고(accuracy), 일관되며(consistency), 완전하고(completeness), 신뢰할 수 있는 상태로 유지되는 성질을 의미한다.

1. 무결성의 핵심 속성

속성설명위반 예시
정확성 (Accuracy)데이터가 현실 세계를 올바르게 반영잘못된 계좌 잔액
일관성 (Consistency)데이터 간 논리적 모순 없음주문은 있는데 고객 정보 없음
완전성 (Completeness)필요한 데이터가 누락되지 않음필수 컬럼 NULL
유효성 (Validity)정의된 형식/범위 준수음수 수량
지속성 (Durability)저장 후 손실되지 않음커밋 후 데이터 유실

2. 낙관적 락

“충돌은 드물다”는 가정 하에, 실제 잠금(lock)을 걸지 않고 데이터 변경 시점에 충돌 여부를 검증하는 동시성 제어 방식이다.

  • 비관적 락처럼 레코드를 선점하지 않는다. 대신 버전(version) 또는 타임스탬프 기반 비교로 무결성을 보장한다.

2.1 낙관적 락은 “검증”

1
2
3
4
5
UPDATE account
SET balance = 80,
    version = version + 1
WHERE id = 1
  AND version = 3;

낙관적 락은 조회 시점에 아무도 점유하지 않고, 나중에 검증을 진행한다. 그리고 조건 불일치면 실패한다.(재시도)

2.2 비관적 락은 “선점”

1
2
3
SELECT * FROM account
WHERE id = 1
FOR UPDATE;

비관적 락은 조희 시점에 데이터를 선점하여 점유하고 충돌을 원천적으로 차단한다. 즉 락 범위의 문제라기 보단 락 타이밍의 문제이다.

2.3 비관적 락과의 차이

구분낙관적 락비관적 락
철학충돌은 드물다충돌은 발생한다
락 획득 시점쓰기 시도 시읽는 순간
실제 DB 락없음 (쓰기 시점만 row lock)SELECT 시점부터 row lock
충돌 처리사후 감지사전 차단
대기없음있음

낙관적 락과 비관적 락은 그 철학이 다르다.

3. Named Lock

DB 내부의 레코드/테이블이 아니라, 사용자가 정의한 문자열 키를 기준으로 획득하는 논리적 락이다.

  • MySQL Named Lock(GET_LOCK)의 key는 디스크나 테이블에 저장되지 않는다.
  • MySQL 서버 프로세스의 메모리 내부 락 매니저 구조에 저장된다.
  • InnoDB row lock과 별개
  • 서버 전역(Global) 메모리 영역에서 관리
1
2
3
SELECT GET_LOCK(CONCAT('account_', 123), 5);
-- 처리 로직
SELECT RELEASE_LOCK(CONCAT('account_', 123));

3.1 트랜잭션과 독립적이다.

1
2
3
BEGIN;
SELECT GET_LOCK(...);
ROLLBACK;
  • 위 상황에서 락은 해제 되지 않음 : 반드시 명시적으로 RELEASE 필요

3.2 커넥션 풀 환경

  • 커넥션 풀에서 락 해제 안하고 풀로 반환되면 Named Lock 유지 상태로 재사용 가능
    • 락 누수 발생 가능

MySQL Named Lock은 생명 주기가 세션 단위(락의 생명주기는 “Connection lifecycle”에 묶여 있다.)

  • 락은 트랜잭션 단위가 아님 DB 세션(커넥션) 단위
  • COMMIT/ROLLBACK 해도 해제 안됨
  1. 애플리케이션이 커넥션을 빌림
  2. GET_LOCK() 호출 → 세션이 락 보유
  3. 작업 완료
  4. RELEASE_LOCK() 호출 안 함
  5. 커넥션을 pool에 반환
    • 커넥션은 닫힌 게 아니라 풀에 보관된다.
    • 세션은 살아 있음 = Named Lock도 그대로 유지됨

위의 상황에서 다른 트래픽이 같은 커넥션(세션)을 사용하여 락을 획득하면 성공 다른 커넥션 사용하면 대기 or 타임아웃이 발

결과적으로

  1. 특정 락이 “영구 점유 상태”가 됨
  2. 다른 인스턴스는 계속 블로킹됨

만약 네임드락의 키가 특정 id를 해싱하는 방식으로 작동한다면 같은 작업(id)를 사용하는 작업은 무조건 블로킹되는 현상이 발생할 수도 있다.

3.3 Named Lock의 한계

락은 트랜잭션 단위가 아니라 세션(커넥션) 단위

  • COMMIT/ROLLBACK과 무관
  • RELEASE_LOCK() 호출 안 하면 유지
  • 커넥션 풀 환경에서 락 누수 발생 가능

결과적으로

  • 특정 키 영구 점유
  • 일부 요청만 계속 타임아웃
  • 원인 파악 어려움

Named Lock은 결국 DB 자원을 사용한다.

  • TPS 증가 → DB가 락 경합 지점이 됨
  • 고부하 환경에서 병목 형성
  • 락 대기 스레드 증가 → 커넥션 고갈

결과적으로

  • 고TPS 시스템에서 Named Lock은 확장성이 좋지 않다.

Primary–Replica가 여러 대라도, Primary 1대에만 모든 락 요청을 강제하면 동작한다.

  • 즉 MySQL이 “클러스터링”되어 있어도, Named Lock(GET_LOCK)은 인스턴스(노드) 단위다.
  • 클러스터 전체에 걸친 전역 락이 아니다.

즉 아래 중 하나라도 있으면 GET_LOCK은 분산락이 아니다.

  • LB misrouting
  • read/write 분리 오류
  • multi-primary
  • failover

4. 분산락

여러 프로세스/서버가 동시에 동일 자원에 접근하지 못하도록 제어하는 메커니즘이다. 단일 JVM의 synchronized와 달리, 네트워크를 넘는 동시성 제어

단일 DB 락과 달리 다음 문제가 존재한다:

  • 네트워크 분할 (Partition)
  • 클럭 불일치
  • 노드 장애
  • GC Stop-the-world
  • 프로세스 freeze
  • 락을 잡은 프로세스가 죽었는지 알기 어려움

핵심 문제는

  • 네트워크 장애, 노드 장애, 지연 상황에서도 “하나만 실행됨”을 어떻게 보장할 것인가

4.1 Redis 기반의 분산락

기본 패턴 : SET key value NX PX 30000

  • NX: 없을 때만 설정
  • PX: TTL
  • value: 랜덤 토큰 (락 소유자 식별)

장점:

  • 빠름
  • 구현 쉬움
  • TTL 기반 자동 해제

단점:

  • 네트워크 분할 시 안전성 문제
  • 단일 Redis는 SPOF
  • Redlock 알고리즘 논쟁 존재

5. Redis Redlock 논쟁

N개의 독립 Redis 인스턴스 중 과반수(N/2+1)에서 락을 잡으면 “획득 성공”으로 간주한다.

5.1 Redlock이 해결하려는 문제

  • 단일 Redis 락의 단점 : 단일 인스턴스에서 락을 잡으면, Redis 장애/재시작/네트워크 문제 시 락의 안전성·가용성이 취약하다.
  • Master–Replica(비동기 복제)에서의 위험 : Primary에서 락을 잡았는데 failover가 일어나면, 복제 지연 때문에 동일 락이 다른 노드에서 다시 잡히는 시나리오가 생길 수 있다.

5.2 Redlock 알고리즘

  1. 클라이언트는 현재 시간을 기록한다.
  2. N개의 Redis 인스턴스에 같은 key/value/TTL로 락 획득을 시도한다. 각 인스턴스에는 “짧은 연결/명령 타임아웃”을 둬서 한 노드 지연이 전체를 망치지 않게 한다.
  3. 과반수 이상(N/2+1)에서 락을 획득했고, 전체 소요 시간이 TTL보다 충분히 작다면 성공으로 간주한다. 이때 락의 유효 시간은 “TTL - 획득에 걸린 시간 - 드리프트 여유”로 계산한다.
  4. 과반수를 못 얻었거나 시간이 너무 오래 걸렸다면 실패로 간주하고, 부분적으로 잡힌 락은 모두 해제한다.
  • 파라미터
    • N: Redis 인스턴스 수 (관례적으로 5를 예로 많이 듦)
    • TTL(lease time): 락 자동 만료 시간 (예: 10s)
    • key: 락 이름
    • value: 랜덤 토큰(UUID 등). “내 락”임을 증명하는 소유자 식별자
    • drift: 클럭 오차/지연을 감안한 여유분(문서에서 고려 필요로 언급)

해제에서 가장 중요한 안전장치:

  • DEL로 무조건 지우면 안 된다.
  • 반드시 “내 value가 맞을 때만 delete”해야 한다.

그래서 Lua 스크립트(원자적 비교-삭제)를 쓴다. Redis 문서(SETNX 관련)도 이 원칙을 강조한다.

5.3 가장 큰 논쟁: “안전(safety)한가?”

Kleppmann은 Redlock이 특정 상황(프로세스 일시정지, 클럭 드리프트, 네트워크 지연/분할 등)에서 상호 배제(mutual exclusion)를 깨뜨릴 수 있는 시나리오를 제시하며 비판했다.

  • “락을 잡았다”는 판단이 시간/지연에 의존하는데,
  • 분산 환경에서 시간 가정이 깨지면
  • 두 클라이언트가 동시에 “내가 락을 가졌다”고 믿는 순간이 생길 수 있다.

Redis 창시자인 antirez는 Kleppmann의 가정/전제에 반론을 제기하며 Redlock의 유용성을 주장한다.

  • Redlock은 “완전무결”을 주장하기보다는 단일 노드 락보다 더 나은 출발점으로 제시되었고,
  • 어떤 워크로드에서는 실용적이라는 입장이다.

5.4 Redlock을 “정확성(correctness) 보장” 용도로 쓰려면 기준이 엄격해야 한다.

  • Redlock을 써도 되는 쪽(현실적으로)
    • 중복 실행이 ‘가능은 하지만’ 비즈니스적으로 흡수 가능
    • 락 구간이 짧고, TTL/재시도/백오프를 엄격히 설계
    • 후단 작업이 멱등(idempotent) 또는 펜싱 토큰(fencing token) 같은 추가 안전장치로 보호됨(락만 믿지 않고 “락이 깨져도 최종 쓰기에서 방어”)
  • Redlock만으로는 위험한 쪽
    • 잔액/정산/체결 같이 중복이 곧 사고인 영역
    • 락 획득만으로 안전하다고 믿고, 후단에 아무 가드(멱등/조건부 업데이트/펜싱)가 없는 설계

6. Redisson에 대해서

Redisson은 Redis를 대상으로 하는 고수준 Java 클라이언트다. 단순히 GET/SET만 하는 클라이언트가 아니라, Redis 위에 다음을 “프리미티브”로 제공한다.

  • 분산 락/동기화: RLock, FairLock, ReadWriteLock, Semaphore, CountDownLatch 등
  • 분산 컬렉션: RMap, RSet, RList, RQueue, RBlockingQueue …
  • 원자 연산/카운터: RAtomicLong 등
  • (옵션) 캐시/분산 객체/Executor 등: 서비스가 커질수록 사용 범위가 넓어짐

6.1 Redisson 분산락 핵심: RLock의 동작과 Watchdog

기본 RLock은 “단일 Redis 기반 락”

1
2
3
4
5
6
7
RLock lock = redisson.getLock("order:123");
lock.lock(); // leaseTime 미지정
try {
  // critical section
} finally {
  lock.unlock();
}
  • 내부적으로는 Redis의 키 + TTL(리스) 기반 락 패턴을 사용하고
  • 해제는 소유자 토큰(value) 검증 후 삭제를 원자적으로 수행(Lua)하는 방식으로 안전성을 확보한다(“남의 락 삭제 방지”는 Redis 분산락의 기본 안전장치).

Watchdog(자동 연장)이 Redisson의 차별점

lock()처럼 leaseTime을 지정하지 않고 락을 획득하면, Redisson은 watchdog를 켜서 TTL을 주기적으로 연장한다.

  • watchdog가 연장을 못 하면 락은 lockWatchdogTimeout 후 만료된다.
  • 목적: 클라이언트 크래시/네트워크 문제로 unlock()을 못 해도 “영구락”이 되지 않게 함

즉, Redisson의 기본 락은 “TTL+자동연장” 모델이다.

중요한 실무 포인트

  • lock(10, SECONDS)처럼 leaseTime을 직접 주면 watchdog는 기본적으로 필요 없어지고, TTL은 고정된다(작업이 더 길어지면 락이 먼저 만료될 수 있음)
  • 이때는 실행 시간 상한을 정확히 아는 작업에만 적합하다. (watchdog의 의미 자체가 “실행시간 예측 어려움” 대응)

6.3 Lua 스크립트를 통한 락 해제 원자성 확보

Redisson의 내부 해제 로직 개념적으로 아래의 형태를 띈다.

1
2
3
4
5
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
  • GET + 비교 + DEL이 하나의 atomic operation
  • 중간에 다른 클라이언트 개입 불가

단순 DEL은 아래의 시나리오에서 터질 수 있다.

  1. A가 락 획득 (TTL=30초)
  2. A가 작업 중
  3. GC stop 또는 네트워크 지연 발생
  4. TTL 만료 → 락 자동 해제
  5. B가 락 획득
  6. A가 다시 살아나서 DEL lockKey 실행

A가 B의 락을 삭제한다.

  • 두 인스턴스가 동시에 크리티컬 섹션 실행 가능
  • 상호 배제 깨짐

Redis 락은 lease(임대) 모델이다.

  • TTL이 지나면 락은 자동 해제
  • 클라이언트는 자기 락이 아직 유효한지 확신할 수 없다

따라서:

  • 락 해제 시 “내 락인지” 확인이 필요
1
2
if redis.get(lockKey) == myValue:
redis.del(lockKey)

위와 같은 방식또한 아래의 시나리오에서 타질 수 있다.

1
2
3
GET lockKey
(네트워크 지연)
DEL lockKey
  • TTL 만료
  • 다른 클라이언트가 락 획득
  • 여전히 남의 락 삭제 가능

따라서 비교 + 삭제를 원자적으로 실행해야 한다.

6.2 Redisson이 제공하는 락/동기화 프리미티브

  • Lock vs FairLock
    • RLock: 일반 분산 재진입 락(대체로 가장 빠름)
    • FairLock: 대기 순서(FIFO) 보장. 대신 오버헤드가 커져 성능이 낮아질 수 있음
  • ReadWriteLock
    • 다수 Reader 동시 허용, Writer는 단독
    • 읽기 비중이 높고 “쓰기 배타성”이 필요한 경우 사용
  • MultiLock / RedLock
    • MultiLock: 여러 락을 “묶어서” 모두 잡아야 성공
    • RedissonRedLock: Redlock 알고리즘 구현(다중 Redis 인스턴스 과반수 기반)
      • 기본 RLock = Redlock 아님 (이건 오해가 많음)

6.3” 락을 잡았다고 해서 절대 1개만 실행된다”는 보장은 없다

Redis 기반 락은 합의(etcd/ZooKeeper) 수준의 강한 안전성이 아니다.

  • 네트워크 분할, 장시간 GC stop-the-world, VM freeze 같은 상황에서 리스(TTL)가 만료되어 중복 실행이 발생할 수 있다는 비판/논쟁이 오래 존재한다.
  • Redlock 문서 자체도 “더 안전한 출발점”으로 제시

7. 실전적 관점

7.1 분산락과 비관적 락

분산락과 비관적 락을 같이 쓰기도 하는데. 하지만 “같이 써야 안전해서”가 아니라, 보호 범위가 다르기 때문에 쓰는 것이다.

  • 잘못 설계하면 중복이고, 잘 쓰면 계층적 방어다.
  • 분산락은 보조 수단이다.
구분비관적 락 (DB row lock)분산락 (Redis 등)
범위단일 DB 인스턴스여러 애플리케이션 인스턴스
대상특정 row논리적 작업 단위
강제력DB가 강제애플리케이션 계약
목적데이터 정합성동시 실행 제어
  • 비관적 락은 “데이터 무결성 보호”
  • 분산락은 “작업 직렬화”
    • 고TPS 핫스팟 완화
      • 분산락으로 상위 레벨에서 직렬화
      • DB 경합 줄임
      • 데드락 확률 감소
    • 순서가 중요하지 않은 작업이라면, 분산락 대신 “버킷(sharding) 기반 직렬화”로 경합을 크게 완화할 수 있다.

###

3.1 의사 결정 기준

  1. 충돌 확률은 얼마나 되는가?
    • 고충돌 핫스팟 시스템에서는 재시도 지옥에 빠질 수 있다.
  2. 충돌이 발생했을 때 재시도 비용은 얼마인가?
    • 비즈니스 비용, 지연 비용등에 대해서도 고려해야한다.
  3. 실패를 사용자에게 노출해도 되는가?
  4. 데이터가 보존 법칙을 가지는가? (잔액, 재고 등)
  5. 지연(latency) vs 정합성 중 무엇이 더 중요한가?
This post is licensed under CC BY 4.0 by the author.