[database] 복제(1/3) - 단일리더 복제

데이터 중심 애플리케이션 설계를 읽고

[database] 복제(1/3) - 단일리더 복제

복제

일반적으로 AP서버의 부하는 scale out을 통해서 해결한다. 이후 로드밸런서가 각요청을 로드밸런싱 알고리즘에 맞게 요청을 분산 시키면 해결된다. 하지만 문제는 DB의 복제이다. DB를 다중화 한다면 데이터간의 일관성을 보장하기 위해 고려해야할 사항들이 많다. DB복제에 쓰이는 시스템 아키텍처에 대해서 알아볼 것이다. 단일리더, 다중리더, 리더없는 복제 방식을 알아볼 것이고 이번 글에서는 단일 리더 복제를 알아볼 것이다.

복제가 필요한 이유

  • 지리적으로 사용자와 가깝게 데이터를 유지해 지연 시간을 줄이기 위해

  • 시스템의 일부에 장애가 발생해도 지속적으로 동작할 수 있게 하기 위해

  • 읽기 질의를 제공하는 장비의 수를 확장해 처리량을 늘리기 위해


복제의 어려움과 알고리즘

데이터가 시간이 지나도 변경되지 않는다면 복제는 쉽다. but 복제에서 모든 어려움은 복제된 데이터의 변경 처리에 있다.

💡
🔔 노드간 변경을 복제하기 위한 세 가지 알고리즘
  1. 단일 리더(single-leader)

  2. 다중 리더(multi-leader)

  3. 리더없는 복제(leaderless)


알고리즘 1️⃣ : 리더와 팔로워

💡
데이터 베이스의 복사본을 저장하는 각 노드 : 복제서버(replica)
  • 데이터베이스의 모든 쓰기는 모든 복제 서버에서 처리돼야 동일한 데이터가 유지된다.

  • 이 문제를 해결하는 가장 일반적인 해결책은 리더 기반 복제 ( 능동-수동 , 마스터-슬레이브 복제라고도 함)

  • 복제 서버중 하나를 리더(마스터나 프라이머리 라고도 함)로 지정한다. 다른 복제 서버는 팔로워(슬레이브, 읽기 복제서버, 2차, 핫 대기 라고도 함)가 된다.

  • 쓰기요청은 리더에서만 이루어진다. 리더가 새로운 데이터를 기록할 때마다 데이터 변경을 복제로그나 변경 스트림의 일부로 팔로워에게 전송한다. 팔로워는 그에 맞게 갱신

  • 읽기 요청은 임의의 팔로워에게 할 수 있다.

용어의미같은 뜻 용어
복제서버DB의 복사본을 저장하는 노드replica
리더기반 복제복제 서버중 하나가 리더가 되어서 동일 데이터 유지active-passive , master-slave
팔로워리더가 아닌 복제서버slave, read replica, secondary, hot stand by
리더복제 서버 중 쓰기 요청이 이루어지는 곳master, primary

리더 기반 복제는 여러 RDBMS에 내장되었고, MongoDB, 에스프레소를 포함한 일부 비관계형 데이터 베이스에서도 사용한다. 또한 데이터베이스에만 국한되지 않고 카프카래빗MQ같은 분산 메시지 브로커에도 사용된다.

동기식 vs 비동기식 복제

  • 복제 시스템의 중요한 세부 사항은 복제가 동기적인지 비동기적인지 여부이다.

  • 모든 팔로워가 동기적인 상황은 비현실 적이다.

그림에서 Follower1은 동기식으로, Follower2는 비동기식으로 복제를 한다. 동기식 복제는 처리하는데 상당한 지연이 있음을 알 수 있다.

  • 현실적으로 동기식 복제는 보통 팔로워 하나만 하고 그 밖의 복제서버는 비동기식으로 복제한다. 이는 적어도 두 노드(리더, 동기식으로 복제한 팔로워)에는 최신 복사본이 있는 것을 보장한다. 이런 설정을 반동기식(semi-synchronous)이라 한다.

  • 비동기식 복제는 내구성을 약화시키기 때문에 나쁜 방법 같지만 팔로워가 많거나 지리적으로 분산되었다면 널리 사용한다. 하지만 복제 지연 문제를 야기한다. (후에 설명)

새로운 팔로워 설정

새로운 팔로워를 설정할 때 어떻게 정확히 리더랑 똑같은 데이터를 가지고 있다고 보장할까?

데이터베이스는 항상 유동적이라 데이터를 복사하고 옮기는 사이에 최신본이 아니게 될 수 있다. DB를 잠가서 일관성을 유지할 수 있지만 고가용성 목표에 부합하지 않는다.

이럴 때 DB의 스냅샷을 이용한다.

  1. 리더의 데이터베이스에서 일정 시점의 스냅샷을 가져온다. (대부분 데이터베이스는 백업이 필요해서 이 기능을 갖춤)

  2. 스냅샷을 새로운 팔로워 노드에 복사

  3. 팔로워는 리더에 연결해서 스냅샷 이후에 발생한 모든 데이터 변경을 요청.

    1. 이는 스냅샷이 리더의 복제 로그의 정확한 위치를 알아야 한다. 이 위치 명칭은 MySQL에서는 이진로그좌표(binlog coordinate)라고 하고 PostgreQL에서는 로그 일련번호(log sequence)라고함
  4. 팔로워가 스냅샷 이후의 데이터변경을 모두 반영한다.

노드 중단 처리

장애로 인해 예기치 않게 중단되거나 유지보수를 위해 중단될 수 있다. 이 때 전체 시스템이 중단되지 않고 개별노드만 중단시켜야 서비스에 영향을 미치지 않는다. 어캄?

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

