개발독서/데이터

[고투런1기] 데이터 중심 애플리케이션 설계 (5장 복제)

보리시스템 2024. 6. 3.
[2부 분산 데이터]
1. 고부하로 확장
1.1 비공유 아키텍처
1.2 복제 대 파티셔닝


5장 복제

1. 리더와 팔로워
1.1 동기식 vs 비동기식 복제
1.2 새로운 팔로워 설정
1.3 노드 중단 처리
1.4 복제 로그 구현

2. 복제 지연 문제

2.1 자신이 쓴 내용 읽기
2.2 단조 읽기
2.3 일관된 순서로 읽기
2.4 복제 지연을 위한 해결책

3. 다중 리더 복제

3.1 다중 리더 복제의 사용 사례
3.2 쓰기 충돌 다루기
3.3 다중 리더 복제 토폴로지

4. 리더 없는 복제

4.1 노드가 다운됐을 때 데이터베이스에 쓰기
4.2 정족수 일관성의 한계
4.3 느슨한 정족수와 암시된 핸드오프
4.4 동시 쓰기 감지

 

[정리: 여러 장비로 분산해 저장하는 데이터]

- 복제의 용도
1) [고가용성]
한 장비(또는 여러 장비나 전체 데이터센터)가 다운될 때도 시스템은 계속 동작
2) [연결이 끊긴 작업] 네트워크 중단이 있을 때도 애플리케이션은 계속 동작
3) [지연 시간] 지리적으로 사용자에게 가까이 데이터를 배치해 사용자가 더 빠르게 작업할 수 있게 함
4) [확장성] 복제본에서 읽기를 수행해 단일 장비에서 다룰 수 있는 양보다 많은 양의 읽기 작업을 처리할 수 있음

- 복제 접근 방식
1) [단일 리더 복제]
클라이언트는 모든 쓰기를 단일 노드(리더)로 전송하고 리더는 데이터 변경 이벤트 스트림을 다른 복제 서버(팔로워)로 전송함. 읽기는 모든 복제 서버가 수행할 수 있지만 팔로워의 읽기는 오래된 값일 수 있음
=> 장점: 이해하기 쉽고 충돌 해소에 대한 우려가 없어 널리 사용됨

2) [다중 리더 복제] 클라이언트는 각 쓰기를 여러 리더 노드 중 쓰기를 받아들일 수 있는 노드로 전송함. 리더는 데이터 변경 이벤트 스트림을 다른 리더와 모든 팔로워 노드로 전송함
=> 장점: 결함 노드, 네트워크 중단, 지연 시간 급증이 있는 상황에서 더욱 견고함
=> 단점: 설명하기 어렵고 일관성이 거의 보장되지 않음

3) [리더 없는 복제] 클라이언트는 각 쓰기를 여러 노드로 전송함. 클라이언트는 오래된 데이터를 감지하고 이를 바로잡기 위해 병렬로 여러 노드에서 읽음
=> 장단점은 다중 리더 복제 내용과 동일

- 동기, 비동기 복제
=> 복제는 동기 또는 비동기로 이뤄지는데 동기/비동기 여부는 결함이 있을 때 시스템 작동에 중요한 영향을 미침

- 복제 지연에 따른 이상 현상

- 일관성 모델
1) [쓰기 후 읽기 일관성]
사용자는 자신이 제출한 데이터를 항상 볼 수 있어야 함
2) [단조 읽기] 사용자가 어떤 시점에 데이터를 본 후에는 예전 시점의 데이터는 나중에 볼 수 없음
3) [일관된 순서로 읽기] 사용자는 인과성이 있는 상태의 데이터를 봐야 함

- 다중 리더 복제, 리더 없는 복제 접근 방식에 내재된 동시성 문제
=> 여러 쓰기가 동시에 발생하는 상황을 허용하기 때문에 충돌이 발생할 수 있음

- 한 작업이 다른 작업 이전에 발생했는지, 동시에 발생했는지 결정하기 위해 DB에서 사용할 수 있는 알고리즘

- 동시 갱신을 함께 병합해 충돌을 해결하는 방법

 


 

