[Data-Intensive] Ch5. 복제 (Leader-based Replication)
by Jaesang Lim˚
복제
‘데이터 중심 애플리케이션 설계’를 읽고 정리하고자함
- 복제는 3가지 접근 방식으로 나눠서 정리할 예정
1. 도입 및 단일 리더 복제
- 다중 리더 복제
- 리더 없는 복제
- 이 글은 복제에 대한 전반적인 내용, 도입부와 단일 리더 복제 방법에 대해 다룰 것
복제 ( Replication )
복제 란?
- 네트워크로 연결된 여러 장비에 동일한 데이터의 복사본을 유지하는 것을 의미
복제가 필요한 이유
- 지리적으로 사용자와 가까게 데이터를 유지해 지연시간을 줄임
- 시스템의 일부가 장애가 발생하더라도 지속적으로 동작할 수 있게 가용성을 높임
- 읽기 질의를 제공하는 장비의 수를 확장해 처리량을 늘림
- 즉, latency, availability, throughput 측면에서 도움을 준다고 생각할 수 있음
복제의 어려움
- 복제중인 데이터가 시간이 지나도 변경되지 않으면 복제는 간단함
- 하지만, 복제된 데이터에 대한 변경처리해야함
리더 기반 복제 (Leader-based replication)
- 복사본을 저장하는 각 노드를 복제 서버(replica) 라고함
- 복제 서버 중 하나를 리더, 마스터, 프라이머리(leader, master, primary)라고함
리더 기반 복제 과정
- 클라이언트가 데이터베이스에 쓸 때, 요청은 리더에게 보내야함
- 요청은 받은 리더는 먼저 로컬 저장소에 새로운 데이터를 기록
- 리더가 로컬 저장소에 새로운 데이터를 기록할 때마다, 데이터 변경을 복제로그(replication log)나 변경 스트림(change stream)의 일부로 팔로워에게 전송
- 각 팔로워는 리더가 처리한 것과 동일한 순서로 모든 쓰기를 적용해, 데이터베이스의 로컬 복사본을 갱신
쓰기/읽기 요청
- 클라이언트의 읽기 요청은 리더 또는 임의의 팔로워에게 질의할 수 있음
- 쓰기는 리더에게만 허용함
동기식 대 비동기식 복제
동기식 복제
- 장점
- 팔로워가 리더와 일관성 있게 최신 데이터 복제본을 가지는 것을 보장함
- 단점
- 팔로워가 응답하지 않으면 쓰기요청이 처리될 수 없음
- 리더는 모든 쓰기 요청에 대해 block하고, 동기 복제 서버가 다시 사용할 수 있을 때까지 대기해야함
- 즉 모든 팔로워가 동기식 상황이라면, 임의의 한 노드의 장애는 전체 시스템을 멈추게함
- 그래서 일반적으로 반동기식(semi-synchronous)을 사용
- 팔로워 하나는 동기식, 나머지는 비동기식으로 처리하는 방법
- 그래서 일반적으로 반동기식(semi-synchronous)을 사용
새로운 팔로워 설정
- 복제 서버 수를 늘리거나, 장애 노드의 대체를 위해 새로운 팔로워을 설정해야함
- 그렇다면, 새로운 팔로워가 리더의 복제본을 정확히 가지고 있는지 어떻게 보장할까?
- 전체 데이터베이스를 잠그지 않고, 리더의 데이터베이스 snapshot을 가져옴
- snapshot을 새로운 노드에 복사함
- 팔로워는 리더에 연결해, snapshot 이후 발생한 모든 데이터 변경을 요청
- 팔로워가 snapshot 이후 데이터 변경의 미처리분(backlog)를 다 처리하면 따라잡았다고 함
노드 중단 처리 (= 고가용성을 달성하는 방법)
팔로워 장애 : 따라잡기 복구
- 각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관함
- 보관된 로그에서 결함이 발생하기 전에 처리한 마지막 트랜잭션을 알아냄
- 팔로워는 리더에 연결해, 끊어진 동안 발생한 데이터 변경을 모두 요청
리더 장애 : 장애 복구
장애복구(failover) 과정
- 팔로워 중 하나를 새로운 리더로 승격해야함
- 클라이언트는 새로운 리더로 쓰기요청을 할 수 있게 재설정도 해야함
- 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비하기 시작해야함
자동 장애복구 과정
- 리더가 장애인지 판단
- timeout
- 새로운 리더 선책
- 선출과정(리더가 나머지 복제 서버의 대다수에 의해 선택)또는 제어 노드(controller node)에 의해 리더를 임명
- 이전 리더의 최신 데이터 변경사항을 가진 복제서버가 일반적인 새로운 리더의 후보
- 새로운 리더 사용을 위해 시스템 재설정
장애복구 과정의 문제가 될만한 사항들
- 데이터 유실
- 비동기식 복제를 사용한다면, 새로운 리더는 이전 리더가 실패하기 전의 쓰기 요청의 일부를 수신 못 할수있음
- 새로운 리더가 선출되고, 이전 리더가 다시 클러스터에 추가된다면?
- 이전 리더의 복제되지 않은 쓰기를 단순히 폐기하는 방법이 있으나, 이는 내구성(Durability)의 문제를 야기함
- 쓰기를 폐기하면, 데이터베이스 외부의 다른 저장소 시스템이 데이터베이스 내용에 맞춰 조정돼야한다면 위험함
- 실제 github에서 발생한 사고로, 일부 개인 데이터가 잘못된 사용자에게 공개되었음
- 유효하지 않은(out-of-date) mysql 팔로워가 리더로 승격되었음
- primary key를 자동증가카운터로 사용했지만, 새로운 리더의 카운터는 이전 리더보다 뒤쳐져 있어, 이전 리더가 예전에 할당한 primary key를 재사용함
- 이 primary key는 redis 저장에도 사용해, mysql과 redis간 불일치 발생
- 스플릿 브레인(split brain)
- 두 노드가 모두 자신이 리더라고 생각하는 상황
- 두 리더가 쓰기를 받으면서 충돌 해소하는 과정을 거치지 않으면, 데이터 유실 또는 데이터가 오염될 수 있음
- 일부 시스템은 두 리더가 감지되면 한 노드를 죽이는 매커니즘이 있음
- 이를 fencing, 노드를 확실하게 죽이기(Shoot The Other Node In The Head,STONITH)라고도 함
- 하둡 NN에 fencing 방법을 사용함
- timeout 선정의 어려움
복제 로그 구현
- 리더 기반 복제는 내부적으로 어떻게 동작하는가
구문 기반 복제 (statement-based replication)
- 리더는 모든 쓰기 요청을 기록하고 쓰기 실행한 뒤, 구문 로그를 팔로워에게 전송
- 관계형 데이터베이스는 insert, update, delete 구문을 팔로워에 전달함
- 각 팔로워들은 마치 클라이언트에게 받은 것 처럼 SQL구문을 파싱하고 처리함
복제가 깨질 수 있는 상황
- NOW()나 RAND() 같은 비결정적 함수 호출
- 자동증가 칼럼을 사용하는 구문
- 데이터베이스에 있는 데이터에 의존하는 경우( e.g) update .. where
) - 2,3번의 경우 복제 서버에서 클라이언트 요청과 정확히 같은 순서로 실행되어야함
- 부수 효과를 가진 구문(e,g 트리거, 스토어드 프로시저, 사용자 정의 함수)의 다른 부수 효과 발생 우려
해결법
- 리더는 구문을 기록할 때, 모든 비결졍적 함수 호출을 고정값으로 변환
mysql 5.1 버전 이전에는 구문 기반 복제(statement-based replication) 사용
- 하지만 구문 중 비결정성이 있다면 로우 기반 복제(row-based replication)으로 변경
쓰기 전 로그 배송 (WAL)
- 데이터베이스의 모든 쓰기를 포함하는 append-only 바이트열인 로그를 사용해
- 리더는 디스크에 기록하고, 또 팔로워에게 네트워크로 로그를 전송
- postgrel, oracle에서 사용 단점
- 로그가 제일 저수준 데이터를 기술한다는 점
- WAL은 어떤 디스트 블록에서 어떤 바이트를 변경했는지와 같은 상세 정보를 포함함
- 그래서 데이터베이스가 저장소 형식을 다른 버전으로 변경하면, 리더와 팔로워의 데이터베이스 소프트웨어 버전을 다르게 실행할 수 없음
- 복제 로그를 저장소 엔진가 매우 밀접함
로우 기반(논리적) 로그 복제 (row-based replication)
- 복제 로그를 저장소 엔진 내부와 분리하는 대안
- 복제와 저장소 엔진을 위해 다른 로그 형식을 사용하는 방법
- 이 종류의 복제 로그를 논리적 로그(logical log)라 하고, 저장소 엔진의 물리적 데이터표현과 구별하고자 함
- 논리적 로그는 로우 단위로 데이터베이스 테이블에 쓰기를 기술하는 레코드 열
- 삽입된 로우의 모든 칼럼의 새로운 값을 포함
- 삭제된 로우이 로그는 로우를 고유하게 식별하는 데 필요한 정보만 포함
- 대기 primary key
- 갱신된 로우의 로그를 고유하게 식별하는데 필요한 정보와 모든 칼럼의 새로운값을 포함
트리거 기반 복제
- 위의 방법은 애플리케이션 코드없이 데이터베이스의 시스템에 의해 구현
- 유연성을 위해 데이터의 서브셋만 복제하거나 데이터베이스를 다른 종류의 데이터베이스로 복제해야하거나 충돌 해소 로직이 필요하면 복제를 애플리케이션으로 넘겨애함
복제 지연 문제
자신이 쓴 내용 읽기
- 자신이 입력한 결과를 확인할 때, 복제 지연으로 값이 나오지 않을 경우
- 쓰기 후 읽기의 일관성을 보장해야함
read-after-write 일관성 구현하는 방법
- 사용자가 수정한 내용을 읽을 때는 리더에서 읽음
- 그 밖에는 팔로워에서 읽음
- 실제로 질의하지 않고 무엇이 수정되었는지 알 수 있는 방법이 필요함
- 소셜 네트워크 프로필은 자신만 수정할 수 있으므로, 항상 사용자 소유의 프로필은 리더에서 읽고,
- 다른 사용자의 프로필은 팔로워에서 읽는 규칙을 만듬
- 갱신 시각으로 마지막 갱신 후 1분 동안은 리더에서 모든 읽기 수행
- 팔로워에서 복제 지연을 모니터링해 리더보다 1분 이상 늦은 모든 팔로워는 질의를 금지할 수 있음
- 클라이언트는 가장 최근 쓰기의 타임스팸프를 기억함
- 시스템은 사용자 읽기를 위한 복제 서버가 최소한 해당 타임스팸프까지 갱신을 반영하게 할 수 있음
단조 읽기
- 각 사용자의 일긱가 항상 동일한 복제서버에서 수행되게끔 함
- 임의 선택이 아닌 ID 해시값을 이용해 복제서버를 선택,
- 해당 복제서버가 문제가 된다면 사용자 질의를 다른 복제서러로 재라우팅해야함
일관된 순서로 읽기
- 인과성의 위반 우려
-
질문하고 답해야하는데, 답을 하고 질문을 하는 초능력같은 상황 발생
- 일관된 순서로 읽기 (Consistent Prefix Read)로 방지할 수 있음
- 일련의 쓰기가 특성 순서로 발생하면 이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰여진 내용을 보게끔 보장
- 파티셔닝된 데이터베이스에서 발생하는 문제
- 분산 데이터베이스에서 서로 다른 파티션은 독립적으로 동작하므로 쓰기의 전역 순서는 없음
- 즉, 사용자가 데이터베이스를 읽을 때, 예전 상태의 일부와 새로운 상태의 일부르 함께 볼 수 있음
해결 책
- 서로 인과성이 있는 쓰기는 동일한 파티션에 기록하게 함
복제 지연의 해결책
트랙잭션이 있는 이유
- 개발자는 이런 미묘한 복제 문제를 걱정하지 않고, 올바른 작업 수행을 위해 항상 데이터베이스르 신뢰할 수 있어야함
- 트랜잭션은 애플리케이션을 더 단순해지기 위해 데이터베이스가 더 강력한 보장을 제공하는 방법 중 하나
Subscribe via RSS