복제

‘데이터 중심 애플리케이션 설계’를 읽고 정리하고자함

  • 복제는 3가지 접근 방식으로 나눠서 정리할 예정 1. 도입 및 단일 리더 복제
    1. 다중 리더 복제
    2. 리더 없는 복제
  • 이 글은 복제에 대한 전반적인 내용, 도입부단일 리더 복제 방법에 대해 다룰 것

복제 ( Replication )

복제 란?

  • 네트워크로 연결된 여러 장비에 동일한 데이터의 복사본을 유지하는 것을 의미

복제가 필요한 이유

  1. 지리적으로 사용자와 가까게 데이터를 유지해 지연시간을 줄임
  2. 시스템의 일부가 장애가 발생하더라도 지속적으로 동작할 수 있게 가용성을 높임
  3. 읽기 질의를 제공하는 장비의 수를 확장해 처리량을 늘림
    • 즉, latency, availability, throughput 측면에서 도움을 준다고 생각할 수 있음

복제의 어려움

  • 복제중인 데이터가 시간이 지나도 변경되지 않으면 복제는 간단함
  • 하지만, 복제된 데이터에 대한 변경처리해야함

리더 기반 복제 (Leader-based replication)

스크린샷 2019-06-01 오후 6 09 14

  • 복사본을 저장하는 각 노드를 복제 서버(replica) 라고함
  • 복제 서버 중 하나를 리더, 마스터, 프라이머리(leader, master, primary)라고함

리더 기반 복제 과정

  1. 클라이언트가 데이터베이스에 쓸 때, 요청은 리더에게 보내야함
  2. 요청은 받은 리더는 먼저 로컬 저장소에 새로운 데이터를 기록
  3. 리더가 로컬 저장소에 새로운 데이터를 기록할 때마다, 데이터 변경을 복제로그(replication log)나 변경 스트림(change stream)의 일부로 팔로워에게 전송
  4. 각 팔로워는 리더가 처리한 것과 동일한 순서로 모든 쓰기를 적용해, 데이터베이스의 로컬 복사본을 갱신

쓰기/읽기 요청

  • 클라이언트의 읽기 요청은 리더 또는 임의의 팔로워에게 질의할 수 있음
  • 쓰기는 리더에게만 허용함

동기식 대 비동기식 복제

스크린샷 2019-06-01 오후 6 19 10

동기식 복제

  • 장점
    • 팔로워가 리더와 일관성 있게 최신 데이터 복제본을 가지는 것을 보장함
  • 단점
    • 팔로워가 응답하지 않으면 쓰기요청이 처리될 수 없음
    • 리더는 모든 쓰기 요청에 대해 block하고, 동기 복제 서버가 다시 사용할 수 있을 때까지 대기해야함
  • 즉 모든 팔로워가 동기식 상황이라면, 임의의 한 노드의 장애는 전체 시스템을 멈추게함
    • 그래서 일반적으로 반동기식(semi-synchronous)을 사용
      • 팔로워 하나는 동기식, 나머지는 비동기식으로 처리하는 방법

새로운 팔로워 설정

  • 복제 서버 수를 늘리거나, 장애 노드의 대체를 위해 새로운 팔로워을 설정해야함
  • 그렇다면, 새로운 팔로워가 리더의 복제본을 정확히 가지고 있는지 어떻게 보장할까?
  1. 전체 데이터베이스를 잠그지 않고, 리더의 데이터베이스 snapshot을 가져옴
  2. snapshot을 새로운 노드에 복사함
  3. 팔로워는 리더에 연결해, snapshot 이후 발생한 모든 데이터 변경을 요청
  4. 팔로워가 snapshot 이후 데이터 변경의 미처리분(backlog)를 다 처리하면 따라잡았다고 함

노드 중단 처리 (= 고가용성을 달성하는 방법)

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

  1. 각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관함
  2. 보관된 로그에서 결함이 발생하기 전에 처리한 마지막 트랜잭션을 알아냄
  3. 팔로워는 리더에 연결해, 끊어진 동안 발생한 데이터 변경을 모두 요청

리더 장애 : 장애 복구

장애복구(failover) 과정

  1. 팔로워 중 하나를 새로운 리더로 승격해야함
  2. 클라이언트는 새로운 리더로 쓰기요청을 할 수 있게 재설정도 해야함
  3. 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비하기 시작해야함

자동 장애복구 과정

  1. 리더가 장애인지 판단
    • timeout
  2. 새로운 리더 선책
    • 선출과정(리더가 나머지 복제 서버의 대다수에 의해 선택)또는 제어 노드(controller node)에 의해 리더를 임명
    • 이전 리더의 최신 데이터 변경사항을 가진 복제서버가 일반적인 새로운 리더의 후보
  3. 새로운 리더 사용을 위해 시스템 재설정