- 여러 장비 간 분산된 DB의 필요성?
1) [확장성] 데이터 볼륨, 읽기 부하, 쓰기 부하가 단일 장비에서 다룰 수 있는 양보다 커지면 부하를 여러 장비로 분해할 수 있음
2) [내결함성/고가용성] 장비 하나(또는 여러 장비나 네트워크, 전체 데이터센터)가 죽더라도 애플리케이션이 계속 동작해야 한다면 여러 장비를 사용해 중복성을 제공할 수 있음. 장비 하나가 실패하면 다른 하나가 이어받음
3) [지연 시간] 전 세계에 사용자가 있다면 사용자와 지리적으로 가까운 곳의 데이터센터에서 서비스를 제공하기 위해 전 세계 다양한 곳에 서버를 두어 지연 시간을 단축함

- 고부하로 확장이 필요하다면?
1) 수직 확장(용량 확장)
=> 더 강력한 장비를 구매하는 것이 가장 간단한 방법
=> 하지만 공유 메모리 접근 방식의 문제는 비용임. 또한 2배 크기의 장비가 반드시 2배의 부하를 처리할 수 있는 것도 아님
=> 공유 메모리 아키텍처는 제한적인 내결함성을 제공하지만 하나의 지리적인 위치로 제한됨
=> 공유 디스크 아키텍처는 여러 장비가 고속 네트워크로 연결될 수 있도록 하지만 잠금경함과 오버헤드가 공유 디스크 접근 방식의 확장성을 제한함
* 공유 디스크 아키텍처? 독립적인 CPU, RAM을 탑재한 여러 장비를 사용하지만 데이터 저장은 장비 간 공유하는 디스크 배열로 함

2) 수평 확장(규모 확장)
=> 비공유 아키텍처(shared-nothing)
=> DB SW를 수행하는 노드(장비)가 CPU, RAM, 디스크를 독립적으로 사용함
=> 특별한 HW가 필요하지 않기 때문에 가격 대비 성능이 가장 좋은 시스템
=> 여러 지리적인 영역에 걸쳐 데이터를 분산함으로써 사용자 지연시간을 줄여 전체 데이터센터의 손실을 줄일 수 있음
=> 하지만 분산 비공유 아키텍처는 부가적인 애플리케이션 복잡도를 야기하고 사용할 수 있는 데이터 모델의 표현을 제한함

- 복제 vs 파티셔닝
=> 여러 노드에 데이터를 분산하는 방법은 복제, 파티셔닝이 있음
1) [복제] 같은 데이터의 복사본을 잠재적으로 다른 위치에 있는 여러 노드에 유지
2) [파티셔닝] 큰 DB를 파티션이라는 작은 서브넷으로 나누고 각 파티션은 각기 다른 노드에 할당(샤딩)

 


 

5장 복제

- 복제?
=> 네트워크로 연결된 여러 장비에 동일한 데이터의 복사본을 유지

- 데이터 복제 필요성?
1) 지리적으로 사용자와 가깝게 데이터를 유지해 지연시간을 줄임
2) 시스템 일부에 장애가 발생해도 지속적으로 동작할 수 있게 해 가용성을 높임
3) 읽기 질의를 제공하는 장비의 수를 확장해 읽기 처리량을 늘림

- 노드 간 변경 복지를 위한 알고리즘 3가지
1) 단일 리더(single-leader) 복제
2) 다중 리더(multi-leader) 복제
3) 리더 없는(leaderless) 복제

 


 

1. 리더와 팔로워

- 복제 서버(replica)
=> DB의 복사본을 저장하는 각 노드

- 모든 복제 서버에 모든 데이터가 있다는 사실을 어떻게 보장할까?
=> DB의 모든 쓰기는 모든 복제 서버에서 치리돼야 함. 그렇지 않으면 복제 서버는 더이상 동일한 데이터를 유지할 수 없음

- 이 문제를 위한 가장 일반적인 해결책? 리더 기반 복제(=능동/수동, 마스터 슬레이브 복제)
=> 동작 순서
1) 복제 서버 중 하나를 리더(마스터, 프라이머리)로 지정하고 다른 복제 서버는 팔로워(읽기 복제 서버, 슬레이브, 2차, 핫대기)로 둠
2) 클라이언트가 DB에 쓰기할 때 클라이언트 요청은 리더에게 보냄
3) 리더는 먼저 로컬 저장소에 새로운 데이터를 기록함
4) 리더가 로컬 저장소에 새로운 데이터를 기록할 때마다 데이터 변경을 복제로그 또는 변경 스트림의 일부로 팔로워에 전송
5) 각 팔로워가 리더로부터 로그를 받으면 리더가 처리한 것과 동일한 순서로 모든 쓰기를 적용해 DB의 로컬 복사본을 갱신
* 클라이언트가 DB로부터 읽기할 때 리더 또는 임의 팔로워에게 질의를 할 수는 있지만, 쓰기는 리더에게만 허용

