557. 멀티 마스터 충돌 해결 - Last Writer Wins (LWW)
⚠️ 이 문서는 전 세계 여러 지역에 흩어져 있는 데이터베이스 노드들이 모두 쓰기(Write) 권한을 가지는 다중 마스터(Multi-Master) 아키텍처에서, 동일한 데이터(레코드)를 두 명의 사용자가 동시에 수정하려 할 때 발생하는 충돌(Conflict)을 시스템이 어떻게 기계적으로 합의하고 해결하는지를 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 마스터-슬레이브 구조에서는 마스터가 1대이므로 충돌이 없지만, 멀티 마스터(카산드라, 다이나모DB 등)에서는 "누가 먼저 썼는가?"를 가려내어 하나의 승자만 남겨야 하는 치명적인 데이터 정합성 문제가 발생한다.
- 가치: 글로벌 서비스에서 사용자가 서울 마스터 서버와 뉴욕 마스터 서버에 각각 다른 값을 동시에 쓸 때, 복잡한 잠금(Lock) 없이 분산 노드 간의 데이터 동기화를 빠르고 단순하게(결정론적으로) 처리할 수 있게 해 준다.
- 기술 체계: '가장 늦게 쓰인 데이터가 승리한다'는 Last Writer Wins (LWW) 정책이 가장 널리 쓰이나, 이는 타임스탬프 동기화 오류 시 치명적인 데이터 유실을 부를 수 있어 벡터 클록(Vector Clock)이나 애플리케이션 레벨의 커스텀 병합 로직과 병행되기도 한다.
Ⅰ. 멀티 마스터(Active-Active)의 달콤함과 저주
글로벌 스케일아웃을 위해 여러 개의 마스터를 두는 순간, 지옥문이 열린다.
- 단일 마스터의 한계:
- 마스터가 서울에 1대뿐이면, 뉴욕 사용자가 데이터를 쓸 때마다 태평양 해저 케이블을 건너와야 하므로 너무 느리다(Latency).
- 이를 해결하기 위해 서울, 뉴욕, 런던에 각각 '쓰기 가능한' 마스터를 두는 것이 Active-Active(멀티 마스터) 아키텍처다.
- 동시 쓰기 충돌 (Concurrent Writes):
- 철수가 서울 서버에 붙어서
내 이름=철수라고 썼고, 동시에 영희가 뉴욕 서버에 붙어서 똑같은 계정에내 이름=영희라고 썼다. - 각 서버는 서로의 존재를 모른 채 0.1초 만에
Commit을 찍어주었다. - 1초 뒤, 서울 서버와 뉴욕 서버가 백그라운드로 서로의 데이터를 동기화(Gossip)하려다 충돌이 발생한다. "이 계정의 이름은 철수인가, 영희인가?"
- 철수가 서울 서버에 붙어서
📢 섹션 요약 비유: 회사에서 구글 독스(단일 마스터)로 문서를 쓸 때는 내가 글을 쓰면 남들이 즉시 볼 수 있어 안 부딪히지만, 멀티 마스터는 각자 USB에 엑셀 파일을 담아가서(각 지역 마스터) 집에서 잔뜩 수정한 뒤 다음 날 아침에 취합하려고 보니 같은 줄을 서로 다르게 고쳐놔서 누구 버전을 살려야 할지 대판 싸우는 것과 같습니다.
Ⅱ. Last Writer Wins (LWW)의 작동 원리
싸움을 말리는 가장 무식하지만 빠르고 명확한 룰이다.
- 타임스탬프 (Timestamp) 기반 승자 독식:
- 데이터베이스는 클라이언트가 데이터를 쓸 때, 해당 레코드에 쓰기 시점의 시간($t$)을 밀리초(ms) 단위로 꼬리표처럼 붙여서 저장한다.
- 충돌이 감지되면 두 서버는 서로의 꼬리표를 비교한다.
[서울: 이름=철수, t=10.005초]vs[뉴욕: 이름=영희, t=10.010초]
- 더 늦은(최신) 데이터가 이긴다:
- 10.010초가 10.005초보다 0.005초 늦게 쓰였으므로 뉴욕 서버의 '영희'가 승리(Wins)한다.
- 서울 서버의 '철수'라는 데이터는 가차 없이 버려지고 덮어써 진다(Overwritten). 전 세계 모든 노드는 최종적으로 '영희'라는 값을 가지게 되어 데이터가 일치(Eventual Consistency)하게 된다.
📢 섹션 요약 비유: 서부 시대 총잡이의 결투에서 무조건 '가장 늦게 총을 뽑은 사람(Last Writer)'이 이기는 것으로 룰을 정해두면, 누가 죽어야 할지(어떤 데이터가 삭제되어야 할지) 복잡한 재판을 열 필요 없이 초시계 기록만 보고 1초 만에 승패를 갈라버리는 빠르고 잔인한 시스템입니다.
Ⅲ. LWW의 치명적 결함과 보완 기술
시간을 믿는 자, 시간 때문에 망한다.
- 시간 동기화의 함정 (NTP의 한계):
- LWW가 완벽하게 동작하려면 서울 서버와 뉴욕 서버의 '시계'가 마이크로초 단위까지 완벽하게 맞아야 한다.
- 하지만 물리적 서버들의 시계는 시간이 지날수록 조금씩 어긋난다(Clock Drift). 서울 서버 시계가 뉴욕보다 실수로 1분 빠르게 설정되어 있다면, 서울에서 쓴 데이터가 사실상 뉴욕에서 쓴 최신 데이터보다 나중 시간으로 찍혀서 정당한 최신 데이터를 억울하게 덮어써 버린다. (데이터 유실 사고)
- 논리적 시계: 벡터 클록 (Vector Clock):
- 물리적 시계(Time)를 믿지 말고, 각 노드에서 사건이 발생한 순서를 카운팅하는 논리적 시계(버전 번호)를 배열 형태로 기록하는 방식이다.
[서울노드: v2, 뉴욕노드: v1] - DynamoDB 등은 벡터 클록을 통해 충돌을 감지한다.
- 물리적 시계(Time)를 믿지 말고, 각 노드에서 사건이 발생한 순서를 카운팅하는 논리적 시계(버전 번호)를 배열 형태로 기록하는 방식이다.
- 애플리케이션 계층으로의 책임 전가:
- 완벽한 병합이 불가능한 쇼핑몰 장바구니 같은 데이터의 경우, DB 엔진이 멋대로 LWW로 버리지 않고 **"충돌이 났으니, 클라이언트(앱) 네가 알아서 합쳐라"**라고 두 개의 데이터를 모두 앱으로 반환하여, 앱 개발자가 커스텀 로직(두 개를 더해버리거나 사용자에게 팝업을 띄움)으로 합치게 유도한다.
📢 섹션 요약 비유: LWW 방식은 각자의 손목시계 시간을 기준으로 승자를 정하는데, 한 명의 시계가 고장 나 있으면 승패가 조작되는 억울한 일(데이터 유실)이 생깁니다. 이를 막기 위해 시계 대신 서로 "내가 두 번째 쓴 거야", "난 첫 번째 쓴 건데"라는 순번 표(벡터 클록)를 들고 와서 대조하거나, 판사가 판결을 포기하고 "당사자들(애플리케이션)끼리 직접 합의해!"라고 던져버리는 방식을 씁니다.