5장. 복제

2025. 9. 14. 01:07·Book/데이터 중심 애플리케이션 설계

❐ 0.Description


복제 (Replication)
  • 네크워트로 연결된 여러 장비에 동일한 데이터의 복사본을 유지한다는 의미
  • 왜 필요한가
    • 지리적으로 사용자와 가깝게 데이터를 유지해 지연 시간을 줄인다.
    • 시스템의 일부에 장애가 발생해도 지속적으로 동작할 수 있게 해 가용성을 높인다.
    • 읽기 질의를 제공하는 장비의 수를 활장해 읽기 처리량을 늘린다.
  • 복제는 고려해야 할 트레이드 오프가 있음.
    • ex. 동기식 복제와 비동기식 복제 중 어떤 것을 사용할지 등등...

 

노드 간 변경을 복제하기 위한 세 가지 인기 있는 알고리즘
(앞으로 살펴 볼 내용)
  • 단일 리더(single-leader)
  • 다중 리더(multi-leader)
  • 리더 없는(leaderless)

 

 

 

 

 

❐ 1. 리더와 팔로워


🌀 1-1. 리더와 팔로워

복제 서버
  • 데이터베이스의 복사본을 저장하는 각 노드
  • 데이터베이스의 모든 쓰기 작업은, 각각의 복제 서버에서도 이루어져야 함.

 

모든 복제 서버에 모든 데이터가 동일하게 있다는 사실을 어떻게 보장할 수 있을까?
  • 리더 기반 복제 (leaders-based replication)
    • 능동/수동(active/passive) 복제
    • 마스터-슬레이브(master-slave) 복제

 

리더와 팔로워 
  • 복제 서버 중 하나를 리더로 지정해야 함.
    • 클라이언트가 데이터베이스에 쓰기 할 때, 리더에게 요청을 보내야 함.
  • 다른 복제 서버는 팔로워라고 함 
    • read-replica, slave, secondary, hot standby
  • 보통 복제는 매우 빠름
    • 팔로워가 장애를 복구 중이거나 문제가 있을 때 쫌 오래 걸릴 순 있음.

 

Leader-based Replication 쓰기 플로우
  • 리더가 로컬 저장소에 새로운 데이터를 기록할 때마다
  • 데이터 변경을 replication log(복제 로그)나 change stream(변경 스트림)의 일부로 팔로워에게 전송
  • 각 팔로워가 리더로부터 로그를 받으면 리더가 처리한 것과 동일한 순서로 모든 쓰기를 적용

 

  • 읽기를 할 때는, 리더 또는 임의의 팔로워에게 질의할 수 있음.

 

 

🌀 1-2. 동기실 대 비동기식 복제

동기식 & 비동기식

  • 동기식 (팔로워1)
    • 모든 팔로워가 동기식인 상황은 비현실적임
      • 보통은 하나의 팔로워만 동기식, 나머지는 비동기식
      • 동기식 팔로워에 문제가 생기면, 다른 비동기식 팔로워 중 하나가 동기식이 됨.
              ➔ 적어도 리더 또는 하나의 동기 팔로워에 데이터의 최신 복사본이 있음을 보장 (반동기식)
    • 장점 - 팔로워가 리더와 일관성 있게 최신 데이터 복사본을 가지는 것이 보장됨.
    • 단점 - 동기 팔로워가 응답하지 않는다면, 쓰기가 처리될 수 없음.
      • 리더는 모든 쓰기를 block하고 동기 복제 서버가 다시 사용할 수 있을 때 까지 대기해야 함.
  • 비동기식(팔로워2)
    • 보통 리더 기반 복제는 완전히 비동기식으로 구성
    • 이런 설정은 모든 팔로워가 잘못되더라도 리더가 쓰기 처리를 계속할 수 있다는 것임

 

 

🌀 1-3. 새로운 팔로워 설정

새로운 팔로워가 리더와 같은 상태인지 어떻게 보장?
  • 단순히 데이터 파일을 복사하는 방식으로는 충분하지 않음.
    • 데이터베이스는 유동적(쓰기 작업)이기 때문.
    • 따라서 복사본이 최신 상태와 일치하지 않을 수 있음.
  • 개념적인 새로운 팔로워 설정 과정
    • (가능하다면) 전체 DB를 잠그지 않고, 리더의 스냅숏을 일정 시점에 가져옴
    • 스냅숏을 새로운 팔로워 노드에 복사
    • 팔로워는 리더에 연결해 스냅숏 이후 발생한 모든 데이터 변경을 요청
      • 스냅숏이 리더의 복제 로그의 정확한 위치와 연관돼야 함 
      • 여기서 위치를 MySQL에서는 이진 로그 좌표(binary-coordinate)라 부름
    • 팔로원가 스냅숏 이슈 데이터 변경의 backlog를 모두 처리했을 때, "caught up" 이라고 부름

 

 

 

🌀 1-4. 노드 중단 처리

리더 기반 복제에서 고가용성(HA)는 어떻게 달성할 수 있을까?

 

팔로워 장애: 따라잡기(Catch-up) 복구
  • 각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관
    • 먼저 보관된 로그에서 결함이 발생하기 직전의 트랜잭션을 알아냄
    • 이렇게 하면 팔로워는 리더에 연결해, 끊어진 동안 발생한 변경을 모두 요청할 수 있음. 

 