- 리더 기반 복제는 DB에만 국한되지 않음
=> 분산 메시지 브로커에도 사용됨
=> 일부 네트워크 파일 시스템, DRBD 같은 복제 블럭 디바이스도 유사함

 


 

1.1 동기식 vs 비동기식 복제

- 복제 시스템에서 중요한 세부 사항은 복제의 동기식/비동기식 여부

- 동기식 복제

=> [장점] 팔로워가 리더와 일관성 있게 최신 데이터 복사본을 가지는 것을 보장함
=> [단점] 동기 팔로워가 응답하지 않으면 쓰기가 처리될 수 없음. 리더는 모든 쓰기를 차단하고 동기 복제 서버가 다시 사용할 수 있을 때까지 기다려야 함
=> 모든 팔로워가 동기식인 상황은 비현실적이므로 반동기식(semi-synchronous)으로 설정함
=> 반동기식? 팔로워 하나는 동기식으로 하고 그밖에는 비동기식으로 함

- 비동기식 복제
=> 리더 기반 복제는 보통 완전히 비동기식으로 구성함
=> [장점] 모든 팔로워가 잘못되더라도 리더가 쓰기 처리를 계속 할 수 있음
=> [단점] 리더가 잘못되고 복구할 수 없으면 팔로워에 아직 복제되지 않은 모든 쓰기는 유실됨 (쓰기가 클라이언트에게 확인된 경우에도 지속성을 보장하지 않음)

 



1.2 새로운 팔로워 설정

- 새로운 팔로워가 리더의 데이터 복제본을 정확히 가지고 있는지 어떻게 보장할까?
=> 클라이언트는 지속적으로 DB에 기록하고 데이터는 항상 유동적이기 때문에 표준 파일 복사본은 다른 시점에 DB의 다른 부분을 조게 됨. 즉, 복사 결과가 유효하지 않을 수 있음
=> 팔로워 설정은 대개 중단시간 없이 수행할 수 있음

- 팔로워 설정 과정
1) 가능하다면 전체 DB를 잠그지 않고 리더의 DB 스냅숏을 일정 시점에 가져옴
2) 스냅숏을 새로운 팔로워 노드에 복사함
3) 팔로워는 리더에 연결해 스냅숏 이후 발생한 모든 데이터 변경을 요청함
* 스냅숏이 리더의 복제 로그의 정확한 위치와 연관돼야 함
* 위치에 대한 명칭은 다양함(MySQL에서는 이진로그 좌표, PostgreSQ에서는 로그 일련번호라고 함)
4) 팔로워가 스냅숏 이후 데이터 변경의 미처리분(backlog)을 모두 처리했을 때 따라잡다고 할 수 있음
5) 이후부터 리더에 발생하는 데이터 변화를 이어 처리할 수 있음

 



1.3 노드 중단 처리

- 리더 기반 복제에서 고가용성은 어떻게 달성할 수 있을까?

 

  • 팔로워 장애: 따라잡기 복구
- 각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관
=> 복구 과정
1) 보관된 로그에서 결함이 발생하기 전에 처리한 마지막 트랜잭션을 알아냄
2) 팔로워는 리더에 연결해 팔로워 연결이 끊어진 동안 발생한 데이터 변경을 모두 요청함
3) 이 변경이 다 적용되면 리더를 따라잡게 됨
4) 데이터 변경의 스트림을 계속 받음

 

  • 리더 장애: 장애 복구
- 장애 복구(failover)?
=> 팔로워 중 하나를 새 리더로 승격하고, 클라이언트는 새 리더로 쓰기를 전송하기 위해 재설정을 하고, 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비하기 시작하는 과정

- 자동 장애 복구 단계
1) 리더가 장애인지 판단
2) 새로운 리더를 선택
3) 새로운 리더 사용을 위해 시스템을 재설정
=> 자동 장애 복구는 여러 문제가 생길 수 있기 때문에 일부 운영팀은 수동 방식을 선호하기도 함

 


 

1.4 복제 로그 구현 

- 리더 기반 복제는 내부적으로 어떻게 동작할까?
=> 다양한 복제 방법을 사용함

 

  • 구문 기반 복제
- 리더는 모든 쓰기 요청(구문, statement)을 기록하고 쓰기를 실행한 다음 구문 로그를 팔로워에게 전송함

