복제

분산 데이터

여러 장비간 분산된 데이터베이스를 필요로 하는 이유는 여러가지이다.

확장성 : 단일 장비에서 다룰 수 있는 야보다 커지면 부하를 여러 장비로 분배할 수 있다.

내결함성/고가용성 : 장비 하나가 죽더라도 애플리케이션이 계속 동작해야 한다면 여러 장비를 사용해 중복성을 제공할 수 있다. 장비 하나가 실패하면 다른 하나가 이어받는다.

지연시간 : 전 세계에 사용자가 있다면 사용자와 지리적으로 가까운 곳의 데이터센터에서 서비스를 제공한다.

고부하로 확장

더 강력한 장비를 구매하는 것이 가장 간단한 방법(수직확장, 용량확장). 많은 CPU, 많은 메모리 칩, 많은 디스크를 하나의 운영체제로 함께 결합할 수 있다. 빠른 상호 연결로 모든 CPU가 메모리나 디스크의 모든 부분에 접근. 공유 메모리 아키텍처(shared-memory architecture) 모든 구성 요소를 단일 장비처럼 다룰 수 있다.

비용이 선형적인 추세보다 빠르게 증가한다는 점. 병목 현상 때문에 두배 크기의 장비가 반드시 두 배의 부하를 처리할 수 있는 것은 아니다. 제한적인 내결함성을 제공한다.

공유 디스크 아키텍처는 독립적인 CPU, RAM을 탑재한 여러 장비를 사용하지만 데이터 저장은 장비가 공유하는 디스크 배열에 한다. 여러 장비는 고속 네트워크로 연결된다. 경합과 오버헤드가 공유 디스크 접근 방식의 확장성을 제한한다.

비공유 아키텍처(shared-nothing)(수평확장, 규모확장) .이 접근 방식에서는 데이터베이스 소프트웨어를 수행하는 각 장비나 가상 장비를 노드라고 부른다. 각 노드는 CPU, RAM 디스크를 독립적으로 사용한다. 노드간 코디네이션은 일반적인 네트워크를 사용해 소프트웨어 수준에서 수행한다.

특별한 하드웨어를 필요로 하지 않으므로 가격 대비 성능이 가장 좋은 시스템을 사용할 수 있다. 가상 장비의 클라우드 배포를 활용하면 소규모 회사라도 이제 다중 지역 분산 아키텍처가 실형 가능하다.

복제 VS 파티셔닝

복제 : 같은 데이터의 복사본을 여러 노드에 유지한다. 성능 향상에 도움이 된다.

파티셔닝(샤딩) : 큰 DB를 파티션이라는 작은 서브셋으로 나누고 각 파티션은 각기 다른 노드에 할당한다.

복제

복제란 네트워크로 연결된 여러 장비에 동일한 데이터의 복사본을 유지한다는 의미. 지리적으로 사용자와 가깝게 데이터를 유지해 지연 시간을 줄인다. 시스템의 일부에 장애가 발생해도 지속적으로 동작할 수 있게 해 가용성을 높인다.

읽기 질의를 제공하는 장비의 수를 확장해 읽기 처리량을 늘린다.

복제에서 모든 어려움은 복제된 데이터의 변경 처리에 있으며 이것이 이번 장의 내용이다. 노드 간 변경을 복제하기 위한 세 가지 인기 있는 알고리즘 단일리더, 다중 리더, 리더 없는 복제. 모든 DB는 세 가지 방법 중 하나를 사용한다.

복제에는 많은 트레이드오프가 있다. 동기식 복제, 비동기식 복제, 잘못된 복제본을 어떻게 처리할 지다. 최종적 일관성과 같은 문제.

리더와 팔로워

복사본을 저장하는 각 노드를 복제 서버(replica)라고 한다. 모든 복제 서버에 모든 데이터가 있다는 사실을 어떻게 보장할 수 있는가?

모든 쓰기는 모든 복제 서버에서 처리돼야 한다. 그렇지 않으면 복제 서버는 더 이상 동일한 데이터를 유지할 수 없다. 일반적인 해결책은 리더 기반 복제(마스터 슬레이브 복제)이다.

복제 서버 중 하나를 리더로 지정하고, 클라이언트가 데이터베이스에 쓰기를 할 때 클라이언트는 요청을 리더에게 보내고 로컬 저장소에 새로운 데이터를 기록한다. 다른 복제 서버는 슬레이브 라고 한다. 리더가 로컬 저장소에 새로운 데이터를 기록할 때마다 데이터 변경을 복제 로그나 변경 스트림의 일부를 팔로워에게 전송한다. 팔로워가 로그를 받으면 리더가 처리한 것과 동일한 순서로 모든 쓰기를 적용해 그에 맞게 데이터베이스의 로컬 복사본을 갱신한다.