리더 장애 : 장애 복구(Failover)
  1. 리더가 장애인지 판단
    • 대부분 시스템에서 장애 판단의 기준은 '타임아웃'
    • 여기서 적당한 타임아웃은 얼마일까?
      • 너무 길면: 장애 복구가 늦어짐.
      • 너무 짧으면: 일시적 네트워크 지연에도 리더가 교체되어 혼란 발생.
  2. 새로운 리더 선택
    • 선출 과정을 통해 새로운 리더 임명
    • 모든 노드한테 동의를 얻어야 함 (새롭게 선정된 리더에 대한)
  3. 새로운 리더 사용을 위해 시스템을 재설정

 

장애 복구 과정은 잘못될 수 있는 것 투성이다.
  1. 비동기식 복제의 한계
    • 새로운 리더를 선출할 때, 이전 리더가 실패하기 직전에 수신하지 못한 쓰기가 있을 수 있음. (데이터 손실)
    • 단순히 이전 리더의 복제되지 않은 쓰기를 폐기하는 방식이 흔히 쓰임.
  2. 쓰기 폐기(Discarding) 방법의 위험
    • DB 외부 저장소(예: MySQL에서 자동 증가 키, Redis 같은 캐시)를 함께 사용할 때 특히 위험.
    • 새로운 리더가 이전 리더의 키 생성 상태를 모르면 불일치 발생.
    • MySQL 팔로워가 리더로 승격 → 자동 증가 키 사용 → 기존 리더보다 작은 키 값이 재사용됨.
  3. 스플릿 브레인 문제
    • 두 노드가 모두 자신이 리더라고 믿는 경우
    • 충돌을 해소하는 과정을 거치지 않으면, 데이터가 유실되거나 오염됨

 

 

➔ 이 문제에 대한 쉬운 해결책은 없음. 수동으로 장애 복구를 수행하는 방식을 선호하는 팀도 있음.

 

 

 

🌀 1-5. 복제 로그 구현

구문 기반 복제 (Statement-based)
  • 합리적인 것 같지만, 복제가 깨질 수 있는 다양한 사례가 있음
    • NOW(), RAND()
    • 자동증가 컬럼
    • 부수 효과를 가진 구문
  • 해결법
    • 리더는 구문을 기록할 때, 모든 비결정적 함수 호출을 고정 값을 반환하게 끔 대체
      • 비결정적 함수(nondeterministic function) = 실행할 때마다 결과가 달라질 수 있는 함수
    • 근데 엣지 케이스가 너무 많음
  • MySQL은 구문에 비결정성이 있다면 기본적으로 로우 기반 복제로 변경

 

 

쓰기 전 로그 배송 (Write-ahead log (WAL) shipping)
  • 완전히 동일한 로그를 사용해 다른 노드에서 복제 서버를 구축할 수 있음.
  • 리더는 디스크에 로그를 기록하는 일 외에도 팔로워에게 네트워크로 로그를 전송하기도 함.
  • 이 복제 방식은 PostgreSQL과 Oracle 등에서 사용
  • 단점 - 로그가 제일 저수준의 데이터를 기술한다는 점
    • WAL은 어떤 디스크 블록에서 어떤 바이트를 변경했는지와 같은 상세 정보를 포함함
    • 이렇게 하면 복제가 저장소 엔진과 밀접하게 엮임
    • 데이터베이스가 저장소 형식을 다른 버전으로 변경한다면,
            대개 리더와 팔로워의 데이터베이스 소프트웨어 버전을 다르게 실행할 수 없음.
  • WAL shipping은 복제 프로토콜이 버전의 불일치를 허용하지 않음
    • 따라서, 버전 업그레이드할 때 중단 시간이 필요함

 

 

논리적(row-based) 로그 복제

 

  • 복제를 위해 저장소 엔진이 내부적으로 사용하는 물리적 데이터 표현 대신, 논리적 로그(logical log) 를 사용
    • 예를 들어 InnoDB, RocksDB 같은 저장소 엔진은 자기만의 데이터 저장 형식(물리적 표현) 을 가짐.
    • 그런데 이 엔진 내부의 방식 그대로 복제 로그를 만들면?
      • 팔로워도 같은 엔진을 써야 하고, 버전이 달라도 문제가 생김.
  • MySQL의 이진 로그는 이 접근 방식을 사용함
  • 논리적 로글르 사용함으로써 하위 호환성을 더 쉽게 유지할 수 있음.
  • 논리적 로그는 애플리케이션과의 연동에도 적합.
    • 예: 오프라인 분석, 검색 인덱스 갱신, 데이터 웨어하우스 전송.
  • 이 기술을 변경 데이터 캡처(Change Data Capture, CDC) 라고 부름

 

 

 

트리거 기반 복제 
  • 아래의 케이스에서는 복제를 애플리케이션 층으로 옮겨야 함
    • 데이터의 서브셋만 복제하기
    • 데이터베이스를 다른 종류의 데이터베이스로 복제하기
    • 충돌해소 로직이 필요한 경우
  • 많은 관계형 데이터베이스에서 사용할 수 있는 기능인 트리거나 스토어드 프로지저를 사용
  • 트리거는 사용자 정의 애플리케이션 코드르 등록할 수 있게 함
    • 데이터베이스 시스템에서 데이터가 변경되면(쓰기TX) 자동으로 실행
  • 일반적으로 다른 복제 방식보다 많은 오버헤드가 있음
    • 하지만 유연성 때문에 매우 유용함

 

 

 

 

❐ 2. 복사 지연 문제