- MySQL 5.1 이전 버전에서는 구문 기반 복제가 사용됐고, 매우 간편해서 오늘날에도 여전히 사용되기도 하지만 구문에 비결정성이 있다면 기본적으로 로우 기반 복제로 변경함

 

  • 쓰기 전 로그 배송
- 로그는 DB의 모든 쓰기를 포함하는 추가 전용(append-only) 바이트 열로 완전히 동일한 로그를 사용해 다른 노드에서 복제 서버를 구축할 수 있음
=> 리더는 디스크에 로그 기록 외 팔로워에게 네트워크로 로그를 전송하기도 함
=> 팔로워가 로그를 처리하면 리더와 동일한 데이터 구조의 복제본이 만들어짐

- PostgreSQL, 오라클 등에서 사용됨

- 가장 큰 단점은 로그가 제일 저수준의 데이터를 기술한다는 것임
=> 운영상 큰 영향을 미칠 수 있음

 

  • 논리적(로우 기반) 로그 복제
- 복제 로그를 저장소 엔진 내부와 분리하기 위한 대안으로 복제와 저장소 엔진을 위해 다른 로그 형식을 사용하는 것임
=> 이런 종류의 복제로그를 저장소 엔진의 (물리적) 데이터 표현과 구별하기 위해 '논리적 로그'라고 부름

- 관계형 DB용 논리적 로그
=> 보통 로우 단위로 DB 테이블에 쓰기를 기술한 레코드 열임
=> 삽입된 로우의 로그는 모든 칼럼의 새로운 값을 포함함
=> 삭제된 로우의 로그는 로우를 고유하게 식별하는 데 필요한 정보를 포함함
=> 갱신된 로우의 로그는 로우를 고유하게 식별하는 데 필요한 정보와 모든 칼럼의 새로운 값을 포함함

 

  • 트리거 기반 복제
- 트리거는 사용자 정의 애플리케이션 코드를 등록할 수 있게 함
=> DB 시스템에서 데이터가 변경되면(쓰기 트랜잭션) 애플리케이션 코드는 자동으로 실행됨
=> 트리거는 데이터 변경을 분리된 테이블에 로깅할 수 있는 기회를 가짐

- 장단점
=> [장점] 유연성
=> [단점] DB에 내장된 복제보다 버그, 제한 사항이 더 많이 발생함

 


 

2. 복제 지연 문제

- 복제 지연이 있을 때 발생할 수 있는 3가지 사례와 해결방법

 


 

2.1 자신이 쓴 내용 읽기 (쓰기 후 읽기) 일관성

- 리더 기반 복제 시스템에서 쓰기 후 읽기 일관성 구현하는 방법?
1) 사용자가 수정한 내용을 읽을 때는 리더에서 일고, 그 밖에는 팔로워에서 읽음
2) 애플리케이션 내 대부분의 내용을 사용자가 편집할 가능성이 있다면 리더에서 읽을지 말지를 결정하기 위해 다른 기준을 사용
3) 클라이언트는 가장 최근 쓰기의 타임스탬프를 기억
4) 복제 서버가 여러 데이터센터에 분산됐다면 복잡도가 증가함. 리더가 제공해야 하는 모든 요청은 리더가 포함된 데이터센터로 라우팅돼야 함

 



2.2 단조 읽기 (monotonic read)

- 사용자가 시간이 거꾸로 흐르는 현상을 목격할 수 있다는 이상 현상이 발생할 수 있음
=> 단조 읽기를 통해 이전에 새로운 데이터를 읽은 후에는 예전 데이터를 읽지 않도록 함

- 구현 방법?
=> 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게 하기

 



2.3 일관된 순서로 읽기 (consistent prefix read)

- 인과성의 위반 우려라는 이상 현상이 발생할 수 있음
=> 일부 파티션이 다른 것보다 느리게 복제되는 경우 관찰자는 질문을 보기 전에 대답을 볼 수 있음
=> '일관된 순서로 읽기'를 통해 일련의 쓰기가 특정 순서로 발생한다면 이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰여진 내용을 보게 됨을 보장함

- 분산 DB에서 서로 다른 파티션이 독립적으로 동작해 쓰기의 전역 순서가 없는 경우?
=> 서로 인과성이 있는 쓰기가 동일한 파티션에 기록되게끔 하여 해결

 


 

2.4 복제 지연을 위한 해결책

- 복제가 비동기식으로 동작하지만 동기식으로 동작하는 척 하는 것이 문제 해결 방법임
=> 트랜잭션이 있는 이유임

 


 

