Table of Contents
복제는 네트워크로 연결된 여러 장비에 동일한 데이터의 복사본을 유지한다는 의미이다.
복제가 필요한 이유
- 지리적으로 사용자와 가깝게 데이터를 유지 -> 지연 시간 단축
- 가용성
- 읽기 처리량 향상
복제의 어려움
- 복제된 데이터의 변경 처리
복제를 위한 세 가지 대표적인 복제 알고리즘
- 단일 리더(single-leader)
- 다중 리더(multileader)
- 리더 없는(leaderless)
복제에는 고려해야 할 많은 트레이드오프가 있다.
- 동기식 & 비동기식 복제
- 잘못된 복제본 처리 방법
- …
복제 서버(replica)는 데이터베이스의 복사본을 저장하는 각 노드를 의미한다.
데이터베이스의 모든 쓰기는 모든 복제 서버에서 처리돼야 한다.
리더와 팔로워
가장 일반적인 방식으로 리더 기반 복제(leader-based replication)이다.
능동(active)/수동(passive), 마스터(master) 슬레이브(slave) 복제라고도 한다.
구성
- 리더(leader) 또는 마스터, 프라이머리
클라이언트가 데이터베이스에 쓰기를 할 때 요청을 리더에게 보내야 함
리더는 먼저 로컬 저장소에 새로운 데이터를 기록 - 팔로워(follower) 또는 읽기 복제 서버(read replica), 슬레이브, 2차(secondary), 핫 대기(hot standby)
리더가 새로운 데이터를 기록할 때마다 데이터 변경을 복제 로그(replication log)나 변경 스트림(change stream)의 일부로 팔로워에게 전송
받은 로그로 리더가 처리한 것과 동일한 순서로 적용해 로컬 복사본을 갱신
클라이언트가 데이터베이스로부터 읽기를 할 때는 리더 또는 임의 팔로워에게 질의할 수 있다.
하지만 쓰기는 리더에게만 허용된다.
리더 기반 복제
동기식 대 비동기식 복제
하나의 동기식 팔로워, 하나의 비동기식 팔로워의 리더 기반 복제
동기식 복제
- 장점
- 팔로워가 리더와 일관성 있게 최신 데이터 복사본을 가지는 것을 보장
- 단점
- 동기 팔로워가 응답하지 않는다면 쓰기가 처리될 수 없음
때문에 모든 팔로워가 동기식인 상황은 비현실적
- 동기 팔로워가 응답하지 않는다면 쓰기가 처리될 수 없음
현실적으로 동기식 복제를 사용하려면 반동기식 복제 방식이 있다.
반동기식(semi-synchronous)
팔로워 하나는 동기식, 나머지는 비동기식으로 하는 것을 의미
적어도 두 노드에 데이터의 최신 복사본이 있는 것을 보장
완전한 비동기식
장점
- 모든 팔로워가 잘못되더라도 리더가 쓰기 처리를 계속 할 수 있음
단점
- 클라이언트에게 응답을 받은 경우에도 지속성을 보장하지 않음
리더가 잘못되고 복구할 수 없으면 팔로워에 아직 복제되지 않은 모든 쓰기는 유실
많은 팔로워가 있거나 지리적으로 분산됐다면 비동기식 복제를 널리 사용하며, 리더 기반 복제의 일반적인 구성이다.
새로운 팔로워 설정
중단 없이 새로운 팔로워가 리더의 데이터 복제본을 일관성 있게 만드는 과정을 개념적으로 살펴보자.
- 가능한 전체 데이터베이스를 잠그지 않고 리더의 데이터베이스 스냅숏을 일정 시점에 가져온다.
- 시냅숏을 새로운 팔로워 노드에 복사
- 팔로워는 리더에 연결해 스냅숏 이후 발생한 모든 데이터 변경을 요청(리더 복제 로그의 정확한 위치 필요)
포스트그레스큐엘의 로그 일련번호(log sequence number)
MySQL의 이진로그 좌표(binlog coordinate), GTID(Global Transaction Identifiers) - 팔로워가 스냅숏 이후 데이터 변경의 미처리분(backlog)을 모두 처리
이때 팔로워는 리더를 따라잡았다고 한다.
노드 중단 처리
중단 시간 없이 개별 노드를 재시작할 수 있다는 점은 운영과 유지보수에 큰 장점이다.
리더 기반 복제에서 고가용성을 달성하는 방법에 대해서 알아보자.
팔로워 장애: 따라잡기 복구
- 리더로부터 받은 데이터 변경 로그를 로컬 디스크에 보관
- 장애 발생 후, 로컬에 보관된 로그에서 장애 전에 처리한 마지막 트랜잭션을 확인(binlog coordinate, GTID 등)
- 마지막 트랜잭션 부터 변경분 요청
리더 장애: 장애 복구
장애 복구(failover) 과정
- 팔로워 중 하나를 새로운 리더로 승격
- 클라이언트는 새로운 리더로 쓰기 전송을 위해 재설정
- 다른 팔로워는 새로운 리더로부터 데이터 변경을 요청 시작
장애 복구는 수동 또는 자동으로 진행한다.
자동 장애 복구 과정
- 리더가 장애인지 판단
무엇이 잘못됐는지 발견할 수 있는 확실한 방법이 없음
그래서 대부분 타임아웃으로 단순하게 확인 - 새로운 리더를 선택
선출 과정 또는 제어 노드(controller node)가 새로운 리더 임명
새로운 리더로 가장 적합한 후보는 보통 최신 데이터 변경사항을 가진 복제 서버 - 새로운 리더 사용을 위한 시스템을 재설정
이런 장애 복구 과정은 문제가 발생할 것 투성이다.
- 비동기식 복제의 경우 새로운 리더는 이전 리더의 쓰기를 일부 수신하지 못할 수 있다.
이런 경우 가장 일반적인 해결책은 복제되지 않은 쓰기를 단순히 폐기하는 방법
이 방법은 내구성에 대한 클라이언트의 기대를 저버리게 된다. - 쓰기를 폐기하는 방법은 데이터베이스 외부의 다른 저장소 시스템이 데이터베이스 내용에 맞춰 조정돼야 한다면 특히 위험(깃허브 사례, 책 확인)
- 특정 결함 시나리오에서 신규 리더와 이전 리더 모두 자신이 리더라고 믿을 수 있다.
이를 스플릿 브레인(split brain)이라 한다. - 적절한 타임아웃의 애매함
이런 문제에 대한 쉬운 해결책은 없다.
그래서 보통 운영팀은 수동으로 장애 복구를 수행하는 방식을 선호한다.
노드 장애, 불안정한 네트워크, 복제 서버 일관성과 관련된 트레이드오프, 지속성, 가용성, 지연시간 등의 문제는 분산 시스템에서 발생하는 근본적인 문제이다.
리더 기반 복제 로그 구현
구문 기반 복제
- 리더는 모든 쓰기 요청을 구문(statement)으로 기록
- 쓰기를 실행
- 구문 로그를 팔로워에게 전송
- 팔로워는 구문 로그를 파싱하고 실행
여기에서 구문은 모든 INSERT, UPDATE, DELETE 구문이다.
문제점
- 비결정적 함수를 포함한 모든 구문은 서버 마다 다른 값을 생성할 수 있다.
예: NOW(), RAND() 등 - 자동 증가 컬럼, 데이터베이스의 데이터에 의존하는 구문은 정확히 같은 순서로 실행해야 한다.
이는 동시에 여러 트랜잭션이 수행되는 것을 제한 - 부수효과를 가진 구문은 부수 효과가 완벽하게 결정적이지 않으면 다른 서버에서 다른 부수 효과가 발생할 수 있다.
예: 트리거, 스토어드 프로시저, 사용자 정의 함수
해당 문제들의 해결책으로 리더의 모든 비결정적 함수 호출을 고정 값을 반환하게끔 대체하는 것이다.
하지만 여기에도 문제가 있기 때문에 다른 복제 방법을 선호한다.
Write-ahead log(WAL) 기반 복제
팔로워가 이 로그를 처리하면 리더와 정확히 동일한 데이터 구조의 복제본을 만들 수 있다.
이 복제 방식은 포스트그레스큐엘과 오라클 등에서 사용된다.
단점
- 제일 저수준의 데이터를 기술
- 어떤 디스크 블록에서 어떤 바이트를 변경했는지 같은 상제 정보를 포함
- 저장소 엔진과 밀접하게 엮임
- 같은 저장소라도 버전에 영향을 받을 수도 있음
- 복제 프로토콜의 버전 불일치 허용 여부에 따른 저장소 업그레이드 시 중단 시간이 필요할 수 있음
논리적(로우 기반) 로그 복제
논리적 로그 : 복제 로그를 저장소 엔진의 데이터 표현과 다른 형식을 사용하는 것
- 삽입된 로우의 로그는 모든 칼럼의 새로운 값을 포함(insert)
- 삭제된 로우의 로그는 로우를 고유하게 식별하는 데 필요한 정보를 포함(delete)
보통 기본키이지만 없다면 모든 컬럼의 값을 로깅 - 갱신된 로우의 로그는 로우를 고유하게 식별하는 데 필요한 정보와 적어도 변경된 칼럼의 새로운 값을 포함(update)
- 여러 로우를 수정하는 트랜잭션은 여러 로그 레코드를 생성 후 트랜잭션이 커밋됐음을 레코드에 표시
장점
- 하위 호환성을 쉽게 유지
다른 버전의 데이터베이스 소프트웨어나 다른 저장소 엔진에서도 실행 가능 - 외부 애플리케이션이 파싱하기가 쉬움(예: CDC)
트리거 기반 복제
위의 세 가지 복제 방식은 데이터베이스 시스템에 의해 구현된다.
보다 더 유연성이 필요한 상황 에서 복제를 애플리케이션 층으로 옮겨야 한다.
예: 서브셋만 복제, 다른 종류의 데이터베이스로 복제, 충돌 해소 로직 등
보통 이 경우에 트리거나 스토어드 프로시저를 사용한다.
오라클의 경우 골든게이트 같은 도구를 제공한다.
트리거 기반 복제의 단점
- 다른 복제 방식보다 많은 오버헤드
- 내장된 복제보다 버그나 제한 사항이 많음
복제 지연 문제
지금까지 복제를 하는 이유는 노드 내결함성, 지연시간, 확장성
이 있었다.
읽기 확장(read-scaling) 아키텍처에서 간단히 팔로워를 더 추가해 읽기 전용 요청을 처리하기 위한 용량을 늘릴 수 있다.
하지만 이 방식은 실제로 비동기식 복제에서만 동작 한다.
비동기 팔로워에서 데이터를 읽을 때 지난 정보를 읽을 수도 있다.(팔로워가 뒤쳐진다면)
하지만 이런 불일치는 일시적인 상태이다.
복제 지연 시간 동안 기다리면 팔로워는 결국 리더와 일치하게 된다.
이런 효과를 최종적 일관성
이라고 한다.
하지만 애플리케이션에서 지연이 매우 크면 문제가 된다.
이제 부터 비동기식 복제에서 복제 지연이 있을 때 발생할 수 있는 세 가지 사례와 해결 방법에 대해서 살펴보자.
자신이 쓴 내용 읽기
그림과 같은 상황에서는 쓰기 후 읽기 일관성(자신의 쓰기 읽기 일관성)이 필요하다.
리더 기반 복제 시스템에서 쓰기 후 읽기 일관성에 대한 몇 가지 기법
- 사용자가 수정한 내용을 읽을 때는 리더에서 읽기
그 외 팔로워에서 읽기 - 여러 사용자가 수정을 하는 내용의 경우
리더에서 읽는 기준을 다른 기준을 사용
예 : 마지막 갱신 시간, 팔로워 복제 지연 시간 모니터링을 통한 일정 시간 지연된 팔로워에 대한 질의 제외 - 클라이언트 기준 쓰기 타임스탬프 이용
해당 타임스탬프 보다 최신의 복제서버에 질의 혹은 대기 - 복제 서버가 여러 데이터센터에 분산된 경우(복잡도 증가)
리더가 제공해야 하는 모든 요청은 리더가 포함된 데이터센터로 라우팅
동일한 사용자가 여러 디바이스로 서비스를 접근할 경우 또 다른 문제가 발생한다.
이 경우 디바이스 간(cross-device) 쓰기 후 읽기 일관성이 제공돼야 한다.
- 클라이언트 기준 쓰기 타임스탬프 방식은 더 어려움
해당 정보를 중앙집중식으로 관리 - 복제 서버가 여러 데이터센터 간에 분산된 경우
여러 디바이스 연결이 동일한 데이터센터로 라우팅된다는 보장이 없다.
리더에서 읽어야 할 필요가 있는 접근법이면 먼저 사용자 디바이스의 용청을 동일한 데이터센터로 라우팅해야 함
단조 읽기
해당 케이스는 사용자가 각기 다른 복제 서버에서 여러 읽기를 수행 때 발생할 수 있다.
단조 읽기(monotonic read)는 그림과 같은 상황이 발생하지 않음을 보장한다.
이는 강한 일관성보다는 덜한 보장이지만 최종적 일관성보다는 강한 보장이다.
한 가지 방법 : 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게끔 하는 것
예 : 사용자 ID의 해시를 기반으로 복제 서버를 선택
일관된 순서로 읽기
그림과 같은 현상은 파티셔닝된 데이터베이스에서 발생하는 특징적인 문제다.
이런 종류의 이상 현상을 방지하기 위해 일관된 순서로 읽기(Consistent Prefix Read) 같은 유형의 보장이 필요하다.
일관된 순서로 읽기는 일련의 쓰기가 특정 순서로 발생한다면 이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰여진 내용을 보게 됨을 보장한다.
한 가지 방법 : 서로 인과성이 있는 쓰기는 동일한 파티션에 기록
이런 방법은 일부 애플리케이션에서 효율적이지 않다.
인과성을 명시적으로 유지하기 위한 알고리즘 또한 있다.
복제 지연을 위한 해결책
최종적 일관성 시스템으로 작업할 때 복제 지연에 따른 애플리케이션이 어떻게 동작할지 생각해 볼 가치가 있다.
결과가 문제가 없다면 좋지만 쓰기 후 읽기와 같은 강한 보장을 제공하게끔 시스템을 설계해야 할 수도 있다.
해결 방안은 복제가 비동기식으로 동작하지만 동기식으로 동작하는 척 하는 것이다.
그래서 위에서 몇 가지 케이스와 해결책에 대한 대략적인 내용에 대해서 살펴 봤다.
애플리케이션이 기본 데이터베이스보다 더 강력한 보장을 제공하는 방법이 있다.
하지만 애플리케이션 코드가 너무 복잡해지고 잘못되기 쉽다.
그래서 트랜잭션이 필요하다.
오랫동안 단일 노드 트랜잭션이 존재 했지만 분산 데이터베이스로 전환하는 과정에서 많은 시스템이 트랜잭션을 포기했다.
성능과 가용성 측면에서 비용이 너무 비싸고 확장 가능한 시스템에서는 어쩔 수 없이 최종적 일관성을 사용해야 한다는 주장이 있다.
이 주장은 일부 사실이지만 지나치게 단순화됐다.
트랜잭션, 일관성과 합의 2개의 챕터와 파생 데이터 파트에서 여러 대안 메커니즘에 대해서 알아본다.