🌀 2-0. 복사 지연 문제

  • 읽기 확장(read-scailing) 아키텍처에서는 간단히 팔로워를 더 추가함으로써
    • 읽기 전용 요청을 처리하기 위한 용량을 늘릴 수 있음. 
    • 하지만, 이 방식은 실제로는 비동기식 복제에서만 동작함
  • 아쉽게도 비동기식 팔로워에서 데이터를 읽으면 데이터베이스에 불일치가 발생

 

 

🌀 2-1. 자신이 쓴 내용 읽기

비동기식 복제에서 생기는 첫 번째 이상 현상

  • 사용자가 쓰기를 수행한 직후 데이터를 본다면 새로운 데이터는 아직 복제 서버에 반영되지 않을 수 있음.
  • 이러한 행위는 사용자에게 불만족스러울 동작임
  • 이런 상황에서는 쓰기 후 읽기 일관성이 필요함.

 

쓰기 후 읽기 일관성을 구현하는 다양한 기법
  1. 사용자가 수정한 내용을 읽을 때는 리더에서 읽기. 그 밖에는 팔로워에서 읽기
  2. 다른 기준을 사용해서 리더에서 읽을지 말지 결정 (ex. 마지막 갱신 시간)
  3. 가장 최근 쓰기의 타임스탬프를 사용하기(논리적 타임스탬프 or 실제 시간)
  4. 리더가 제공해야 하는 모든 요청은 리더가 포함된 데이터센터로 라우팅

 

디바이스 간(cross-device) 쓰기 후 읽기 일관성을 보장시 고려해야 할 것
  1. 마지막 갱신 타임스탬프 관리 어려움
    • 단일 디바이스에서는 "내가 마지막으로 쓴 시점"을 기억하면 됨.
    • 하지만 여러 디바이스에서는 한 기기에서 쓴 데이터를 다른 기기는 알 수 없음.
    • 따라서 이 메타데이터(사용자의 마지막 쓰기 시간) 를 중앙에서 관리해야 함.
  2. 데이터센터 분산 문제
    • 복제 서버가 여러 데이터센터에 분산돼 있으면, 서로 다른 디바이스 요청이 다른 데이터센터로 갈 수 있음.
    • 이 경우 각 요청이 서로 다른 데이터센터로 라우팅될 수 있음.
    • 일관성을 보장하려면 사용자 요청을 동일한 데이터센터로 라우팅해야 함.

 

 


🌀 2-3. 단조 읽기 (Monotonic Reads)

비동기식 복제에서 생기는 두 번째 이상 현상

  • 시간이 거꾸로 흐르는 현상(변경 전 데이터를 보는 경우?)

 

단조 읽기
  • 단조 읽기는 이런 종류의 이상 현상이 발생하지 않음을 보장함.
  • 강한 일관성 보다는 약함. 하지만 최종적 일관성보다는 강함

 

어떻게 단조 읽기를 구현할까?
  • 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게끔 하는 것
  • 예를 들어 사용자 ID의 해시를 기반으로 복제 서버를 선택하기
  • 하지만 선택한 복제 서버가 고장나면? 다른 복제 서버로 재라우팅할 수 있어야 함.

 

 


🌀 2-4. 일관된 순서로 읽기

비동기식 복제에서 생기는 세 번째 이상 현상: 

  • 인과성의 위반 우려(violation of causality)
    • 쉽게 말해서 앞뒤가 안맞는 것. (원인과 결과의 순서가 안맞음)
    • 위 이미지에서 관찰자에게는 케이크 부인이 푼스 씨가 물어보기 전에 질문에 대답한 것 처럼 보임.

 

어떻게 이런 현상을 방지할 수 있을까?
  • 일관된 순서로 읽기(Consistent Prefix Read)
    • 이것은 일련의 쓰기가 특정 순서로 발생한다면,
            이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰여진 내용을 보게됨을 보장함
  • 이런 문제는 파티셔닝(샤딩)된 데이터베이스에서 발생하는 특징적인 문제
    • 분산 DB에서 섯로 다른 파티션은 독립적으로 동작하므로 쓰기의 전연 순서는 없음.
    • 즉, 사용자가 DB에서 읽을 때 예전 상태의 일부와 새로운 상태의 일부를 함께 볼 수 있음
    • 이 경우 한 가지 해결책은 서로 인관성이 있는 쓰기가 동일한 파티션에 기록되게끔 하는 것

 

 

🌀 2-5. 복제 지연을 위한 해결책

  • 실제로는 비동기 복제인데, 마치 동기 복제인 것처럼 가정하고 시스템을 설계하면?
    • 나중에 반드시 문제가 생김
  • 애플리케이션 코드가 강력한 보장을 제공하는 방법이 있긴한데 엄청 복잡함
  • 그래서 트랜잭션이 있음.

 

 

 

 

 

 

❐ 3. 다중 리더 복제


🌀 3-0. 다중 리더 복제

리더 기반 복제의 주요 단점
  • 리덛가 하나만 존재하고 모든 쓰기는 해당 리더를 거쳐야 한다는 부분
  • 어떠한 이유에서 리더에 연결할 수 없다면? 데이터베이스에 쓰기를 할 수 없음.

 

다중 리더 설정
  • 쓰기를 허용하는 각 노드를 하나 이상 두는 것.
  • 쓰기 처리를 하는 각 노드는 데이터 변경을 다른 모든 노드에 전달해야 함.
  • "마스터 마스터" or "액티브/액티브" 복제라고도 함
  • 각 리더는 다른 리더의 팔로워 역할도 겸함

 

🌀 3-1. 다중 리더 복제의 사용 사례

