❐ 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)
- 리더가 장애인지 판단
- 대부분 시스템에서 장애 판단의 기준은 '타임아웃'
- 여기서 적당한 타임아웃은 얼마일까?
- 너무 길면: 장애 복구가 늦어짐.
- 너무 짧으면: 일시적 네트워크 지연에도 리더가 교체되어 혼란 발생.
- 새로운 리더 선택
- 선출 과정을 통해 새로운 리더 임명
- 모든 노드한테 동의를 얻어야 함 (새롭게 선정된 리더에 대한)
- 새로운 리더 사용을 위해 시스템을 재설정
장애 복구 과정은 잘못될 수 있는 것 투성이다.
- 비동기식 복제의 한계
- 새로운 리더를 선출할 때, 이전 리더가 실패하기 직전에 수신하지 못한 쓰기가 있을 수 있음. (데이터 손실)
- 단순히 이전 리더의 복제되지 않은 쓰기를 폐기하는 방식이 흔히 쓰임.
- 쓰기 폐기(Discarding) 방법의 위험
- DB 외부 저장소(예: MySQL에서 자동 증가 키, Redis 같은 캐시)를 함께 사용할 때 특히 위험.
- 새로운 리더가 이전 리더의 키 생성 상태를 모르면 불일치 발생.
- MySQL 팔로워가 리더로 승격 → 자동 증가 키 사용 → 기존 리더보다 작은 키 값이 재사용됨.
- 스플릿 브레인 문제
- 두 노드가 모두 자신이 리더라고 믿는 경우
- 충돌을 해소하는 과정을 거치지 않으면, 데이터가 유실되거나 오염됨
➔ 이 문제에 대한 쉬운 해결책은 없음. 수동으로 장애 복구를 수행하는 방식을 선호하는 팀도 있음.
🌀 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. 자신이 쓴 내용 읽기
비동기식 복제에서 생기는 첫 번째 이상 현상

- 사용자가 쓰기를 수행한 직후 데이터를 본다면 새로운 데이터는 아직 복제 서버에 반영되지 않을 수 있음.
- 이러한 행위는 사용자에게 불만족스러울 동작임
- 이런 상황에서는 쓰기 후 읽기 일관성이 필요함.
쓰기 후 읽기 일관성을 구현하는 다양한 기법
- 사용자가 수정한 내용을 읽을 때는 리더에서 읽기. 그 밖에는 팔로워에서 읽기
- 다른 기준을 사용해서 리더에서 읽을지 말지 결정 (ex. 마지막 갱신 시간)
- 가장 최근 쓰기의 타임스탬프를 사용하기(논리적 타임스탬프 or 실제 시간)
- 리더가 제공해야 하는 모든 요청은 리더가 포함된 데이터센터로 라우팅
디바이스 간(cross-device) 쓰기 후 읽기 일관성을 보장시 고려해야 할 것
- 마지막 갱신 타임스탬프 관리 어려움
- 단일 디바이스에서는 "내가 마지막으로 쓴 시점"을 기억하면 됨.
- 하지만 여러 디바이스에서는 한 기기에서 쓴 데이터를 다른 기기는 알 수 없음.
- 따라서 이 메타데이터(사용자의 마지막 쓰기 시간) 를 중앙에서 관리해야 함.
- 데이터센터 분산 문제
- 복제 서버가 여러 데이터센터에 분산돼 있으면, 서로 다른 디바이스 요청이 다른 데이터센터로 갈 수 있음.
- 이 경우 각 요청이 서로 다른 데이터센터로 라우팅될 수 있음.
- 일관성을 보장하려면 사용자 요청을 동일한 데이터센터로 라우팅해야 함.
🌀 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) 리더 복제
- 성능
Single-leader Multi-leader » 모든 쓰기는 인터넷을 통해 리더가 있는 데이터 센터로
이동해야 함.
» 이것은 쓰기에 지연 시간을 상당히 늘리는 원인
» (초반에는) 여러 데이터 센터를 갖는 목적에도 위배» 모든 쓰기는 로컬 데이터센터에서 처리한 다음
비동기 방식으로 다른 데이터센터에 복제
» 따라서, 데이터센터 간 네트워크 지연은 감춰짐
» 사용자가 느끼는 체감은 더 좋음 - 데이터 중단 내성
Single-leader Multi-leader » 리더가 있는 데이터센터가 고장나면
장애 복구를 위해 다른 데이터센터에서 한 팔로워를 리더로 승진» 각 데이터센터는 다른 데이터센터와 독립적으로 동작하고
고장 난 데이터센터가 온라인으로 돌아왔을 때, 복제를 catch-up - 네트워크 문제 내성
Single-leader Multi-leader » 데이터센터 내 연결의 쓰기는 동기식
» 때문에 데이터센터 내 연결 문제에 매우 민감» 네트워크 문제에 보다 잘 견딤 (비동기 방식 사용하기 때문)
» 일시적인 네트워크 중단에도 쓰기 처리는 진행되기 때문임
다중 리더 복제의 단점
- 다중 리더 복제는 많은 데이터베이스에서 다소 사후적으로(retrofitted) 추가된 기능
- 동일한 데이터를 다른 두 개의 데이터센터에서 동시에 변경할 수 있음. (동시성 이슈)
- 미묘한 설정상의 실수나 다른 데이터베이스 기능과의 뜻밖의? 상호작용이 있음.
- 자동 증가 키 (AUTO_INCREMENT)
- 각 리더가 독립적으로 키를 생성 → 중복 키 충돌 위험.
- 트리거 (Trigger)
- 리더 간 동일 트리거가 중복 실행 → 같은 데이터가 여러 번 반영될 수 있음.
- 무결성 제약 조건 (Integrity constraints)
- 한 리더에서는 유효하지만, 다른 리더에서는 제약 조건 위반 발생 가능.
- 자동 증가 키 (AUTO_INCREMENT)
➔ 이런 이유로, 다중 리더 복제는 위험한 영역으로 간주되며 가능하다면 피하는 것이 좋다.
오프라인 작업을 하는 클라이언트
- 인터넷이 끊어진 동안 애플리케이션이 계속 동작해야 하는 경우, 다중 리더 복제가 적절함.
- 예를 들어, 캘린더 앱을 생각
- 노트북에서 회의 일정을 추가하면 → 클라우드 서버를 거쳐서 → 스마트폰과 태블릿의 달력에도 복제됨.
- 이 과정이 비동기적 다중 리더 복제이며, 우리가 흔히 말하는 “동기화(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)이 시간이 지나면서 같은 상태로 수렴한다"는 의미
수렴 충돌을 해소하는 다양한 방법
- 고유 ID 부여하고, 가장 높은 ID를 가진 쓰기를 고르기
- 타임스탬프를 사용하는 경우를 최종 쓰기 승리(last write wins, LWW)라고 함
- 대중적이긴 한데, 데이터 유실 위험이 있음.
- 복제 ID 기반 우선순위
- 높은 ID가 항상 우선 적용
- 이것도 데이터 유실 위험 있음
- 어떻게든(Somehow) merge
- 예를 들어, 사전 순으로 정렬한 후 연결
- 모든 정보를 보존
- 대신에 애플리케이션에서 충돌을 해소해야 함.
사용자 정의 충돌 해소 로직
- 쓰기 수행 중 충돌 해소 코드 실행 되는 경우
- 충돌 감지하자마자 충돌 핸들러 호출
- 백그라운드 프로세스에서 빠르게 실행되어야 함.
- 읽기 수행 중 충돌 해소 코드 실행 되는 경우
- 충돌을 감지하면 모든 충돌 쓰기를 저장
자동 충돌 해소
- 충돌 해결 규칙은
- 금방 복잡해질 수 있고,
- 커스텀 코드(custom code)는 오류가 발생하기 쉬움
- 아마존의 충돌 해결 로직은 장바구니에 추가된 아이템은 유지했지만, 제거된 아이템은 유지하지 않음.
- 따라서 고객들은 장바구니에서 분명히 제거했던 물품이 다시 나타나는 상황이 있었음.
- 흥미로운? 연구
- 충돌 없는 복제 데이터타입(Conflict-free Replicated Data Types, CRDTs)
- 여러 사용자가 동시에 데이터를 수정해도 자동으로 충돌을 해결할 수 있는 특별한 자료구조 집합.
- 이중(two-way) merge 사용
- 병합 가능한 영속 데이터 구조(Mergeable persistent data structure)
- 명시적으로 히스토리를 추적하고, 삼중 병합 함수(three-way merge functino)를 사용
- 운영 변환(Operational transformation)
- 협업 편집 애플리케이션의 충돌 해소 알고리즘
- 충돌 없는 복제 데이터타입(Conflict-free Replicated Data Types, CRDTs)
🌀 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. 노드가 다운됐을 때, 데이터베이스에 쓰기
노드가 다운된 경우 발생할 수 있는 시나리오

