전자 지갑은 카드 정보나 현금, 암호화폐와 같은 자산을 디지털 형태로 저장하여 스마트폰이나 PC 등의 기기를 통해 편리하게 결제, 송금 및 관리할 수 있게 해주는 가상 계좌 시스템입니다. 이커머스에서 적립되는 포인트도 일종의 전자 지갑으로 볼 수 있습니다. 전자지갑 간 이체는 은행 간 이체보다 빠르며, 일반적으로 추가 수수료를 부과하지 않는다는 중요한 차이가 있습니다.
전자 지갑은 많은 TPS 를 요구하기 보다는 정확성이 중요한 서비스입니다. 그렇기 때문에, 이번 시간에는 분산 트랜잭션과 데이터 일관성 유지 전략에 대한 이야기를 많이 나누었습니다. 책에 나오는 이론적인 내용이 실제로는 어떻게 사용되고있는지 사례도 공유해보았습니다.
분산 트랜잭션 알아보기
[2PC(2-Phase Commit)와 그 한계]
2PC 는 분산 트랜잭션의 고전적인 해법입니다. 강한 정합성을 가지는 것이 특징입니다. 2PC 에서는 모든 노드가 준비 되었는지 확인한 뒤 최종 확정처리 합니다. 먼저 모든 노드가 준비 되었는지를 확인해야하는 이유는, 모두 준비가 되지 않은 상태에서 누구 하나라도 커밋이 완료되었다면, 데이터 일관성이 깨질 수 있기 때문입니다.
2PC 자체가 나쁜 것은 아닙니다. 하지만 서비스에서 사용이 어렵게 하는 이유는 2PC는 2단계 커밋단계에서 실패할 경우, 정합성이 맞춰질 때까지 무한 대기 상태에 돌입해야하기 때문입니다. 이는 서비스 전체의 마비로 이어질 수 있습니다.
이러한 위험성 때문에 2PC는 정산 시스템처럼 실시간성보다 정합성이 압도적으로 중요하고, 문제가 생겼을 때 시스템을 잠시 중단하고 복구하는 것이 용납되는 보수적인 영역에서만 제한적으로 활용됩니다.
[TCC(Try-Confirm-Cancel)]
TCC는 약간의 정합성을 포기한 대신, 실시간성 서비스에 사용할 수 있습니다. 먼저 Try 단계에서 자원을 예약한 후, 모두 성공하였을 경우 확정하고, 하나라도 실패하면 취소하여 데이터 일관성을 유지합니다. 여기서 취소는 보상의 개념으로 들어가게 됩니다.
Rollback 과 보상의 차이에 대해서 설명하자면, Rollback 은 커밋 이전의 상태이기 때문에 아직 데이터가 노출되지 않은 상태에서 되돌리는 것이라면, 보상은 이미 커밋이 완료된 상태(외부에서 데이터가 노출된 상태, 물리적으로 되돌릴 수 없는 상태)에서 이미 일어난 일의 반대 동작을 수행하여 결과적으로 0을 만드는 방식입니다.
따라서 2PC 와 달리 무한대기에 빠지거나 하지는 않아. 실시간성이 필요한 서비스에서도 사용할 수 있는 방식입니다. (약간의 정합성을 희생)
위 이미지에 보면 ‘NOP(No Operation)’ 이라는 처리들을 볼 수 있습니다. 이름만 들었을 때, 아무것도 하지 않을 것처럼 생겼는데 왜 화살표가 그려져 있지 생각이 드실수도 있을 것 같습니다. 실제 서비스를 운영하기 위해서는 단계별 상태를 관리하기 위한 데이터들이 오고가게 하는 것이 좋습니다. 도중에 서비스가 중단된다면, 복구할 수 있는 방법이 필요하기 때문입니다.
TCC 를 실제로 사용하는 사례를 한 회원분이 공유를 해주셨는데, Try 단계에서는 빠르고 가볍게 자원을 선점 한 후, Confirm 단계에서 무거운 작업들을 수행하는 방식으로 데이터 이중처리가 발생되지 않도록 하고 있으시다고 합니다. 또 가능하면 시스템에서 Cancel 처리를 하지만, 만약 시스템에서 Cancel 처리를 못해줬다면 배치를 통해서라도 데이터가 보상될 수 있도록 보장하고 있다는 내용도 설명해주셨습니다.
[Saga 패턴]
Saga 패턴은 여러 서비스에 걸친 트랜잭션을 로컬 트랜잭션의 연속으로 묶고, 실패 시 보상 트랜잭션(Compensating Transaction)을 통해 데이터의 일관성을 유지하는 디자인 패턴입니다. 마이크로서비스에서 사실상 표준처럼 사용되고 있습니다.
앞서 다룬 TCC 와의 차이는, TCC 는 자원을 선점하고 확정하는 2가지 단계로 되어있다는 부분이 차이가 되겠습니다. (TCC를 Saga 의 일종으로도 볼 수 있습니다.)
분산 조율 방식과 중앙 집중형 조율 방식이 있습니다.
분산 조율(Choreography):
분산 트랜잭션에 관련된 모든 서비스가 다른 서비스의 이벤트를 구독하여 작업을 수행합니다. 탈 중앙화 되었다는 것이 특징입니다.
중앙 집중형 조율(Orchestration):
하나의 조정자(coordinator)가 모든 서비스가 올바른 순서로 작업을 실행하도록 조율합니다. 보상도 마찬가지로 진행됩니다.
데이터가 꼬이는 근본적인 원인은 대개 '업데이트(Update)' 에 있습니다. 현재 잔액을 직접 수정하는 방식은 중간 단계의 이력을 상실하게 만들죠. 이를 해결하는 것이 이벤트 소싱입니다. 모든 상태 변화를 '삽입(Insert)'만으로 기록하는 것입니다.
이벤트 소싱은 재현성(Reproducibility)과 추적 가능성 측면에서 탁월한 방식입니다. 하지만 현실적인 제약도 있습니다. 매번 처음부터 모든 이벤트를 계산해 잔액을 산출하면 성능이 떨어집니다. 그래서 실무에서는 CQRS(명령과 조회 책임 분리)를 적용해 읽기 전용 데이터를 따로 만들거나, 특정 시점의 상태를 저장하는 스냅샷(Snapshot)을 활용해 계산 시간을 단축합니다.
책에서는 이벤트 소싱을 구현하기 위해 RocksDB 를 선택하였습니다. 전통적인 B-Tree 기반의 RDB 의 경우 데이터를 insert 할 때, 내부적으로 rebalancing이 발생될 수 있습니다. 그리고 랜덤 I/O가 발생되게 됩니다. 하지만 LSM 의 경우 먼저 데이터를 메모리에 쌓고, 이후에 디스크에서 순차적 쓰기를 진행하기 때문에, 이벤트 소싱에 어울리는 방법이 되겠습니다.
현실적인 이야기
책에서 최종적으로 제시한 설계안은 다음과 같았습니다.
이런 시스템 설계를 실제로 사용할 수 있을까요? 쉽지 않을지도 모르겠습니다. 충분한 레퍼런스가 생기기 전까지는 돈이 오고 가는 곳에서 섣불리 새로운 컨셉을 도전하기는 어렵기 때문입니다. 금융권이 여전히 오라클 RAC(Real Application Clusters) 같은 거대 솔루션에 의존하게 되는 이유라고 생각합니다. Oracle RAC은 공유 저장소 기반의 멀티 노드 클러스터링을 통해 분산된 노드 간의 완벽한 동기화로 금융권 수준의 높은 데이터 정합성과 트랜잭션 신뢰성을 보장합니다.
마무리
스터디에서 실제로 경험했던 이야기들을 포함하여 더 많은 이야기들을 함께 나눠보았지만, 리뷰글은 여기서 마무리 해보겠습니다.
이번장에서 나온 이야기를 마인드 맵으로 정리해보면 다음과 같습니다.
우리 스터디에서는 실제 사례들을 통해 문제를 어떻게 해결해 나갔을까 많은 고민을 해보고 이야기를 나눠보고 있습니다. 이번주도 다들 열심히 참여해주셔서 더 풍성한 스터디가 될 수 있었습니다.
✅ 직접 스터디를 개설해보고 싶은 분이 계시다면, K-DEVCON에서 운영을 도와드리겠습니다. 데브콘의 '랩짱'에 도전하여 커뮤니티 성장을 함께 이끌어주세요! K-DEVCON Linkedin 에 DM 부탁드리겠습니다😉