다중 데이터센터 운영

  • 각 데이터 센터마다 리더가 있을 수 있음.
  • 각 데이터 센터 내에는 보통의 리더-팔로워 복제를 사용
  • 데이터 센터 간에는 각 데이터 센터의 리더가 다른 데이터센터의 리더에게 변경 사항을 복제

 

멀티 데이터센터 환경에서, 단일(single) 리더 복제 vs 다중(multi-leader) 리더 복제
  1. 성능 
    Single-leader Multi-leader
    » 모든 쓰기는 인터넷을 통해 리더가 있는 데이터 센터로
        이동해야 함.
    » 이것은 쓰기에 지연 시간을 상당히 늘리는 원인
    » (초반에는) 여러 데이터 센터를 갖는 목적에도 위배 
    » 모든 쓰기는 로컬 데이터센터에서 처리한 다음
       비동기 방식으로 다른 데이터센터에 복제
    » 따라서, 데이터센터 간 네트워크 지연은 감춰짐
    » 사용자가 느끼는 체감은 더 좋음
  2. 데이터 중단 내성
    Single-leader Multi-leader
    » 리더가 있는 데이터센터가 고장나면 
        장애 복구를 위해 다른 데이터센터에서 한 팔로워를 리더로 승진
    » 각 데이터센터는 다른 데이터센터와 독립적으로 동작하고
        고장 난 데이터센터가 온라인으로 돌아왔을 때, 복제를 catch-up
  3. 네트워크 문제 내성
    Single-leader Multi-leader
    » 데이터센터 내 연결의 쓰기는 동기식
    » 때문에 데이터센터 내 연결 문제에 매우 민감 
    » 네트워크 문제에 보다 잘 견딤 (비동기 방식 사용하기 때문)
    » 일시적인 네트워크 중단에도 쓰기 처리는 진행되기 때문임

 

다중 리더 복제의 단점
  • 다중 리더 복제는 많은 데이터베이스에서 다소 사후적으로(retrofitted) 추가된 기능
  • 동일한 데이터를 다른 두 개의 데이터센터에서 동시에 변경할 수 있음. (동시성 이슈)
  • 미묘한 설정상의 실수나 다른 데이터베이스 기능과의 뜻밖의? 상호작용이 있음.
    • 자동 증가 키 (AUTO_INCREMENT)
      • 각 리더가 독립적으로 키를 생성 → 중복 키 충돌 위험.
    • 트리거 (Trigger)
      • 리더 간 동일 트리거가 중복 실행 → 같은 데이터가 여러 번 반영될 수 있음.
    • 무결성 제약 조건 (Integrity constraints)
      • 한 리더에서는 유효하지만, 다른 리더에서는 제약 조건 위반 발생 가능.

 

➔ 이런 이유로, 다중 리더 복제는 위험한 영역으로 간주되며 가능하다면 피하는 것이 좋다.

 

 

오프라인 작업을 하는 클라이언트
  • 인터넷이 끊어진 동안 애플리케이션이 계속 동작해야 하는 경우, 다중 리더 복제가 적절함.
  • 예를 들어, 캘린더 앱을 생각
    • 노트북에서 회의 일정을 추가하면 → 클라우드 서버를 거쳐서 → 스마트폰과 태블릿의 달력에도 복제됨.
    • 이 과정이 비동기적 다중 리더 복제이며, 우리가 흔히 말하는 “동기화(sync)”.

 

협업 편집 (Collaborative editing, 동시 편집? 같은 느낌인듯)
  • Real-time collaborative editing applications
    • 동시에 여러 사람이 문서를 편집할 수 있는 애플리케이션
  • 일반적으로 협업 편집을 데이터베이스 복제 문제로 생각하지 않음.
    • 내부적으로 보면 여러 복제본(replica)이 동시에 수정되고 동기화되는 문제임
    • 따라서 앞서 다룬 오프라인 편집이나 멀티 리더 복제와 본질적으로 같은 속성을 갖는 것임
  • 편집 충돌이 없음을 보장하려면 문서의 lock을 얻어야 함.
    • 이러한 협업 모델은 리더에서 트랜잭션을 수행하는 단일 리더 복제와 동일하다.
    • 다른 사람의 작업이 다 끝날 때 까지 기다려야하기 때문

 

 

🌀 3-2. 쓰기 충돌 다루기 (Handling Write Conflicts)

다중 리더 복제의 가장 큰 문제. 쓰길 충돌

  • 위와 같이 동일한 레코드를 동시에 갱신하면, 변경을 비동기로 복제할 때 충돌을 감지함.

 

[충돌 감지]
동기 vs 비동기 
  • 다일 리더의 매커니즘
    • 첫 번째 쓰기가 완료될 때 까지 두 번째 쓰기 차단
    • (또는) 두 번째 쓰기 트랜잭션을 중단해 사용자가 쓰기를 재시도하게 함.
  • 다중 리더의 매커니즘
    • 두 쓰기는 모두 성공하며 충돌은 이후 특정 시점에서 비동기로만 감지
    • 이때 사용자에게 충돌을 해소하게끔 요청하면 너무 늦을 수 있음.

 

충돌 회피
  • 충돌을 처리하는 제일 간단한 방법
  • 특정 레코드의 모든 쓰기가 동일한 리더를 거치도록 애플리케이션이 보장하는 것

 

