본문 바로가기
1. 개발/1.8. ActiveMQ

JDBC Persistence 사용 시 'Lease-based Locking'의 원리는?

by 엉짱 2026. 3. 24.
반응형

JDBC Persistence 사용 시 'Lease-based Locking'의 원리는?

엔터프라이즈 환경에서 메시지 브로커의 고가용성(High Availability, HA)을 확보하기 위해 가장 흔하게 채택하는 아키텍처 중 하나가 바로 관계형 데이터베이스(RDBMS)를 공유하는 'Shared Database HA' 방식입니다. 여러 대의 브로커가 하나의 데이터베이스를 바라보며, 오직 한 대의 브로커(Master)만이 활성화되어 메시지를 처리하고 나머지는 대기(Slave)하는 Active-Passive 구조를 이룹니다.

이 아키텍처의 가장 핵심적인 과제는 "어떻게 완벽하게 한 대의 브로커만 Master 권한을 쥐게 할 것인가?"입니다. 이를 제어하는 전통적인 방식의 치명적인 한계를 극복하기 위해 등장한 혁신적인 매커니즘이 바로 'Lease-based Locking(임대 기반 잠금)'입니다. 본 가이드에서는 이 임대 기반 잠금 방식이 어떻게 분산 환경의 장애(Split-Brain)를 방지하고 안정적인 페일오버(Failover)를 이끌어내는지 상세히 해부합니다.


1. 기존 'Exclusive Lock(배타적 잠금)'의 치명적인 한계

Lease-based Locking의 가치를 이해하려면 먼저 기존 데이터베이스 잠금 방식의 약점을 알아야 합니다.

기존의 브로커들은 데이터베이스의 특정 테이블(예: ACTIVEMQ_LOCK)에 대해 SELECT ... FOR UPDATE 구문을 사용하여 배타적 행 잠금(Row Lock)을 획득하는 방식을 사용했습니다.
정상적인 상황에서는 마스터 브로커가 잠금을 쥐고 있다가, 종료될 때 잠금을 해제하면 대기 중이던 슬레이브 브로커가 즉시 잠금을 획득하여 마스터로 승격됩니다.

문제 발생 시나리오 (좀비 락):
만약 마스터 브로커 장비의 네트워크 케이블이 갑자기 뽑히거나, OS 패닉으로 인해 브로커 프로세스가 비정상적으로 멈춰버린다면 어떻게 될까요?
브로커는 데이터베이스에 '잠금을 해제하겠다'는 신호를 보내지 못하고 죽어버립니다. 데이터베이스 서버 입장에서는 TCP 연결 타임아웃이 발생하기 전까지 해당 잠금을 계속 유지(Hold)하게 됩니다. 결국 마스터는 죽었는데 슬레이브들은 데이터베이스의 잠금이 풀리기만을 무한정 기다리게 되며, 시스템 전체가 마비되는 대참사가 발생합니다.


2. Lease-based Locking(임대 기반 잠금)의 핵심 동작 메커니즘

이러한 무한 대기 현상(좀비 락)을 원천적으로 차단하기 위해 고안된 것이 '시간' 개념을 도입한 Lease(임대) 방식입니다. 영구적인 소유권을 주장하는 대신, 특정 시간 동안만 권한을 빌리는(Lease) 것입니다.

A. 임대(Lease)의 획득과 유지 (Master의 역할)
데이터베이스의 락 테이블에는 잠금을 획득한 브로커의 ID와 함께 '임대 만료 시간(Expiration Time)'이라는 타임스탬프가 기록됩니다.
마스터로 승격된 브로커는 이 임대 기간(예: 30초)이 끝나기 전에 주기적으로 백그라운드 스레드를 깨워 데이터베이스의 만료 시간을 미래로 계속 연장(Update)합니다. 이것을 하트비트(Heartbeat) 또는 Keep-Alive 작업이라고 부릅니다.