질의는 모든 데이터베이스에게 할 수 있지만, 쓰기는 리더한태만 허용된다. 여러 관계형 데이터베이스에 내장된 기능. 일부 네트워크 파일 시스템과 DRBD같은 복제 블록 디바이스도 유사하다.

동기식 대 비동기식 복제

복제가 동기식으로 발생하는지 비동기식으로 발생하는지 여부가 중요하다.

동기식 복제의 장점은 팔로워가 리더와 일관성 있게 최신 데이터 복사본을 가지는 것을 보장한다. 단점은 동기 팔로워가 응답하지 않는다면 쓰기가 처리될 수 없다는 것이다. 그래서 동기식인 상황은 비현실적이다. 현실적으로 보통 팔로워 하나는 동기식으로 하고 그 밖에는 비동기식으로 하는 방법. 사용할 수 없게 되거나 느려지면 비동기식 팔로워 중 하나가 동기식이 된다. 반동기식

완전 비동기식 설정은 모든 팔로워가 잘못되더라도 리더가 쓰기 처리를 계속 할 수 있는 장점. 내구성을 약화시키기 때문에 나쁜 트레이드 오프 같지만 팔로워가 많거나 지리적으로 분산됐다면 이 방식을 사용한다.

새로운 팔로워 설정

리더의 스냅샷을 일정 시점에 가지고 오고, 복사 중 발생한 모든 데이터 변경을 요청한다. 데이터 변경의 미처리분을 모두 처리했을 때 따라잡았다고 말한다. 이제부터 리더에 발생하는 데이터 변화를 이어 처리할 수 있다.

노드 중단 처리

중단없이 개별 노드를 재부팅 할 수 있게 해야 한다. 개별 노드의 장애에도 전체 시스템이 동작하게끔 유지하고 노드 중단의 영향을 최소화하는 것이 목표다.

팔로워 장애 : 따라잡기 복구

각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관한다. 팔로워가 죽어 재시작 하거나 네트워크가 일시적으로 중단된다면 팔로워는 보관된 로그에서 결함이 발생하기 전에 처리한 마지막 트랜잭션을 처리하고. 리더에 연결해 끊어진 동안 발생한 데이터 변경을 모두 요청한다.

리더 장애 : 장애 복구

팔로워 중 하나를 새로운 리더로 승격하고, 새로운 리더로 쓰기를 전송하기 위해 재설정이 필요하며, 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비하기 시작해야한다.

1. 리더가 장애인지 판단.
2. 새로운 리더를 선택
3. 새로운 리더 사용을 위해 시스템을 재설정한다. 다른 복제 서버들이 자신을 리더인지 아닌지 판단.

이 문제에 대한 쉬운 해결책은 없다. 그래서 자동 장애 복구를 지원하더라도 수동으로 장애 복구를 수행하는 방법을 선호한다.

복제 로그 구현

구문 기반 복제

리더는 모든 쓰기 요청을 기록하고 쓰기를 실행한 다음 구문 로그를 팔로워에게 전송한다.(insert, update, delete) .복제가 깨질 수 있는 상황

1. 현재 날짜와 시간을 얻기 위한 now()의 경우, rand()의 경우
2. 자동증가 칼럼의 경우 각 복제 서버에서 정확히 같은 순서로 실행돼야 한다.
3. 트리거 사용자 정의함수의 부수 효과가 완벽하게 결정적이지 않으면 
   각 복제 서버에서 다른 부수효과가 발생할 수 있다.

모든 비결정적 함수 호출을 고정 값을 반환하게끔 대체한다.

쓰기 전 로그 배송:

모든 쓰기는 로그에 기록한다. 완전히 동일한 로그를 사용해 다른 노드에서 복제 서버를 구축할 수 있다. 리더는 디스크에 로그를 기록하는 일 외에도 팔로워에게 네트워크로 로그를 전송하기도 한다.

논리적 로그 복제:

복제와 저장소 엔진을 위해 다른 로그 형식을 사용하는 것. 여러 로우를 수정하는 트랜잭션은 여러 로그 레코드를 생성한 다음 트랜잭션이 커밋 됐음을 레코드에 표기한다. 논리적 로그를 저장소 엔진 내부와 분리했기 때문에 하위 호환성을 더 쉽게 유지, 리더, 팔로워에서 다른 버전의 데이터베이스 SW나 심지어 다른 저장소 엔진을 실행할 수 있다. 부 애플리케이션이 파싱하기 더 쉽다. 외부 시스템이나 DW에 내용을 전송하고자 할 때 유용하다. 변경 데이터 캡쳐.

복제 지연 문제