3. 다중 리더 복제

- 리더 기반 복제의 단점으로는 어떤 이유로 리더에 연결할 수 없다면(클라이언트-리더 간 네트워크 중단 등) DB 쓰기가 불가

- 쓰기를 허용하는 노드를 하나 이상 두는 것으로 확장하는데 이를 다중 리더 설정(마스터 마스터, 액티브/액티브 복제)라고 함
=> 쓰기 처리를 하는 각 노드는 데이터 변경을 다른 모든 노드에 전달
=> 각 리더는 다른 리더의 팔로워 역할을 함

 


 

3.1 다중 리더 복제의 사용 사례

  • 다중 데이터센터 운영
- 리더는 하나의 데이터센터에 있고 모든 쓰기는 해당 데이터센터를 거치는 방식
=> 성능? 사용자가 인지하는 성능은 더 좋음(모든 쓰기는 로컬 데이터센터에서 처리한 다음 비동기 방식으로 다른 데이터 센터에 복제하므로 데이터센터 간 네트워크 지연은 사용자에게 숨겨짐)
=> 데이터센터 중단 내성? 각 데이터센터는 다른 데이터센터와 독립적으로 동작하고 고장 난 데이터센터가 온라인으로 돌아왔을 때 복제를 따라잡음
=> 네트워크 문제 내성? 데이터센터 간 트래픽은 보통 공개 인터넷을 통해 처리되므로 데이터센터 내 로컬 네트워크보다 안전성이 떨어짐. 하지만 비동기 복제를 사용하므로 일시적인 네트워크 중단에도 쓰기 처리가 진행되므로 네트워크 문제에 보다 잘 견딤

- 단점으로는 동일한 데이터를 다른 2개의 데이터 센터에서 동시에 변경할 수 있어 발생하는 쓰기 충돌을 해소해야 함

 

  • 오프라인 작업을 하는 클라이언트
- 오프라인 상태에서 데이터를 변경하면 디바이스가 온라인 상태가 됐을 때 서버와 다른 디바이스를 동기화
=> 모든 디바이스 상 복제 서버 간 다중 리더 복제를 비동기 방식으로 수행하는 프로세스(동기화)
=> 아키텍처 관점에서 근복적으로는 데이터센터 간 다중 리더 복제와 동일함
=> 카우치DB가 이런 동작 모드를 위해 설계됨

 

  • 실시간 협업 편집
- 한 사용자가 문서를 편집할 때 변경 내용을 즉시 로컬 복제 서버에 적용하고 나서 동일한 문서를 편집하는 다른 사용자와 서버에 비동기 방식으로 복제함

- 편집 충돌이 없음을 보장하려면 애플리케이션은 사용자가 편집하기 전에 문서의 잠금을 얻어야 함
=> 다른 사용자가 같은 문서를 편집하려면 첫 번째 사용자의 변경이 커밋되고 잠금이 해제될 때까지 기다려야 함
=> 리더에서 트랜잭션을 사용하는 단일 리더 복제와 동일한 협업 모델임

- 더 빠른 협업을 위해 변경 단위를 매우 작게 하여 잠금을 피할 수 있음
=> 여러 사용자가 동시 편집은 할 수 있지만 충돌 해소 등 다중 리더 복제에서 발생하는 모든 문제를 야기

 



3.2 쓰기 충돌 다루기

  • 동기 vs 비동기 충돌 감지
- 충돌 감지는 동기식으로 만들 수 있음
=> 쓰기가 성공한 사실을 사용자에게 말하기 전 모든 복제 서버가 쓰기를 복제하기를 기다림
=> 하지만 이 방식은 다중 리더 복제의 주요 장점인 각 복제 서버가 독립적으로 쓰기를 허용한다는 것을 잃음
=> 단일 리더 복제만 사용해야 할 수도 있음

 

  • 충돌 회피
- 충돌 처리의 제일 간단한 전략임
=> 특정 레코드의 모든 쓰기가 동일한 리더를 거치도록 애플리케이션이 보장하면 충돌은 발생하지 않음

- 충돌 회피가 실패하는 경우? 다른 리더에서 동시 기록 가능성을 대처해야 함
=> 한 데이터센터가 고장나 트래픽을 다른 데이터센터로 다시 라우팅해야 하는 상황
=> 사용자가 다른 지역으로 이동해 현재는 다른 데이터센터가 가깝다면 레코드를 위해 지정된 리더를 변경하고 싶은 경우

 

  • 일관된 상태 수렴