B. 임대 만료의 감시와 승격 (Slave의 역할)
슬레이브 브로커들은 더 이상 데이터베이스의 락이 풀리기를 수동적으로 기다리지 않습니다. 주기적으로 락 테이블을 조회(Polling)하여 현재 기록된 '임대 만료 시간'을 확인합니다.
만약 마스터 브로커가 죽어서 일정 시간 동안 만료 시간을 연장하지 못했다면, 슬레이브가 테이블을 조회했을 때 '현재 시간 > 임대 만료 시간'인 상태가 됩니다. 슬레이브는 즉시 기존 마스터가 사망했다고 판단하고, 해당 테이블의 브로커 ID를 자신의 ID로 덮어쓰며 새로운 임대 시간을 기록하여 새로운 마스터로 승격합니다.


3. 데이터베이스 시스템 시간(DB Clock)에 대한 절대적 의존성

Lease-based Locking 아키텍처를 설계할 때 아키텍트가 반드시 명심해야 할 가장 중요한 대원칙이 있습니다.

분산 시스템에서는 브로커 1번, 브로커 2번, 그리고 데이터베이스 서버의 로컬 시간이 미세하게 다를 수 있습니다(Clock Drift). 만약 브로커 자신의 시스템 시간을 기준으로 만료 여부를 판단한다면, 시간 오차로 인해 두 브로커가 동시에 자신이 마스터라고 착각하는 스플릿 브레인(Split-Brain) 장애가 발생합니다.

따라서 Lease-based Locking은 오직 단 하나의 기준 시간, 즉 '데이터베이스 서버의 현재 시간'만을 신뢰하도록 설계되어 있습니다.
마스터 브로커가 임대 시간을 연장할 때나, 슬레이브 브로커가 만료 여부를 검사할 때 모두 데이터베이스의 내장 시간 함수(예: Oracle의 SYSDATE, MySQL의 CURRENT_TIMESTAMP)를 호출하여 판단합니다. 브로커 서버들의 시간이 아무리 엉망으로 꼬여있더라도, 데이터베이스 서버라는 단일 진실의 원천(Single Source of Truth)을 기준으로 동기화되므로 안전한 페일오버가 보장됩니다.


4. 설정 튜닝과 아키텍처적 트레이드오프

ActiveMQ 환경에서 이 기능을 활성화하려면 activemq.xml의 JDBC Persistence Adapter 설정에 useDatabaseLock="false" (기본 배타적 락 비활성화)와 leaseDatabaseLocker 빈(Bean)을 명시적으로 선언해야 합니다.

성능과 안정성을 위해 다음 세 가지 설정값의 밸런스를 정교하게 맞춰야 합니다.

  • lockAcquireSleepInterval (폴링 주기): 슬레이브가 만료 시간을 확인하기 위해 DB에 쿼리를 날리는 주기입니다. 너무 짧으면 DB에 엄청난 부하(Polling Overhead)를 주고, 너무 길면 마스터 장애 시 페일오버 복구 시간이 지연됩니다. (통상 10초 내외 권장)
  • leaseHolderId: 클러스터 내에서 각 브로커를 식별하는 고유한 ID입니다.
  • lockKeepAlivePeriod (하트비트 주기): 마스터가 임대 시간을 연장하는 갱신 주기입니다.

최적화 모범 사례 (Best Practice):
네트워크 지연이나 일시적인 브로커의 GC(Garbage Collection) 멈춤 현상을 고려하여, 마스터가 갱신하는 주기(Keep-Alive)는 슬레이브가 실패를 판단하는 폴링 주기보다 훨씬 짧아야 합니다. 통상적으로 하트비트는 35초마다 한 번씩 갱신하고, 슬레이브는 1015초 이상의 여유를 두고 만료 여부를 판단하도록 설계해야 억울하게 마스터 권한을 박탈당하는 플래핑(Flapping) 현상을 막을 수 있습니다.


5. 요약 및 결론

Lease-based Locking은 무한 대기라는 RDBMS 배타적 잠금의 고질적인 약점을 '시간 만료'라는 직관적인 개념으로 우아하게 해결한 메커니즘입니다. 데이터베이스만 튼튼하게 구축되어 있다면 별도의 Zookeeper나 etcd 같은 복잡한 코디네이터(Coordinator) 시스템을 도입하지 않고도 매우 안정적인 HA 환경을 구축할 수 있게 해주는 가장 강력하고 실용적인 인프라 설계 패턴입니다.

반응형