일관된 상태 수렴 (Converging toward a consistent state)
  • 단일 리더 매커니즘
    • 모든 쓰기는 리더가 정해진 순서대로 처리됨.
    • 같은 필드에 여러 번 업데이트가 일어나면 → 마지막 쓰기(last write) 가 최종 값이 됨.
    • 순서가 하나로 정해져 있으므로 애매함이 없음.
  • 다중 리더 매커니즘
    • 각 리더가 독립적으로 쓰기를 처리하므로 전역적으로 정해진 순서가 없음.
    • 예시:
      • 리더 1: title = B → title = C
      • 리더 2: title = C → title = B
    • 서로 순서가 다름 → “어느 게 맞는 값인지” 명확하지 않음.
  • 문제점
    • 각 리더가 자기가 본 순서대로만 적용하면,
      • 리더 1의 최종 값 = C
      • 리더 2의 최종 값 = B
    • → 일관성 깨짐 (replicas마다 값이 다름).
  • 해결책
    • 모든 복제본(replica)이 결국 동일한 최종 값에 도달해야 함.
    • 이를 수렴적(convergent) 충돌 해결이라고 함.
    • "모든 복제본(replica)이 시간이 지나면서 같은 상태로 수렴한다"는 의미

 

 

수렴 충돌을 해소하는 다양한 방법
  1. 고유 ID 부여하고, 가장 높은 ID를 가진 쓰기를 고르기
    • 타임스탬프를 사용하는 경우를 최종 쓰기 승리(last write wins, LWW)라고 함
    • 대중적이긴 한데, 데이터 유실 위험이 있음. 
  2. 복제 ID 기반 우선순위
    • 높은 ID가 항상 우선 적용 
    • 이것도 데이터 유실 위험 있음
  3. 어떻게든(Somehow) merge
    • 예를 들어, 사전 순으로 정렬한 후 연결
  4. 모든 정보를 보존
    • 대신에 애플리케이션에서 충돌을 해소해야 함.

 

사용자 정의 충돌 해소 로직 
  • 쓰기 수행 중 충돌 해소 코드 실행 되는 경우
    • 충돌 감지하자마자 충돌 핸들러 호출
    • 백그라운드 프로세스에서 빠르게 실행되어야 함.
  • 읽기 수행 중 충돌 해소 코드 실행 되는 경우
    • 충돌을 감지하면 모든 충돌 쓰기를 저장

 

 

자동 충돌 해소
  • 충돌 해결 규칙은
    • 금방 복잡해질 수 있고,
    • 커스텀 코드(custom code)는 오류가 발생하기 쉬움
  • 아마존의 충돌 해결 로직은 장바구니에 추가된 아이템은 유지했지만, 제거된 아이템은 유지하지 않음.
    • 따라서 고객들은 장바구니에서 분명히 제거했던 물품이 다시 나타나는 상황이 있었음.
  • 흥미로운? 연구
    1. 충돌 없는 복제 데이터타입(Conflict-free Replicated Data Types, CRDTs)
      • 여러 사용자가 동시에 데이터를 수정해도 자동으로 충돌을 해결할 수 있는 특별한 자료구조 집합.
      • 이중(two-way) merge 사용
    2. 병합 가능한 영속 데이터 구조(Mergeable persistent data structure)
      • 명시적으로 히스토리를 추적하고, 삼중 병합 함수(three-way merge functino)를 사용
    3. 운영 변환(Operational transformation)
      • 협업 편집 애플리케이션의 충돌 해소 알고리즘

 

 

 

 

🌀 3-3. 다중 리더 복제 토폴로지

복제 토폴로지(Replication topology)
  • 복제 토폴로지는 쓰기가 한 노드에서 다른 노드로 전파되는 통신 경로를 설명(describe)한 것
  • 두 리더가 있다면, 가능한 토폴로지는 단 한 개 

 

다양한 토폴로지에 대해서

  • (a) : 원형 토폴로지
    • MySQL은 기본적으로 원형 토폴로지 사용
    • 각 노드가 하나의 노드로부터 쓰기를 받고, 이 쓰기를 다른 한 노드에게 전달
  • (b) : 별 모양 토폴로지
    • 지정된 루트 노드 하나가 다른 모든 노드에 쓰기를 전달
    • 트리로 일반화(generalized) 할 수 있음.
  • (C) : 전체 연결(All-to-all) 토폴로지
    • 전체 연결 토폴로지가 가장 일반적인 토폴로지
    • 모든 리더가 각자의 쓰기를 다른 모든 리더에게 전송

 

원형과 별 모양 토폴로지의 공통 특징
  • 쓰기는 모든 복제 서버에 도달하기 전에 여러 노드를 거침
  • 그러므로 노드들은 다른 노드로부터 받은 데이터 변경 사항을 전달해야 함.
  • 무한 복제 루프를 방지하기 위해, 각 노드에는 고유 식별자가 있음.
    • 복제 로그에서 각 쓰기는, 거치는 모든 노드의 식별자가 태깅됨.
    • 태깅된 경우 데이터 변경 사항을 무시함.

 

원형과 별 모양 토폴로지의 문제점
  • 하나의 노드에 장애가 발생하면  다른 노드간 복제 메시지 흐름에 방해를 줌
  • 해다 노드가 복구될 때까지 통신을 할 수 없는 상태가 됨.
  • 토폴로지는 장애 노드를 회피하게끔 설정할 수 있음. (대신 수동으로 해야 함.)
  • all-to-all은 촘촘하게 연결되어 있기 때문에 내결함성(fault tolerance)이 더 좋음
    • 촘촘해서 메시지가 여러 경로로 이동할 수 있음.

 

