스플릿 브레인(Split-Brain) 붕괴를 막는 분산 락(Distributed Lock) 시스템과 펜싱(Fencing) 토큰의 도입
결제 중복 출금이라는 치명적인 CS 인입 메일이 터진 날, 저희 팀의 분산 락(Distributed Lock) 서버 코드가 완벽히 무력화되었음을 인지하고 등골이 서늘해졌습니다. 원인은 서버 시계의 오차도, 네트워크 패킷의 증발도 아닌 JVM의 가비지 컬렉션(GC) 일시 정지 현상이었습니다.
다수의 마이크로서비스 인스턴스가 동일한 사용자 잔액을 변경하려 할 때, 우리는 Redis나 ZooKeeper를 통해 락(Lock)을 임대하여 상호 배제를 적용합니다. 만약 A 서버가 불의의 사고로 다운되어 락을 영원히 놓지 않는 데드락을 방지하기 위해 이 분산 락에는 필연적으로 파기 기한(Lease Time/TTL)이 설정됩니다. 그러나 이 TTL에 의존하는 설계에는 끔찍한 치명타, 이른바 시간 정지 버그가 도사리고 있습니다.
A 서버가 성공적으로 락을 획득하여 결제 시작 직전, JVM이 거대한 힙 공간을 청소하며 Stop-the-World 현상에 빠져 서버 프로세스 전체가 10초간 완전히 정지해 버렸습니다. 시간은 흘러 분산 락 서버 내에서 락의 TTL 기한은 만료되어 소멸했고, 이때 B 서버가 새롭게 접속해 다른 변경 트랜잭션을 잡고 무사히 잔고를 다 빼갔습니다. 잠시 후 GC 청소가 끝나 깨어난 A 서버는 락을 여전히 소유하고 있다고 착각한 채 뒤이어 같은 잔고 차감을 날려버린 무시무시한 스플릿 브레인(Split-Brain) 참사가 발발한 것입니다.
이 원천적인 시스템의 닫힌 시계 세계를 방어하기 위해 도입한 개념이 바로 펜싱 토큰(Fencing Token) 기법입니다. 락을 발급하는 ZooKeeper 트랜잭션은 단조 증가하는 절대적인 고유 번호(예: Token #33)를 락에 덧붙여 발급합니다. 쓰기 요청을 받는 백엔드 데이터베이스 엔티티 구조에서, 자신이 마지막으로 반영한 토큰보다 적은 번호(예: 뒤늦게 깬 A서버의 Token #33 시도와 달리 현재 DB의 락인 Token #34가 진행된 상태)의 쓰기는 모두 롤백 및 거부 처리하도록 설계 원칙을 바꾸었습니다. 인프라의 상태 공간은 언제나 무너질 수 있음을 인정하고, 제일 밑단 스토리지 계층에서 펜싱 경호벽을 치는 다중 겹 구조의 데이터 엔지니어링 미학이었습니다.
Related Posts
JVM JIT 컴파일러의 극단적 런타임 최적화: 탈출 분석(Escape Analysis)과 스칼라 치환의 마법
정적 컴파일 언어를 압도하는 자바 머신의 동적 스크립트 프로파일링 및 객체 힙 버림 최적화 기법.
리눅스 eBPF와 XDP를 활용한 커널 바이패스(Kernel Bypass) 초저지연 패킷 필터링 아키텍처
운영체제 네트워크 스택의 병목을 우회하여 디바이스 드라이버 레벨에서 직접 샌드박스 코드를 주입하는 eBPF의 혁명.
CRDT(Conflict-Free Replicated Data Type)의 벡터 클럭 병합 알고리즘: 협업 편집 환경의 무충돌 동기화
Operational Transformation의 중앙화된 복잡성을 허물어뜨리고 로컬 우선(Local-First) P2P 동시성 텍스트 병합을 수행하는 분산 구조 증명.