리더기반 복제는 많은 팔로워를 만들어 팔로워 간 읽기 요청을 분산하는 매력적이 옵션이 있다 이런 읽기 확장 아키텍처에서는 간단히 팔로워를 더 추가함으로써 읽기 전용 요청을 처리하기 위한 용량을 늘릴 수 있다. 이 접근 방식은 비동기식 복제에서만 동작한다. 팔로워가 뒤처진다면 지난 정보를 볼 수도 있고, 명백하게 불일치가 발생한다. 최종적 일관성.

애플리케이션에서 지연이 매우 크면 불일치는 이론적인 문제가 아니라 실제 문제가 된다.

자신이 쓴 내용 읽기:

쓰기 후 읽기 일관성(자신의 쓰기 읽기 일관성)이 필요하다. 사용자가 페이지를 재로딩했을 때 항상 자신이 제출한 모든 갱신을 볼 수 있음을 보장하며 다른 사용자에 대해서는 보장하지 않는다.

1. 사용자가 수정한 내용을 읽을 때는 리더에서 읽는다. Ex)사용자 소유의 프로필은 리더에서 읽고 다른 사용자의 프로필은 팔로워에서 읽는 간단한 규칙을 만든다.

마지막 갱신 후 1분 동안은 리더에서 모든 읽기를 수행한다. 가장 최근 쓰기의 타임스탬프를 기억한다.

단조읽기

시간이 거꾸로 흐르는 현상을 목격할 수 있다. 사용자가 각기 다른 복제 서버에서 여러 읽기를 수행할 때 발생한다. 단조 읽기(monotonic read)는 이런 현상이 발생하지 않음을 보장한다. 최종적 일관성보다는 더 강한 보장이다. 한 사용자가 여러 번 읽어도 시간이 되돌아가는 현상을 보지 않는다는 의미. 새로운 데이터를 읽은 후에는 예전 데이터를 읽지 않는다.

일관된 순서로 읽기

인과성의 위반 우려다.일관된 순서로 읽기는 일련의 쓰기가 특정 순서로 발생한다면 이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰여진 내용을 보게 됨을 보장한다. 서로 인과성이 있는 쓰기가 동일한 파티션에 기록되게끔 하는 방법.

다중 리더 복제

리더가 하나인 경우 어떤 이유로 리더에 연결할 수 없다면 데이터베이스에 쓰기를 할 수 없다. 쓰기 처리를 하는 각 노드는 데이터 변경을 다른 모든 노드에 전달해야 한다. 다중 리더 설정

성능, 데이터센터 중단 내성, 네트워크 문제 내성의 장점이 있지만 동일한 데이터를 다른 두 개의 데이터센터에서 동시에 변경할 수 있다.

쓰기 충돌 다루기:

이론적으로 충돌 감지는 동기식으로 만들 수 있지만 다중 리더 복제의 주요 장점을 잃는다.

1. 충돌 회피

특정 레코드의 모든 쓰기가 동일한 리더를 거치도록 애플리케이션이 보장한다면 충돌은 발생하지 않는다. 특정 사용자의 요청을 동일한 데이터센터로 항상 라우팅하고 그 리더를 사용해 읽기와 쓰기를 하게끔 보장할 수 있다.

2. 일관된 상태 수렴

다중 리더 설정에서는 쓰기 순서가 정해지지 않아 최종 값이 무엇인지 명확하지 않다. 따라서 데이터베이스는 수렴 방식으로 충돌을 해소해야 한다.

각 쓰기에 고유 ID를 부여하고 가장 높은 ID를 가진 쓰기를 고르고 나머지는 버린다. 타임스탬프를 사용하는 경우 최종 쓰기 승리. 이 방법은 대중적이지만 데이터 유실 위험이 있다.

각 복제 서버에 고유 ID를 부여하고 높은 숫자의 복제 서버에서 생긴 쓰기가 항상 우선적으로 적용되게 한다.

어떻게든 값을 병합하는 방법.

명시적 데이터 구조에 충돌을 기록해 모든 정보를 보존한다. 나중에 사용자가 선택하게 한다.

사용자 정의 충돌 해소 로직

애플리케이션에 따라 다르다.

쓰기 수행 중엔 충돌 핸들러를 호출한다. 일반적으로 사용자에게 충돌 내용을 표시하지 않고 백그라운드에서 실행돼야 한다.

읽기 수행 중에 충돌을 감지하면 모든 충돌 쓰기를 저장하고 여러 버전의 데이터가 애플리케이션에 반환된다.

알고리즘

1. 충돌 없는 복제 데이터타입 : 동시 여러 사용자가 편집할 수 있고 합리적인 방법으로 충돌을 자동해소 한다.

2. 병합 가능한 영속 데이터 구조 : Git버전 제어 시스템과 유사하게 명시적으로 히스토리를 추적하고 삼중 병합 함수를 사용한다.

3. 운영 변환

다중 리더 복제 토폴로지