근데 all-to-all도 문제가 있음.

    • 어떤 문제? 메시지간 추월(overtake)하는 문제
    • 결국 인과성의 문제임
      • 갱신은 이전 삽인에 종속적이라 모든 노드에서는 먼저 삽입처리를 해야함.
    • 암튼 이를 올바르게 정렬하기 위해서는 버전 벡터(version verctor)라는 기법을 사용할 수 있음

 

 

 

 

 

❐ 4. 리더 없는 복제(Leaderless Replication)


🌀 4-1. 노드가 다운됐을 때, 데이터베이스에 쓰기 

노드가 다운된 경우 발생할 수 있는 시나리오

  1. 클라이언트(사용자1234)가 쓰기를 세 개의 복제 서버에 병렬로 전송
  2. 복제 서버3은 쓰기를 놓침 ➔ 놓친 사실을 무시
  3. '복제 서버3' 다시 online
  4. 클라이언트 읽기 시작 
  5. 복제1,2를 읽을 땐 문제 없음
  6. 복제3을 읽으면 오래된(outdated) 값을 얻을 수 있음.

 

위 문제를 해결하려면?
  • 클라이언트가 읽기 요청을 병렬로 여러 노드에 전송해야 함.
  • 이렇게 하면 클라이언트는 여러 노드에서 다른 응답을 받을 수 있음.
    • 어떤 노드에서는 최신값을,  어떤 노드에서는 오래된 값을 받음
    • 이때는 버전을 사용해 최신 내용 여부를 판별하면 됨.

 

읽기 복구와 안티 엔트로피(Read repair and anti-entropy)
➔ 여기서 entropy는 물리 용어 그대로 "무질서도"를 의미하는 거임
  • 사용 불가능한 노드가 온라인 상태가 된 후 누락된 쓰기를 어떻게 catch-up할까?
  • Dynamo-style datastores에서는 2가지 매커니즘을 주로 사용함
    1. 읽기 복구
      • 여러 노드에 병렬로 읽기를 수행해서, 오래된 응답을 감지
    2. 안티 엔트로피 처리
      • 무질서도를 줄여 복제본 간 데이터 일관성(consistency) 을 되돌리는 메커니즘.
      • 백그라운드에서 지속적으로 차이를 찾고 누락된 데이터를 복사함

 

읽기 & 쓰기를 위한 정족수(Quorum)
➔ 정족수 뜻 : 결정을 내리기 위해 필요한 최소 인원
  • 변수 설명
    • N : 복제본 수
    • W : 쓰기 정족수
    • R : 읽기 정족수
  • N개의 복제 서버가 있을 때
    • W개의 노드에서 성공해야 쓰기가 확정
    • 모든 읽기는 최소한 R개의 노드에 질의 해야함
  • N = 3, W = 2, R = 2
    • 'W + R > N' 이면 읽을 때 최신 값을 얻을 것으로 기대
    • 최소한 R개의 노드 중 하나에서 최신 값을 읽을 수 있기 때문
  • 이런 R과 W를 따르는 읽기와 쓰기를 정족수 읽기와 쓰기라고 함. 
    • 최신 값을 읽기 위해 필요한 최소한의 노드수 만족

 

  • 정족수 조건이 'W + R > N' 면 아래와 같이 사용 불가능한 노드를 용인(tolerate, 허용)함
    • r < n이면 노드 하나를 사용할 수 없어도 여전히 읽기를 처리할 수 있다.
    • n = 3, w = 2, r = 2이면 사용 불가능한 노드 하나를 용인한다.
    • n = 5, w = 3, r = 3이면 사용 불가능한 노드 둘을 용인한다. 그림 5-11의 상황이다.
    • 일반적으로 읽기와 쓰기는 항상 모든 n개의 복제 서버에 병렬로 전송한다.
      • 파라미터 w와 r은 얼마나 많은 노드를 기다릴지 결정한다.
      • 즉, 읽기나 쓰기가 성공했다고 간주하려면
        • n개의 노드 중 몇 개의 노드에서 성공을 확인해야 하는지를 나타낸다.
  • 필요한 W나 R개 노드보다 사용 가능한 노드가 적으면 쓰기나 읽기는 에러를 반환
  • 노드가 성공 응답을 반환했는지 여부만 중요하고 다양한 종류의 오류를 구별할 필요는 없음.

 

 

 

🌀 4-2. 정족수 일관성의 한계 (Limitations of Quorum Consistency)

정족수 일관성의 한계
  • W + R > N
    • 일반적으로 모든 읽기는 키의 최신 값을 반환할 것을 기대
  • 보통 R과 W의 값으로 과반수(n/2)를 선택함
    • n/2 노드까지 장애를 허용해도 이 'W + R > N' 보장되기 때문
  • 정족수가 다수(majority)일 필요는 없음.
    • majority = more than n/2
    • 그니깐 꼭 정족수가 n/2보다 클 필요는 없다는 말
    • 중요한 건 W+R>N 조건을 만족하느냐 마느냐

 

'W+R > N' 인 경우에도 오래된 값을 반환하는 엣지 케이스
  1. 느슨한(sloppy) 정족수를 사용하는 경우
    • R개의 노드와 W개의 노드가 겹치는 것을 보장하지 않음
  2. 동시성 이슈로 인해, 쓰기의 순서가 분명하지 않은 경우
  3. 쓰기/읽기가 동시에 발생하면, 일부 복제 서버에만 반영될 수 있음.
  4. 쓰기가 일부 서버에서 실패하는 경우
  5. 새 값을 전달하는 노드가 고장난 경우

 