중단되기 전 마지막 트랜잭션을 알아낸다. 이후의 데이터 변경을 모두 리더에게 요청한다. → 해결

리더장애 : 장애복구

리더가 장애인지 어떻게 판단할까? 대부분의 시스템은 단순히 타임아웃을 사용한다.

  1. 팔로워 중 하나를 새로운 리더로 승격하고

  2. 클라이언트는 새로운 리더로 쓰기를 전송하기 위해서는 재설정이 필요

  3. 다른 팔로워는 새로운 리더로부터 데이터를 받아와야 한다.

이 과정을 장애복구(fail over) 라 한다.

잘못될 수 있는 것 투성이인 장애복구

  • 비동기식 복제를 사용한다면 새로운 리더는 이전 리더가 실패하기 전 리더의 쓰기 일부를 수신하지 못할 수 있다. 새로운 리더가 선출되고 다시 이전리더가 복귀하면 PK가 충돌한다. 가장 일반적인 해결책은 이전 리더의 복제되지 않은 쓰기를 단순히 폐기하는 방법

  • 특정 결함 시나리오에서 두 노드가 모두 자신이 리더라고 믿을 수도 있다. 이런 상황을 스플릿 브레인 이라 한다.

  • 리더가 분명히 죽었다고 판단 가능한 적절한 타임아웃은 얼마일까? 일시적인 부하 급증으로 응답시간이 느린 것을 장애로 판단해버릴 수도 있다.

복제 로그 구현

리더 기반 복제의 동작을 알아본다.

구문 기반 복제

리더는 모든 쓰기 쿼리문을 기록하고 쓰기를 실행한 다음 팔로워에게도 똑같은 쿼리를 날린다. 합리적일 것 같지만 복제가 깨질수 있는 다양한 사례가 존재

  • 현재 날짜를 얻기 위한 now()나 임의의 숫자를 얻기 위한 rand() 같은 함수를 호출하는것은 각 팔로워마다 다른 값을 생성할 가능성이 있다

  • 부수효과(트리거, stored procedure, 사용자 정의 함수 등)은 부수효과가 완벽하게 결정적이지 않으면 각 복제 서버에서 다른 부수효과가 발생할 수 있다.

  • 문제점의 대안은 리더는 구문을 기록할 때, 모든 비결정적 함수 호출을 고정 값을 반환하게끔 한다.

쓰기 전 로그 배송

완전히 동일한 로그를 사용해 다른 노드에서 복제 서버를 구축할 수 있다.

  • 리더는 디스크에 로그를 기록하고, 팔로워에게 네트워크로 로그를 전송한다.

  • 팔로워가 전송받은 로그를 처리하면 리더와 동일한 데이터 구조의 복제본이 만들어짐.

주의점

  • 로그가 제일 저수준의 데이터를 기술하고 있으므로,

    • 저장소 엔진 의존성이 생긴다.

      • 리더와 팔로워의 DB 버전을 다르게 하면 안됨.
    • 버전 업데이트시, 팔로워를 먼저 업데이트하고, 리더를 나중에 업데이트.

논리적(로우 기반)로그 복제

복제 로그를 저장소 엔진과 분리하기 위한 대안비 의존적인 로그 형식(logical log, 논리적 로그)을 사용한다.

  • 삽입된 로우의 로그는 모든 칼럼의 새로운 값을 포함한다.

  • 삭제된 로우의 로그는 로우를 고유하게 식별하는 데 필요한 정보를 포함한다.

  • 갱신된 로우의 로그는 로우를 고유하게 식별하는 데 필요한 정보와 모든 칼럼의 새로운 값을 포함한다.

트리거 기반 복제

  • 트리거는 애플리케이션 코드를 등록할 수 있게 한다.

    • 데이터베이스 시스템에서 데이터가 변경되면(쓰기 트랜잭션) 자동으로 실행된다.
  • 트리거 기반 복제는 다른 복제 방식보다 많은 오버헤드가 있다.

복제 지연 문제

팔로워가 많아질수록 동기식 복제는 불가능에 가까워지니 비동기식 복제가 대부분.

💡
복제지연문제 비동기 팔로워에게서 데이터를 읽을 때 팔로워가 뒤쳐진다면 지난 정보를 볼 수도 있게되는 문제.

실제로는 눈에 띄지 않는 아주 짧은 순간이지만 네트워크 문제가 생기면 수초에서 수분으로 지연시간이 길어진다.

자신이 쓴 내용 읽기

  • 사용자가 수정한 내용을 읽을 때에는 리더에서 읽는다. 그 밖에는 팔로워에서 읽는다. 이를 위해서는 무엇이 수정됐는지 알 수 있는 방법이 필요함.

    • 예를들어 sns의 프로필을 읽을 때 사용자 소유의 프로필은 리더에서 읽고 다른 사용자의 프로필은 팔로워에서 읽는 규칙
  • 마지막 갱신 시간을 찾아서 마지막 갱신 후 일정시간동안은 리더에서 모든 읽기를 수행하는 방법

단조(mono)읽기

두 개의 팔로워 중에서 팔로워1은 지연이 없고 팔로워2는 지연이 크다고 했을 때, 사용자 2345가 같은 데이터를 두 번 요청할 때 있었던 데이터가 사라지는, 즉 시간이 거꾸로 흐르는 현상을 목격할 수 있다.

단조 읽기는 이를 해결해 주는데, 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게끔 하는 것이다.

(하지만 복제 서버가 고장나면 …)

일관된 순서로 읽기

부 파티션이 다른 것보다 느리게 복제되는 경우 관찰자는 질문을 보기 전에 대답을 볼 수 있다.

해결할 방법은 서로 인과성이 있는 쓰기가 동일한 파티션에 기록되게끔 하는 방법으로 해결