원형 토폴로지, 별 모양 토폴로지는 하나의 노드에 장애가 발생하면 다른 노드 간 복제 메시지 흐름에 방해를 준다.

전체 연결 토폴로지는 단일 장애점을 피할 수 있기 때문에 내결함성이 훨씬 더 좋다. 그러나 일부 네트워크 연결이 다른 연결보다 빠르다면 일부 복제 메시지가 다른 메시지를 추월할 수 있다. 일관된 순서로 읽기의 문제가 발생한다.

리더 없는 복제

모든 복제 서버가 클라이언트로부터 쓰기를 직접 받을 수 있게 허용하는 접근 방식을 사용 다이나모 스타일. 클라이언트가 여러 복제 서버에 쓰기를 직접 전송하는 반면 코디네이터 노드가 클라이언트를 대신해 이를 수행하기도 한다.

리더 없는 설정에서는 장애 복구가 필요하지 않다. 쓰기를 할 때, 읽기를 할 때 모든 DB로 병렬 요청을 한다. 그리고 최신 내용을 사용한다.

사용 불가능한 노드가 온라인 상태가 된 후 누락된 쓰기를 어떻게 따라잡는가?

읽기 복구: 애플리케이션에서 처리. DB에서는 그냥 무시

안티 엔트로피 처리: 백그라운드 프로세스를 두고 복제 서버 간 데이터 차이를 지속적으로 찾아 누락된 데이터를 하나의 복제 서버에서 다른 서버로 복사한다. 상당한 지연이 있다.

버전 벡터

키당 버전 번호뿐만 아니라 복제본당 버전 번호도 사용해야 한다. 각 복제본은 쓰기를 처리할 때 자체 버전 번호를 증가시키고 각기 다른 복제본의 버전 번호도 계속 추적해야 한다. 이 정보는 덮어쓸 값과 형제로 유지할 값을 나타낸다. 모든 복제본의 버전 번호 모음을 버전 벡터라 한다. 버전 벡터는 값을 읽을 때 DB 복제본에서 클라이언트로 보낸다. 이후에 값이 기록될 때 DB로 다시 전송해야 한다. 이 버전 벡터를 사용하면 데이터베이스는 덮어쓰기와 동시 쓰기를 구분할 수 있다. 애플리케이션은 형제를 병합해야 할 수도 있다. 버전 벡터 구조는 하나의 복제본을 읽은 다음 이어 른 복제본에 다시 쓰는 작업이 안전함을 보장한다.

정리

복제는 다양한 용도로 사용할 수 있다

1. 고가용성: 한 장비가 다운될 때도 시스템이 계속 동작하게 한다.

2. 연결이 끊긴 작업 : 네트워크 중단이 있을 때도 애플리케이션이 계속 동작할 수 있게 한다.

3. 지연 시간: 지리적으로 사용자에게 가까이 데이터를 배치해 사용자가 더 빠르게 작업할 수 있게 한다.

4. 확장성: 복제본에서 읽기를 수행해 단일 장비에서 처리할 수 있는 양보다 많은 양의 읽기 작업을 처리할 수 있다.

복제에 대한 접근 방식

1. 단일 리더 복제: 모든 쓰기는 단일 노드(리더)로 전송하고 리더는 변경 이벤트 스트림을 다른 복제 서버(팔로워)로 전송한다. 팔로워의 읽기는 오래된 값일 수도 있다.

2. 다중 리더 복제: 클라이언트는 각 쓰기를 여러 리더 노드 중 쓰기를 받아들일 수 있는 노드로 전송한다. 리더는 변경 이벤트 스트림을 다른 리더와 모든 팔로워 노드로 전송한다.

3. 리더 없는 복제: 각 쓰기를 여러 노드로 전송한다. 클라이언트는 오래된 데이터를 감지하고 이를 바로잡기 위해 병렬로 읽는다.

복제는 동기 또는 비동기로 이뤄진다. 비동기 복제는 시스템이 원활히 동작할 때는 빠르지만 복제 지연이 증가하고 서버 장애가 발생하면 처리하는 작업이 중요하다. 리더가 고장나면 최근에 커밋된 데이터를 잃을 수 있다.

복제 지연으로 인해 발생할 수 있는 현상

1. 쓰기 후 읽기 일관성 : 사용자는 자신이 제출한 데이터를 항상 볼 수 있어야 한다.

2. 단조 읽기: 어떤 시점의 데이터를 읽고 나면 그 이전 데이터는 볼 수 없다.

3. 일관된 순서로 읽기: 인과성이 있는 상태의 데이터를 봐야한다.

다중 리더 복제와 리더 없는 복제는 여러 쓰기가 동시에 발생하는 상황을 허용하기 때문에 충돌이 발생할 수 있다.

Last updated