최신성 모니터링
  • 리더 기반 복제
    • 데이터베이스는 일반적으로 복제 지연에 대한 지표를 모니터링 시스템을 통해 노출함
  • 리더 없는 복제
    • 쓰기가 적용된 순서를 고정할 수 없기 때문에, 모니터링이 조금 더 어려움.

 

 

🌀 4-3. 느슨한 정족수와 암시된 핸드오프 (Sloppy Quorums and Hinted Handoff)

정상 정족수인 경우

 

  • 정족수(quorum)를 잘 설정해 두면,
    • 노드 하나가 고장 나도 괜찮음 (failover 불필요). 
    • 노드 하나가 느리게 응답해도 괜찮음 (모든 노드를 기다릴 필요 없음, w개 또는 r개만 응답하면 됨).

 

  • 암튼 그래서, leaderless replication는 아래의 요소가 중요한 시스템에 매력적인(appealing) 선택지
    • 고가용성(High Availability)
    • 낮은 지연 시간(Low Latency)
  • 단, 가끔 오래된 데이터(stale read)를 읽는 걸 감수해야 함.

 

 

정족수 방식의 한계
  • 정족수는 노드 몇 개가 죽어도 괜찮긴 하지만, 생각보다 내결함성(fault tolerance) 이 완벽하진 않음
  • 왜냐하면 네트워크 문제가 생기면, 클라이언트가 많은 노드와 단절될 수 있기 때문.
    • 사실 그 노드들은 살아 있고, 다른 클라이언트는 잘 접속할 수 있어도,
    • 해당 클라이언트 입장에서는 연결 안됨. (죽은 거나 마찬가지)
  • 이런 상황에서는 w개나 r개 노드에 도달할 수 없게 되어 정족수를 만족하지 못함
    • 클라이언트는 더 이상 읽기나 쓰기를 못 하게 됨.

 

  • 큰 클러스터에서 네트워크 문제가 생기면, 클라이언트가 일부 노드에는 연결할 수 있긴 함
    • 하지만, 정족수(quorum)에 필요한 정확한 노드 집합에는 연결 못할 수 있음.
    • 이때 DB 설계자는 두 가지 선택지 중 하나를 골라야 함
      1. 엄격한 정족수
        • 정족수(W, R)를 만족 못하면 무조건 에러 반환.
        • 데이터 일관성은 지킬 수 있지만, 가용성 떨어짐.
      2. 느슨한 정족수 (Sloppy Quorum)
        • 정족수 노드가 아니라도, 접근 가능한 다른 노드에 임시로 쓰기/읽기 허용.
        • 가용성은 높아지지만, 나중에 읽을 때 최신 데이터 보장이 깨질 수 있음

 

암시된 핸드오프(Hinted handoff)

 

  • 네트워크 장애가 해결되면, 장애 동안 임시로 다른 노드가 대신 받아둔 쓰기(write)들을
          원래 저장해야 하는 노드(home node) 로 옮겨 주는 매커니즘

 

 

느슨한 정족수
  • 장점
    • 쓰기 가용성을 높이는데 특히 유용함
  • 단점
    • w + r > n 조건을 만족하더라도 최신 값 보장 안 됨.
    • 왜? 최신 데이터가 임시로 N 바깥 노드에 저장돼 있을 수 있기 때문.
    • 따라서 전통적인 의미의 정족수(quorum) 라고 보기는 어려움.
    • 단순히 “데이터가 어딘가 w개 노드에는 저장됐다”는 내구성(durability) 보장임
    • 읽기(R개)에서 반드시 최신 데이터를 본다는 보장은 없음
    • 최신성을 확보하려면 나중에 힌티드 핸드오프(hinted handoff) 과정이 끝나야 함.

 

다중 데이터센터 운영
  • 리더 없는 복제의 경우에도 데이터센터 운영에 적합함
    • 동시 쓰기 충돌 허용
    • 네트워크 중단 허용
    • 지연 시간 급증 허용

 

 

🌀 4-4.  동시 쓰기 감지(Detecting Concurrent Writes)

이벤트 순서가 엉망인 케이스

  • 노드1은 A로 부터의 쓰기를 받지만, 순간적인 장애로 B로 부터 쓰기를 받지 못함
  • 노드2는 A로 부터의 쓰기를 먼저 받고, 그 다음 B로부터 쓰기를 받음
  • 노드3은 B로 부터의 쓰기를 먼저 받고, 그 다음 A로부터 쓰기를 받음

 

 

최종 쓰기 승리(동시 쓰기 버리기)
: Last write wins (discarding concurrent writes)
  • 각 복제본이 가진 "예전" 값을 버리고 가장 "최신"값으로 덮어쓰는 방식
  • 근데 이 아이디어는 오해의 소지가 있음.
    • 5-12를 보면 이벤트 순서가 정해지지 않았기 때문에 "동시 쓰기"라고 해야함
  • Last Write Win(LWW)
    • 마지막 쓰기가 무조건 이김
    • 최종적 수렴(eventual convergence) 달성이 목표
    • 하지만 지속성을 희생함
    • LWW로 데이터베이스를 안전하게 사용하는 유일한 방법은 
      • 키를 한번만 쓰고 이후에는 불변 값으로 다루는 것
      • 이 방법은 같은 키를 동시에 갱신하는 상황을 방지함

 

"이전 발생"관계와 동시성
: The “happens-before” relationship and concurrency
  • 이것도 인과성 관련인 듯
  • B가 A를 기반으로 한다면, A는 B의 이전 발생(happens-before)
  • 이전 발생(happens-before)이 동시성의 의미를 정의하는 핵심

 