- 단일 리더 DB는 순차적인 순서로 쓰기를 적용하지만 다중 리더 설정에서는 쓰기 순서가 정해지지 않아 최종 값이 명확하지 않음
=> 모든 변경이 복제돼 모든 복제 서버에 동일한 최종 값이 전달되게 해야 하는 '수렴(convergent)' 방식으로 충돌을 해소해야 함

- 수렴 충돌 해소 방법?
1) 각 쓰기에 고유 ID를 부여하고 가장 높은 ID를 가진 쓰기를 고르고, 다른 쓰기는 버림
=> 타임스탬프를 사용하는 경우를 '최종 쓰기 승리(last write wins, LWW)'라고 함
=> 대중적인 방법이지만 데이터 유실 위험이 있음

2) 각 복제 서버에 고유 ID를 부여하고 높은 숫자의 복제 서버에서 생긴 쓰기가 낮은 숫자의 복제 서버에서 생긴 쓰기보다 항상 우선적으로 적용되게 함
=> 데이터 유실 가능성이 있음

3) 값을 병합
=> 예를 들어 일정 순서대로 정렬할 후 연결하는 방식이 있음

4) 명시적 데이터 구조에 충돌을 기록해 모든 정보를 보존하고 나중에 충돌을 해소하는 애플리케이션 코드를 작성

 

  • 사용자 정의 충돌 해소 로직
- 충돌 해소에 가장 적합한 방법은 애플리케이션에 따라 달라지므로 애플리케이션 코드를 사용해 충돌 해소 로직을 작성함
=> 해당 코드는 쓰기나 읽기 수행 중에 실행될 수 있음
1) 쓰기 수행 중? 복제된 변경 사항 로그에서 DB 시스템이 충돌을 감지하자마자 충돌 핸들러를 호출
2) 읽기 수행 중? 충돌을 감지하면 모든 충돌 쓰기를 저장

- 충돌 해소는 전체 트랜잭션이 아닌 개별 로우나 문서 수준에서 적용됨

 

  • 충돌은 무엇인가?
- 어떤 종류의 충돌은 감지하기 어려운 경우도 있음
=> 복제 시스템에서 충돌을 감지하고 해소하는 확장 가능한 접근 방식이 필요함

 



3.3 다중 리더 복제 토폴로지

- 복제 토폴로지(topology)
=> 쓰기를 한 논드에서 다른 노드로 전달하는 통신 경로

- 다중 리더 복제를 설정하는 3가지 토폴로지
1) 전체 연결(all-to-all) 토폴로지
=> 모든 리더가 각자의 쓰기를 다른 모든 리더에서 전송
=> 단점? 일부 네트워크 연결이 다른 연결보다 빠르다면 일부 복제 메시지가 다른 메시지를 추월할 수 있음. 이런 이벤트를 올바르게 정렬하기 위해 '버전 벡터(version vector)' 기법을 사용할 수 었음

2) 원형 토폴로지
=> 각 노드가 하나의 노드로부터 쓰기를 받고, 이 쓰기를 다른 한 노드에 전달

3) 별 모양 토폴로지
=> 지정된 루트 노드 하나가 다른 모든 노드에 쓰기를 전달

- 원형, 별 모양 토폴로지 단점?
=> 하나의 노드에 장애가 발생하면 장애가 다른 노드 간 복제 메시지 흐름에 방해함 (해당 노드가 복구될 때까지 통신할 수 없음)

 


 

4. 리더 없는 복제

- 초기 복제 데이터 시스템은 대부분 리더가 없었음
=> 관계형DB가 우세한 시대에는 대부분 잊혀짐
=> 다이나모(Dynamo) 스타일이라고 함

- 일부 리더 없는 복제 구현에서는 클라이언트가 여러 복제 서버에 쓰기를 직접 전송하는 반면 코디네이터 노드가 클라이언트를 대신해 이를 수행하기도 함
=> 하지만 특정 순서로 쓰기를 수행하지 않음

 


 

4.1 노드가 다운됐을 때 DB에 쓰기

- 리더 없는 설정에서는 장애 복구가 필요하지 않지만 노드가 다운된 동안 발생한 모든 쓰기는 해당 노드에서 누락되는 문제가 있음
=> 이를 해결하기 위해 클라이언트가 DB에서 읽을 때 하나의 복제 서버로 요청을 보내지 않고 읽기 요청을 병렬로 여러 노드에 정송함

 

  • 읽기 복구와 안티 엔트로피
