557. 멀티 마스터 충돌 해결 - Last Writer Wins (LWW)

⚠️ 이 문서는 전 세계 여러 지역에 흩어져 있는 데이터베이스 노드들이 모두 쓰기(Write) 권한을 가지는 다중 마스터(Multi-Master) 아키텍처에서, 동일한 데이터(레코드)를 두 명의 사용자가 동시에 수정하려 할 때 발생하는 충돌(Conflict)을 시스템이 어떻게 기계적으로 합의하고 해결하는지를 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: 마스터-슬레이브 구조에서는 마스터가 1대이므로 충돌이 없지만, 멀티 마스터(카산드라, 다이나모DB 등)에서는 "누가 먼저 썼는가?"를 가려내어 하나의 승자만 남겨야 하는 치명적인 데이터 정합성 문제가 발생한다.
  2. 가치: 글로벌 서비스에서 사용자가 서울 마스터 서버와 뉴욕 마스터 서버에 각각 다른 값을 동시에 쓸 때, 복잡한 잠금(Lock) 없이 분산 노드 간의 데이터 동기화를 빠르고 단순하게(결정론적으로) 처리할 수 있게 해 준다.
  3. 기술 체계: '가장 늦게 쓰인 데이터가 승리한다'는 Last Writer Wins (LWW) 정책이 가장 널리 쓰이나, 이는 타임스탬프 동기화 오류 시 치명적인 데이터 유실을 부를 수 있어 벡터 클록(Vector Clock)이나 애플리케이션 레벨의 커스텀 병합 로직과 병행되기도 한다.

Ⅰ. 멀티 마스터(Active-Active)의 달콤함과 저주

글로벌 스케일아웃을 위해 여러 개의 마스터를 두는 순간, 지옥문이 열린다.

  1. 단일 마스터의 한계:
    • 마스터가 서울에 1대뿐이면, 뉴욕 사용자가 데이터를 쓸 때마다 태평양 해저 케이블을 건너와야 하므로 너무 느리다(Latency).
    • 이를 해결하기 위해 서울, 뉴욕, 런던에 각각 '쓰기 가능한' 마스터를 두는 것이 Active-Active(멀티 마스터) 아키텍처다.
  2. 동시 쓰기 충돌 (Concurrent Writes):
    • 철수가 서울 서버에 붙어서 내 이름=철수라고 썼고, 동시에 영희가 뉴욕 서버에 붙어서 똑같은 계정에 내 이름=영희라고 썼다.
    • 각 서버는 서로의 존재를 모른 채 0.1초 만에 Commit을 찍어주었다.
    • 1초 뒤, 서울 서버와 뉴욕 서버가 백그라운드로 서로의 데이터를 동기화(Gossip)하려다 충돌이 발생한다. "이 계정의 이름은 철수인가, 영희인가?"

📢 섹션 요약 비유: 회사에서 구글 독스(단일 마스터)로 문서를 쓸 때는 내가 글을 쓰면 남들이 즉시 볼 수 있어 안 부딪히지만, 멀티 마스터는 각자 USB에 엑셀 파일을 담아가서(각 지역 마스터) 집에서 잔뜩 수정한 뒤 다음 날 아침에 취합하려고 보니 같은 줄을 서로 다르게 고쳐놔서 누구 버전을 살려야 할지 대판 싸우는 것과 같습니다.


Ⅱ. Last Writer Wins (LWW)의 작동 원리

싸움을 말리는 가장 무식하지만 빠르고 명확한 룰이다.

  1. 타임스탬프 (Timestamp) 기반 승자 독식:
    • 데이터베이스는 클라이언트가 데이터를 쓸 때, 해당 레코드에 쓰기 시점의 시간($t$)을 밀리초(ms) 단위로 꼬리표처럼 붙여서 저장한다.
    • 충돌이 감지되면 두 서버는 서로의 꼬리표를 비교한다.
    • [서울: 이름=철수, t=10.005초] vs [뉴욕: 이름=영희, t=10.010초]
  2. 더 늦은(최신) 데이터가 이긴다:
    • 10.010초가 10.005초보다 0.005초 늦게 쓰였으므로 뉴욕 서버의 '영희'가 승리(Wins)한다.
    • 서울 서버의 '철수'라는 데이터는 가차 없이 버려지고 덮어써 진다(Overwritten). 전 세계 모든 노드는 최종적으로 '영희'라는 값을 가지게 되어 데이터가 일치(Eventual Consistency)하게 된다.

📢 섹션 요약 비유: 서부 시대 총잡이의 결투에서 무조건 '가장 늦게 총을 뽑은 사람(Last Writer)'이 이기는 것으로 룰을 정해두면, 누가 죽어야 할지(어떤 데이터가 삭제되어야 할지) 복잡한 재판을 열 필요 없이 초시계 기록만 보고 1초 만에 승패를 갈라버리는 빠르고 잔인한 시스템입니다.


Ⅲ. LWW의 치명적 결함과 보완 기술

시간을 믿는 자, 시간 때문에 망한다.

  1. 시간 동기화의 함정 (NTP의 한계):
    • LWW가 완벽하게 동작하려면 서울 서버와 뉴욕 서버의 '시계'가 마이크로초 단위까지 완벽하게 맞아야 한다.
    • 하지만 물리적 서버들의 시계는 시간이 지날수록 조금씩 어긋난다(Clock Drift). 서울 서버 시계가 뉴욕보다 실수로 1분 빠르게 설정되어 있다면, 서울에서 쓴 데이터가 사실상 뉴욕에서 쓴 최신 데이터보다 나중 시간으로 찍혀서 정당한 최신 데이터를 억울하게 덮어써 버린다. (데이터 유실 사고)
  2. 논리적 시계: 벡터 클록 (Vector Clock):
    • 물리적 시계(Time)를 믿지 말고, 각 노드에서 사건이 발생한 순서를 카운팅하는 논리적 시계(버전 번호)를 배열 형태로 기록하는 방식이다. [서울노드: v2, 뉴욕노드: v1]
    • DynamoDB 등은 벡터 클록을 통해 충돌을 감지한다.
  3. 애플리케이션 계층으로의 책임 전가:
    • 완벽한 병합이 불가능한 쇼핑몰 장바구니 같은 데이터의 경우, DB 엔진이 멋대로 LWW로 버리지 않고 **"충돌이 났으니, 클라이언트(앱) 네가 알아서 합쳐라"**라고 두 개의 데이터를 모두 앱으로 반환하여, 앱 개발자가 커스텀 로직(두 개를 더해버리거나 사용자에게 팝업을 띄움)으로 합치게 유도한다.

📢 섹션 요약 비유: LWW 방식은 각자의 손목시계 시간을 기준으로 승자를 정하는데, 한 명의 시계가 고장 나 있으면 승패가 조작되는 억울한 일(데이터 유실)이 생깁니다. 이를 막기 위해 시계 대신 서로 "내가 두 번째 쓴 거야", "난 첫 번째 쓴 건데"라는 순번 표(벡터 클록)를 들고 와서 대조하거나, 판사가 판결을 포기하고 "당사자들(애플리케이션)끼리 직접 합의해!"라고 던져버리는 방식을 씁니다.