장애복구 과정의 문제가 될만한 사항들

  1. 데이터 유실
    • 비동기식 복제를 사용한다면, 새로운 리더는 이전 리더가 실패하기 전의 쓰기 요청의 일부를 수신 못 할수있음
    • 새로운 리더가 선출되고, 이전 리더가 다시 클러스터에 추가된다면?
    • 이전 리더의 복제되지 않은 쓰기를 단순히 폐기하는 방법이 있으나, 이는 내구성(Durability)의 문제를 야기함
  2. 쓰기를 폐기하면, 데이터베이스 외부의 다른 저장소 시스템이 데이터베이스 내용에 맞춰 조정돼야한다면 위험함
    • 실제 github에서 발생한 사고로, 일부 개인 데이터가 잘못된 사용자에게 공개되었음
    • 유효하지 않은(out-of-date) mysql 팔로워가 리더로 승격되었음
    • primary key를 자동증가카운터로 사용했지만, 새로운 리더의 카운터는 이전 리더보다 뒤쳐져 있어, 이전 리더가 예전에 할당한 primary key를 재사용함
    • 이 primary key는 redis 저장에도 사용해, mysql과 redis간 불일치 발생
  3. 스플릿 브레인(split brain)
    • 두 노드가 모두 자신이 리더라고 생각하는 상황
    • 두 리더가 쓰기를 받으면서 충돌 해소하는 과정을 거치지 않으면, 데이터 유실 또는 데이터가 오염될 수 있음
    • 일부 시스템은 두 리더가 감지되면 한 노드를 죽이는 매커니즘이 있음
      • 이를 fencing, 노드를 확실하게 죽이기(Shoot The Other Node In The Head,STONITH)라고도 함
      • 하둡 NN에 fencing 방법을 사용함
  4. timeout 선정의 어려움

복제 로그 구현

  • 리더 기반 복제는 내부적으로 어떻게 동작하는가

구문 기반 복제 (statement-based replication)

  • 리더는 모든 쓰기 요청을 기록하고 쓰기 실행한 뒤, 구문 로그를 팔로워에게 전송
    • 관계형 데이터베이스는 insert, update, delete 구문을 팔로워에 전달함
    • 각 팔로워들은 마치 클라이언트에게 받은 것 처럼 SQL구문을 파싱하고 처리함

복제가 깨질 수 있는 상황

  1. NOW()나 RAND() 같은 비결정적 함수 호출
  2. 자동증가 칼럼을 사용하는 구문
  3. 데이터베이스에 있는 데이터에 의존하는 경우( e.g) update .. where )
    • 2,3번의 경우 복제 서버에서 클라이언트 요청과 정확히 같은 순서로 실행되어야함
  4. 부수 효과를 가진 구문(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. 사용자가 수정한 내용을 읽을 때는 리더에서 읽음
    • 그 밖에는 팔로워에서 읽음
    • 실제로 질의하지 않고 무엇이 수정되었는지 알 수 있는 방법이 필요함
    • 소셜 네트워크 프로필은 자신만 수정할 수 있으므로, 항상 사용자 소유의 프로필은 리더에서 읽고,
    • 다른 사용자의 프로필은 팔로워에서 읽는 규칙을 만듬
  2. 갱신 시각으로 마지막 갱신 후 1분 동안은 리더에서 모든 읽기 수행
    • 팔로워에서 복제 지연을 모니터링해 리더보다 1분 이상 늦은 모든 팔로워는 질의를 금지할 수 있음
  3. 클라이언트는 가장 최근 쓰기의 타임스팸프를 기억함
    • 시스템은 사용자 읽기를 위한 복제 서버가 최소한 해당 타임스팸프까지 갱신을 반영하게 할 수 있음

단조 읽기

스크린샷 2019-06-01 오후 7 05 30

  • 각 사용자의 일긱가 항상 동일한 복제서버에서 수행되게끔 함
    • 임의 선택이 아닌 ID 해시값을 이용해 복제서버를 선택,
    • 해당 복제서버가 문제가 된다면 사용자 질의를 다른 복제서러로 재라우팅해야함

일관된 순서로 읽기

스크린샷 2019-06-01 오후 7 05 44

  • 인과성의 위반 우려
  • 질문하고 답해야하는데, 답을 하고 질문을 하는 초능력같은 상황 발생

  • 일관된 순서로 읽기 (Consistent Prefix Read)로 방지할 수 있음
    • 일련의 쓰기가 특성 순서로 발생하면 이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰여진 내용을 보게끔 보장
  • 파티셔닝된 데이터베이스에서 발생하는 문제
    • 분산 데이터베이스에서 서로 다른 파티션은 독립적으로 동작하므로 쓰기의 전역 순서는 없음
    • 즉, 사용자가 데이터베이스를 읽을 때, 예전 상태의 일부와 새로운 상태의 일부르 함께 볼 수 있음

해결 책

  • 서로 인과성이 있는 쓰기는 동일한 파티션에 기록하게 함

복제 지연의 해결책

트랙잭션이 있는 이유

  • 개발자는 이런 미묘한 복제 문제를 걱정하지 않고, 올바른 작업 수행을 위해 항상 데이터베이스르 신뢰할 수 있어야함
  • 트랜잭션은 애플리케이션을 더 단순해지기 위해 데이터베이스가 더 강력한 보장을 제공하는 방법 중 하나