- 클라이언트(사용자1234)가 쓰기를 세 개의 복제 서버에 병렬로 전송
- 복제 서버3은 쓰기를 놓침 ➔ 놓친 사실을 무시
- '복제 서버3' 다시 online
- 클라이언트 읽기 시작
- 복제1,2를 읽을 땐 문제 없음
- 복제3을 읽으면 오래된(outdated) 값을 얻을 수 있음.
위 문제를 해결하려면?
- 클라이언트가 읽기 요청을 병렬로 여러 노드에 전송해야 함.
- 이렇게 하면 클라이언트는 여러 노드에서 다른 응답을 받을 수 있음.
- 어떤 노드에서는 최신값을, 어떤 노드에서는 오래된 값을 받음
- 이때는 버전을 사용해 최신 내용 여부를 판별하면 됨.
읽기 복구와 안티 엔트로피(Read repair and anti-entropy)
➔ 여기서 entropy는 물리 용어 그대로 "무질서도"를 의미하는 거임
- 사용 불가능한 노드가 온라인 상태가 된 후 누락된 쓰기를 어떻게 catch-up할까?
- Dynamo-style datastores에서는 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' 인 경우에도 오래된 값을 반환하는 엣지 케이스
- 느슨한(sloppy) 정족수를 사용하는 경우
- R개의 노드와 W개의 노드가 겹치는 것을 보장하지 않음
- 동시성 이슈로 인해, 쓰기의 순서가 분명하지 않은 경우
- 쓰기/읽기가 동시에 발생하면, 일부 복제 서버에만 반영될 수 있음.
- 쓰기가 일부 서버에서 실패하는 경우
- 새 값을 전달하는 노드가 고장난 경우
최신성 모니터링
- 리더 기반 복제
- 데이터베이스는 일반적으로 복제 지연에 대한 지표를 모니터링 시스템을 통해 노출함
- 리더 없는 복제
- 쓰기가 적용된 순서를 고정할 수 없기 때문에, 모니터링이 조금 더 어려움.
🌀 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 설계자는 두 가지 선택지 중 하나를 골라야 함
- 엄격한 정족수
- 정족수(W, R)를 만족 못하면 무조건 에러 반환.
- 데이터 일관성은 지킬 수 있지만, 가용성 떨어짐.
- 느슨한 정족수 (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 |