이전 발생 관계 파악하기
: Capturing the happens-before relationship

  • 처음에 장바구니는 비어 있고, 두 클라이언트가 데이터베이스에 쓰기를 다섯 번 수행함.

 

  • 화살표의 의미
    • 어떤 작업이 다른 작업 이전에 발생했는지
    • 나중 작업이 이전에 수행된 작업을 알거나 의존했는지
  • 이 예제에서 클라는 서버 데이터와 동일한 최신로 유지 못함
    • 왜? 항상 다른 작업이 동시에 수행됐기 때문
    • 하지만, 결국에는 손실된 쓰기는 없게 됨.(계속 덮어 쓰기 때문)
  • 이 알고리즘의 동작방식
    • 서버가 모든 키에 대한 버전 번호를 유지하고, 기록할 때 마다 버전 번호 증가
    • 클라가 읽을 땐, 최신 버전뿐만 아니라 덮어쓰지 않은 모든 값을 반환 
    • 클라가 키를 기록할 땐, 이전 읽기의 버전 번호를 포함해서 읽고 받은 모든 값을 merge
    • 서버가 특정 버전 번호를 가진 쓰기를 받을 때 해당 버전 이하 모든 값을 overwrite

 

동시에 쓴 값 병합
  • 어떤 데이터도 자동으로 삭제되지 않음을 보장.
  • 하지만 클라가 추가적으로 작업을 수행해야함.
    • 어떤 작업이냐. 여러 작업이 동시에 발생하면 클라는 동시에 쓴 값을 합쳐 정리해야 함.
    • 리악은 이런 동시 값을 형제(Sibling) 값이라고 부름

 

형제 값의 병합
(장바구니 예제)
  • 추가만 하는 경우
    • 충돌 난 여러 장바구니(=sibling)를 합집합(union) 으로 병합하면 합리적임.
    • 예:
      • A 장바구니 = [milk, flour, eggs, bacon]
      • B 장바구니 = [eggs, milk, ham]
      • 병합 결과 = [milk, flour, eggs, bacon, ham] (중복 제거)
    • 문제 없음.
  • 삭제까지 허용하는 경우
    • 단순 합집합(union) 병합은 문제가 됨.
    • 예:
      • A 장바구니 = [milk, eggs]
      • B 장바구니 = [milk] (eggs 삭제됨)
      • 단순 합집합 결과 = [milk, eggs] → eggs가 부활
    • 즉, 삭제가 반영되지 않음.
  • 해결 방법 → Tombstone (삭제 마커)
    • 아이템을 삭제할 때 DB에서 아예 없애는 게 아니라, “삭제됨”이라는 마커를 버전과 함께 저장.
    • 병합할 때 tombstone을 확인하면, 해당 아이템이 제거된 걸 알 수 있음.
    • 결과적으로 삭제가 올바르게 반영됨.

 

 

버전 벡터

 

  • 모든 복제본의 버전 번호들을 모아둔 집합
  • 각 복제본은:
    • 자기 자신 버전 번호를 증가시키고,
    • 다른 복제본에서 본 버전 번호도 기록.
  • 이를 통해 어떤 값은 덮어쓸 수 있고(overwrite),
  • 어떤 값은 동시 쓰기라서 형제(sibling)로 유지해야 하는지 알 수 있음.

 

 

'Book > 데이터 중심 애플리케이션 설계' 카테고리의 다른 글

7장. 트랜잭션  (0) 2025.09.29
6장. 파티셔닝  (0) 2025.09.20
Part2. 분산 데이터  (0) 2025.09.13
4장. 부호화와 발전 (Encoding & Evolution)  (0) 2025.09.06
3장. 저장소와 검색  (0) 2025.08.30
'Book/데이터 중심 애플리케이션 설계' 카테고리의 다른 글
  • 7장. 트랜잭션
  • 6장. 파티셔닝
  • Part2. 분산 데이터
  • 4장. 부호화와 발전 (Encoding & Evolution)
gilbert9172
gilbert9172
gilbert9172 님의 블로그 입니다.
  • gilbert9172
    バックエンド
    gilbert9172
  • 전체
    오늘
    어제
    • All Categories (207)
      • 우테코 7기 (21)
        • 1주차 (8)
        • 2주차 (5)
        • 3주차 (6)
      • Langauge (6)
        • Java (3)
        • Kotlin (3)
      • Back-End (13)
        • SpringBoot (1)
        • Trouble Shooting (0)
        • Setup & Configuration (1)
        • SQL (3)
        • Redis (8)
      • Architecture (6)
        • Multi Module (1)
        • DDD (5)
      • CS (30)
        • Data Structure (6)
        • Operating System (0)
        • Network (12)
        • Database (10)
        • Design Pattern (2)
      • Algorithm (78)
        • 내용 정리 (18)
        • 문제풀이 (60)
      • DevOps (6)
        • AWS (5)
        • Git (1)
      • Front-End (1)
        • Trouble Shooting (1)
      • Project (6)
        • 페이스콕 (6)
      • Book (39)
        • 친절한 SQL 튜닝 (9)
        • 데이터 중심 애플리케이션 설계 (14)
        • 이벤트 기반 마이크로서비스 구축 (6)
        • Spring Batch docs (10)
        • Quartz docs (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    binarysearch
    sliding-window
    greedy
    오블완
    부분단조성
    Two-Pointer
    Back-Tracking
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
gilbert9172
5장. 복제
상단으로

티스토리툴바