- 사용 불가한 노드가 온라인 상태가 된 후 누락된 쓰기를 어떻게 따라잡을까?
=> 다이나모 스타일 데이터스토어는 2가지 메커니즘을 주로 사용함
1) 읽기 복구
=> 클라이언트가 여러 노드에서 병렬로 읽기를 수행하면 오래된 응답을 감지할 수 있음. 클라이언트는 해당 복제 서버에 새로운 값을 다시 기록함
=> 값을 자주 읽는 상황에 적합

2) 안티 엔트로피 처리
=> 백그라운드 프로세스를 두고 복제 서버 간 데이터 차이를 지속적으로 찾아 누락된 데이터를 하나의 복제 서버에서 다른 서버로 복사
=> 특정 순서로 쓰기를 복사하기 때문에 데이터가 복사되기까지 상당한 지연이 있을 수 있음

 

  • 읽기와 쓰기를 위한 정족수
- 3개의 복제 서버 중 하나만 쓰기를 허용한다면 어느 범위까지 허용해야 할까?
=> 적어도 2개의 복제 서버에서 읽으면 2개 중 하나는 최신 값인지 여부를 확인할 수 있음
=> n, w, r 파라미터 설정이 가능함. 보통 w = r = (n+1)/2반올림 으로 설정함
=> 필요한 w나 r개 노드보다 사용 가능한 노드가 적다면 쓰기나 읽기는 에러를 반환함

 



4.2 정족수 일관성의 한계

- 읽기와 쓰기 동작에서 사용하는 노드 셋 중 적어도 하나의 노드만 겹치면 되므로 정족수가 다수일 필요는 없음
=> 정족수가 읽기 시 최근에 쓴 값을 반환하게끔 보장하지만 실제로는 그렇게 간단하지 않음
=> 매개변수 w,r로 오래된 갓을 읽는 확률을 조정할 수 있지만 절대적으로 보장할 수는 없음

 

  • 최신성 모니터링
- DB가 최신 결과를 반환하는지 여부를 모니터링하는 것은 중요함
=> 리더 기반 복제에서 DB는 일반적으로 복제 지연에 대한 지표를 노출함
=> 리더 없는 복제 시스템에서는 쓰기가 적용된 순서를 고정할 수 없어 모니터링이 더 어려움

 



4.3 느슨한 정족수와 암시된 핸드오프

- 적절히 설정된 정족수가 있는 DB는 장애 복구 없이 개별 노드 장애를 용인함
=> 높은 가용성과 낮은 지연 시간이 필요함

- 정족수는 내결함성이 없어 네트워크 중단으로 다수의 DB 노드와 클라이언트는 쉽게 연결이 끊어질 수 있음
=> w나 r 노드 정족수를 만족하지 않는 모든 요청에 오류를 반환하는 것이 더 좋을까?
=> 일단 쓰기를 받아들이고 값이 보통 저장되는 n개 노드에 속하지는 않지만 연결할 수 있는 노드에 기록할까? 느슨한 정족수라고 부름

- 암시된 핸드오프?
=> 네트워크 장애 상황이 해제되면 한 노드가 다른 노드를 위해 일시적으로 수용한 모든 쓰기를 해당 홈 노드로 전송하는 방식

- 느슨한 정족수?
=> 쓰기 가용성을 높이는 데 특히 유용함
=> 전통적인 의미의 정족수가 아님. 단지 지속성에 대한 보장으로 데이터가 w 노드 어딘가에는 저장된다는 의미임. 암시된 핸드오프가 완료될 때까지 r 노드의 읽기가 저장된 데이터를 본다는 보장은 없음
=> 모든 일반적인 다이나모 구현에서는 선택 사항임

 

  • 다중 데이터센터 운영
- 리더 없는 모델도 동시 쓰기 충돌, 네트워크 중단, 지연 시간 급증을 허용하기 때문에 다중 데이터센터 운영에 적합함
=> n개의 복제 서버 수에는 모든 데이터센터의 노드가 포함되고 설정에서 각 데이터센터마다 n개의 복제 서버 중 몇 개를 보유할지를 지정할 수 있음
=> 클라이언트의 각 쓰기는 데이터센터 상관없이 모든 복제 서버에 전송되지만 클라이언트는 보통 로컬 데이터센터 안에서 정족수 노드의 확인 응답을 기다리기 때문에 데이터센터 간 연결의 지연과 중단에 영향을 받지 않음
=> 다른 데이터센터에 대한 높은 지연 시간의 쓰기는 설정에 어느 정도 유연성이 있지만 대개 비동기로 발생하게끔 설정함

 



4.4 동시 쓰기 감지

- 다이나모 스타일 DB는 여러 클라이언트가 동시에 같은 키에 쓰느 넋을 허용하기 때문에 엄격한 정족수를 사용하더라도 충돌이 발생함
=> 문제는 다양한 네트워크 지연과 부분적인 장애 때문에 이벤트가 다른 노드에 다른 순서로 도착할 수 있다는 것임
=> 최종적인 일관성을 달성하기 위해 복제본들은 동일한 값이 되어야 함

 

  • 최종 쓰기 승리(동시 쓰기 버리기)
- 최종적으로 값을 수렴하기 위한 접근 방식으로 각 복제본이 예전 값을 버리고 가장 최신 값으로 덮어쓰는 것이 있음
=> 어떤 쓰기가 최신인지 명확하게 결정할 수 있는 한 모든 쓰기는 최종적으로 모든 복제 서버에 복사되므로 복제본은 최종적으로 동일한 값으로 수렴함
=> 쓰기는 자연적인 순서가 없지만 임의로 순서를 정할 수 있음

 

  • '이전 발생' 관계와 동시성
- 이전 발생?
=> 작업 A 삽입이 작업 B의 증가 이전에 발생
=> 작업 A는 작업 B의 이전 발생(happens-before)임
=> B는 A에 인과성이 있다고 함(causally dependent)

- 작업이 다른 작업보다 먼저 발생하지 않으면 '동시 작업'이라고 함

 

  • 이전 발생 관계 파악하기
- 서버는 버전 번호를 보고 두 작업이 동시에 수행됐는지 여부를 결정할 수 있으므로 값 자체를 해석할 필요는 없음. 값을 데이터 구조로 사용할 수 있음

- 해당 알고리즘의 동작 방식
=> 어떤 데이터도 자동으로 삭제되지 않음을 보장하지만 클라이언트가 추가적으로 작업을 수행해야 함
1) 서버가 모든 키에 대한 버전 번호를 유지하고 키를 기록할 때마다 버전 번호를 증가시킴. 기록한 값은 새로운 버전 번호를 가지고 저장됨
2) 클라이언트가 키를 읽을 때는 서버는 최신 버전 뿐만 아니라 덮어쓰지 않은 모든 값을 반환함. 클라이언트는 쓰기 전에 키를 읽어야 함
3) 클라이언트가 키를 기록할 때는 이전 읽기의 버전 번호를 포함해야 하고 이전 읽기에서 받은 모든 값을 함께 합쳐야 함
4) 서버가 특정 버전 번호를 가진 쓰기를 받을 때 해당 버전 이하 모든 값을 덮어쓸 수 있음. 하지만 이보다 높은 버전의 모든 값은 유지해야 함 (이 값들은 유입된 쓰기와 동시에 발생했기 때문임)

 

  • 동시에 쓴 값 병합
- 여러 작업이 동시에 발생하면 클라이언트는 동시에 쓴 값을 합쳐 정리해야 함
=> 동시 값은 형제(sibling) 값이라고 함

- 형제 값의 병합은 다중 리더 복제에서 충돌 해소 문제와 본질적으로 같음
=> 시스템은 형제를 병합할 때 툼스톤이라는 삭제 표시를 함
=> 리악의 데이터타입 지원은 합리적인 방법으로 형제를 자동 병합할 수 있는 CRDT[38, 39, 55]라는 데이터 구조군을 사용함

 

  • 버전 벡터
- 버전 벡터?
=> 모든 복제본의 버전 번호 모음

- 리악에서 사용하는 도티드 버전 벡터라는 방식이 있음
=> 버전 벡터는 값을 읽을 때 DB 복제본에서 클라이언트로 보냄. 이후에 값이 기록될 때 DB로 다시 전송해야 함
=> 이를 사용하면 DB는 덮어쓰기와 동시 쓰기를 구분할 수 있음

- 형제를 병합해야 할 수도 있음
=> 버전 벡터 구조는 하나의 복제본을 읽은 다음 이어 다른 복제본에 다시 쓰는 작업이 안전함을 보장함
=> 이렇게 하면 형제가 생성돼도 형제가 올바르게 병합되는 한 데이터 손실은 없음