ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 5장 - 복제
    CS/데이터 중심 애플리케이션 설계 요약 2022. 12. 2. 00:01
    728x90

    분산된 데이터 베이스가 필요한 이유

    - 데이터 볼륨, 읽기 부하, 쓰기 부하가 단일 장비에서 다룰 수 있는 양보다 커지면 여러 장비로 부하를 분배하는 확장성을 갖출 수 있다

     

    - 장비 하나가 죽더라도 여러 장비를 사용하여 다른 하나가 그 기능을 이어 수행한다.

    즉, 내결함성을 갖출 수 있다

     

    - 글로벌 서비스라면 DB에 접근하기 위해 지구 반 바퀴를 돌 필요 없이 사용자와 가까운 곳에 데이터 센터를 설치하여 저지연성을 제공할 수 있다

     

    고부하를 지탱하는 데이터 베이스로 확장시키는 방법

    - 더 좋은 성능의 DB로 스케일업(가장 간단)

    하지만 시간이 지날수록 2배의 장비를 투입해도 2배의 부하를 추가적으로 처리할 수 있지 않으며 지수함수 같은 비용 곡선이 발생합니다.

     

    - 비공유 아키텍처(수평 확장)

    데이터베이스 소프트웨어를 수행하는 각 장비를 노드라 칭합니다.

    여러 지리적인 영역에 걸쳐 데이터를 분산해 사용자 지연 시간을 줄이고 전체 데이터센터의 손실을 줄일 수 있습니다.

    클라우드 배포를 활용하면 소규모 회사라도 다중 지역 분산 아키텍처가 실현 가능합니다.

     

    하지만 데이터를 여러 노드에 분산하려면 분산 시스템에서 발생하는 제약 조건과 트레이드오프를 알고 있어야 합니다.

    대게 애플리케이션 복잡도를 야기하고 때로는 사용할 수 있는 데이터 모델의 표현을 제한합니다.

     

    이 책에서는 비공유 아키텍처에 중점을 맞추어 다양한 문제를 자세히 알아봅니다.

     

    여러 노드에 데이터를 분산하는 방법

    - 복제

    같은 데이터 복제본을 다른 위치에 있는 여러 노드에 유지

    일부 노드가 사용 불가능 해도 다른 노드로 기능을 제공 및 성능 향상

     

    - 파티셔닝

    큰 데이터베이스를 파티션이라는 작은 서브셋으로 나누고 각 파티션을 다른 노드에 할당

    샤딩의 개념(데이터베이스의 수평적 분할 또는 수직적 분할)

     


    복제

    복제란 네트워크로 연결된 여러 장비에 동일한 데이터의 복사본을 유지한다는 의미입니다.

     

    데이터의 복제가 필요한 이유는?

    - 지리적으로 사용자가 가깝게 데이터를 유지해 지연 시간을 줄임

    - 시스템의 일부에 장애가 발생해도 지속적으로 동작할 수 있도록 가용성 높임

    - 읽기 질의를 제공하는 장비의 수를 확장하여 읽기 처리량 늘림(읽기- 쓰기 DB 분리)

     

    이번장에서는 데이터셋의 크기가 작아 각 장비에 전체 데이터셋의 복사본을 보유할 수 있다고 가정합니다.

     

    복제 중인 데이터의 변경

    데이터가 시간이 지나도 변경되지 않는다면 복제는 쉽습니다.

    모든 어려움은 복제된 데이터의 변경 처리에서 발생합니다.

     

    노드 간 변경을 복제하기 위한 세 가지 인기 있는 알고리즘이 있습니다.

    - 단일 리더

    - 다중 리더

    - 리더 없음

     

    복제에서는 고려해야 할 많은 트레이드오프가 존재합니다.

    - 동기식 복제 vs 비동기식 복제

    - 잘못된 복제는 어떻게 처리할지

     

    리더와 팔로워

    리더 팔로와, 마스터 슬레이브라고 많이 부르며 replica(복제 서버)라는 각 노드에 데이터베이스의 복사본을 저장합니다.

    데이터베이스의 모든 쓰기는 모든 복제 서버에 처리되어야 합니다.

    그렇지 않으면 데이터의 동기화가 적절하게 이루어지지 않습니다.

     

    replica 중 하나를 리더, 마스터로 지정하며 모든 쓰기는 여기에서 발생합니다.

    다른 복제 서버들은 팔로워, 슬레이브라고 하며 리더가 데이터를 쓰게 되면 데이터 변경을 복제 로그나 변경 스트림의 일부로 팔로워에게 전송합니다.

     

    Mysql, PostgreSQL 등 다양한 RDB들이 이 기능을 제공하며 몽고 DB, 리싱크 DB, 에스프레소를 포함한 일부 NoSQL에서도 이 기능을 제공합니다.

    나아가 카프카, 레빗 MQ와 같은 고가용성 큐 같은 분산 메시지 브로커에도 사용됩니다.

     

    동기식 복제와 비동기식 복제

    RDB에서는 보통 설정 가능한 옵션으로 주어집니다.

     

    동기식을 사용하면 클라이언트는 갱신 요청을 리더에게 전송합니다.

    리더는 요청을 받고 데이터 변경을 팔로워에게 전달합니다.

    최종적으로 리더는 클라이언트에게 갱신이 성공했음을 알려줍니다.

     

    동기식의 장점은 팔로워가 리더와 일관성 있게 최신 데이터 복사본을 가지는 것을 보장합니다.

    갑자기 리더가 동작하지 않아도 데이터는 팔로워에서 계속 사용할 수 있습니다.

     

    하지만 이때 팔로워 중 하나가 메시지를 처리하기 전까지 상당한 지연이 있는 경우 또는 팔로워가 장애를 복구 중이거나 노드 간 네트워크 문제가 발생하는 경우가 발생할 수 있습니다.

     

    이런 경우 모든 쓰기가 차단되고 동기 복제 서버가 다시 사용할 수 있을 때까지 기다려야 합니다.

    이런 이유로 모든 팔로워가 동기식인 상황은 비현실적입니다.

    임의 한 노드의 장애는 전체 시스템을 멈추게 합니다.

     

    따라서 현실적으로 DB에서 동기식 복제를 사용하려면 보통 팔로워 하나는 동기식으로 하고 그밖에는 비 동식으로 처리합니다.

    동기식 팔로워가 사용할 수 없게 되거나 느려지면 비동기식 팔로워 중 하나가 동기식이 됩니다.

     

    이것은 적어도 두 노드에 데이터의 최신 복사본이 있는 것을 보장합니다.

    이런 설정을 반동기식이라 합니다.

     

    보통 리더 기반 복제는 완전한 비동기식으로 구성됩니다.

    이런 경우 리더가 잘못되고 복구할 수 없으면 팔로워에 아직 복제되지 않은 모든 쓰기는 유실됩니다.

    즉, 쓰기가 클라이언트에게 확인된 경우에도 지속성을 보장하지 않습니다.

    하지만 완전 비동기식 설정은 모든 팔로워가 잘못되더라도 리더가 쓰기 처리를 계속할 수 있다는 장점이 있습니다.

     

     

    새로운 팔로워 설정

    만약 복제 서버 수를 늘리거나 장애 노드의 대체를 위해 새로운 팔로워를 설정하는 경우에는 팔로워가 리더의 데이터 복제본을 정확히 가지고 있는지 어떻게 보장할까요?

     

    간단히 한 노드에서 다른 노드로 데이터 파일을 복사하는 것만으로는 충분하지 않습니다.

    클라이언트는 지속적으로 데이터베이스에 기록하고 데이터는 항상 유동적이기 때문에 표준 파일 복사본은 다른 시점에 데이터베이스의 다른 부분을 보게 됩니다.

     

    데이터베이스를 쓰기가 불가능하게 만들어 디스크 파일을 일관성 있게 만들 수 있지만 고가용성 목표에 부합하지 않습니다.

     

    다행히 팔로워 설정은 대개 중단시간 없이 수행할 수 있으며, 그 과정은 개념적으로 다음과 같습니다.

     

    1. 가능하다면 DB를 잠그지 않고 리더 데이터베이스 스냅숏을 일정 시점에 가져온다

     

    2. 스냅숏을 새로운 팔로워 노드에 복사한다.

     

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

     

    4. 팔로워가 스냅숏 이후 데이터 변경의 미처리분을 모두 처리했을 때 따라잡았다고 말하며 이제 리더의 발행하는 데이터 변화에 이어 처리를 진행한다.

     

    노드 중단 처리

    시스템의 모든 노드는 장애로 인해 예기치 않게 중단될 수 있습니다.

     

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

    각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관합니다.

    이를 통해 결함이 발생하기 전 처리한 마지막 트랜잭션을 알아내고 리더와 연결하여 끊어진 동안 발생한 데이터 변경을 모두 요청합니다.

     

    리더 장애: 장애 복구

    리더의 장애를 처리하는 일은 까다롭습니다.

     

    장애 복구(Failover) 과정

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

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

    3. 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비해야 합니다.

     

    장애 복구는 수동으로 진행하거나 자동으로 진행합니다.

     

    자동 장애 복구 과정

    1. 리더가 장애인지 판단한다

    고장, 정전, 네트워크 문제 등 잠재적으로 여러 가지가 잘못될 수 있다

    대부분의 시스템은 단순히 타임아웃을 사용합니다.

    노드들은 자주 서로 메시지를 주고받으면 일정 시간 동안 노드가 응답하지 않으면 죽은 것으로 간주합니다.

     

    2. 새로운 리더를 선택합니다.

    선출 과정을 통해 이루어지거나 이전에 선출된 제어 노드에 의해 새로운 리더가 임명됩니다.

    가장 적합한 후보는 보통 이전 리더의 최신 데이터 변경사항을 가진 복제 서버입니다.

     

    3. 새로운 리더 사용을 위해 시스템을 재설정합니다.

    클라이언트는 이제 새로운 쓰기 요청을 새로운 리더에게 보내야 합니다.

     

     

    장애 복구 과정에서 발생할 수 있는 문제들

     

    - 비동기식 복제 사용 시 새로운 리더가 이전 리더가 실패하기 전에 이전 리더의 쓰기를 일부 수신하지 못할 수 있다. 이런 경우 내구성에 대한 클라이언트의 기대를 저버리게 됩니다.

     

    - 깃허브에서 발생한 사고 중 하나로 유효하지 않은 팔로워가 리더로 승격된 사례가 있습니다.

    autoincrement 방식에서 새로운 리더의 카운터는 이전 리더보다 뒤처져 있었으며 이전 리더가 예전에 할당한 기본키를 재사용하였습니다. 이 기본키가 redis 저장에도 사용했기 때문에 일부 개인 데이터가 잘못된 사용자에게 공개되었습니다.

     

    - 특정 결함 시나리오에서 두 노두가 모두 자신이 리더라고 믿을 수 있습니다. 스플릿 브레인 상황이라고 하며 매우 위험한 상황입니다.  쓰기의 충돌을 해결하지 않으면 데이터가 유실되거나 오염될 수 있습니다.

     

    - 타임아웃의 적절한 시간 측정하기 : 타임아웃이 너무 짧으면 불필요한 장애 복구, 타임아웃이 너무 길면 복구하는데 시간이 오래 걸립니다. 일시적인 부하 급증으로 노드의 응답 시간이 타임아웃보다 커지거나 네트워크 고장으로 패킷이 지연되는 경우에는 불필요한 장애 복구가 상황을 더 악화시킬 수 있습니다.

     

    이런 문제에 대한 쉬운 해결책이 없습니다.

    따라서 일부 운영팀은 소프트웨어가 자동 장애 복구를 지원하더라도 수동으로 장애 복구를 수행하는 방식을 선호합니다.

     

    복제 로그 구현 : 리더 기반 복제의 내부적인 동작법

    리더는 모든 쓰기 요청을 기록하고 쓰기를 실행한 다음 구문 로그를 팔로워에게 전송합니다.

     

    RDB에서 모든 DML 구문을 팔로워에게 전달하고 각 팔로워는 클라이언트에서 직접 받은 것처럼 SQL 구문을 파싱 하고 실행할 수 있습니다.

     

    간단해 보이지만 복제가 깨질 수 있는 다양한 사례가 존재합니다.

     

    - 현재 날짜와 시간을 얻기 위한 NOW()나 임의 숫자를 얻기 위한 RAND() 같은 비결정적 함수를 호출하는 모든 구문을 각 복제 서버마다 다른 값들을 생성할 수 있음

     

    -자동 증가 칼럼을 사용하는 구문이나 DB에 있는 데이터에 의존한다면 구문은 각 복제 서버에서 정확히 같은 순서로 실행되어야 합니다. 즉 동시성이 떨어지게 됩니다.

     

    - side effect를 가진 구문에서 각 복제 서버에 다른 부수 효과가 발생할 수 있습니다.

     

     

    이런 경우에는 고정 값을 반환하게끔 하여 대처할 수 있지만 일반적으로는 다른 복제 방법을 선호합니다.

    MySQL 5.1 이전 버전에서는 구문 기반 복제가 사용됐습니다.

    하지만 현재는 구문에 비결정적이 있다면 기본적으로 로우 기반 복제를 사용합니다.

     

     

    최종적 일관성

    리더에 쓰기가 일어나기 때문에 팔로워까지 데이터가 동기화되기 전에 사용자가 조회할 경우 쓰기 DB와 읽기 DB의 데이터가 다를 수 있습니다.

     

    하지만 이런 불일치는 일시적인 상태에 불과하며 시간이 잠시 진면 결국 리더와 팔로워의 데이터를 일치하게 됩니다.

     

    이런 효과를 최종적 일관성이라고 합니다.

     

    일반적으로 지연시간은 매우 짧지만 시스템에 부하가 있거나 네트워크 문제가 있으면 지연은 쉽게 수초에서 수분으로 증가할 수 있습니다.

     

    자신이 쓴 내용 읽기

    사용자가 자신의 글을 게시하는 상황입니다.

    리더에 쓰기 요청을 보내고 리더로부터 완료 응답을 받았습니다.

    하지만 리더는 팔로워에게 데이터 동기화를 요청하며 그 순간 사용자가 팔로워에게 자신이 쓴 게시글의 데이터를 요청합니다.

     

    이때 팔로워는 아직 데이터 동기화가 안되어 사용자는 자신의 글을 조회할 수 없는 상황입니다.

     

    이런 상황에서는 쓰기 후 읽기 일관성이 필요합니다.

     

    쓰기 후 읽기 일관성을 지키는 방법

    - 사용자가 수정한 내용을 읽을 때는 리더에서 읽고 그 밖에서는 팔로워에서 읽는다.

    보통 SNS에서 사용자 프로필 정보는 자신만 편집할 수 있어서 사용자 소유의 프로필은 리더에서 읽고 다른 사용자의 프로필은 팔로워에서 읽는 간단한 규칙을 사용합니다.

     

    - 마지막 갱신 시간을 추적하여 1분 이내는 리더에서 읽는다

     

    또한 동일한 사용자가 여러 디바이스로 서비스를 접근할 때 또 다른 문제가 발생할 수 있습니다.

    이 경우에는 디바이스 간 쓰기 후 읽기 일관성이 제공되어야 합니다.

     

    단조 읽기

    사용자가 각기 다른 복제 서버에서 여러 읽기를 수행할 수 있습니다.

    이때 사용자의 시간이 거꾸로 흐르는 현상을 목격할 수 있습니다.

     

    예를 들어 사용자가 팔로워 서버 2,3을 통해 읽기를 수행합니다.

    이때 사용자의 요청이 정상적으로 등록되어 정상적인 읽기를 수행할 수 있습니다.

     

    이때 팔로워 서버 4,5에는 아직 데이터 동기화가 안되었고 사용자가 4,5로부터 읽기를 수행합니다

    팔로워 서버 2,3으로부터 보았던 데이터가 사라져 있는 것을 확인할 수 있고 사용자는 혼란에 빠집니다.

     

    단조 읽기는 이전에 새로운 데이터를 읽은 후에는 예전 데이터를 읽지 않도록 보장합니다.

     

    간단한 방법으로 사용자의 읽기가 항상 동일한 복제 서버에서 수행하게끔 하면 됩니다.

     

    사용자의 ID의 해시를 기반으로 복제 서버를 선택하도록 하면 됩니다.

    하지만 복제 서버가 고장 나면 사용자 질의를 다른 복제 서버로 재 라우팅해야 합니다.

     

    일관된 쓰기 읽기

    푼스 씨와 케이크 부인의 짧은 대화를 통해 알아보겠습니다.

     

    푼스 씨 : 미래에 대해 얼마나 멀리 볼 수 있나요. 케이크 부인?
    케이크 부인 : 보통 10초 정도요, 푼수 씨.

     

     

    두 문장 사이에는 인과성이 존재합니다.

     

    이때 리더와 팔로워 개념을 통해 보았을 때 케이크 부인의 말은 지연 없이 전달되고 푼스 씨의 말은 지연이 되었습니다.

     

    이때 사용자는 다음과 같이 보게 됩니다.

    케이크 부인 : 보통 10초 정도요, 푼수 씨.
    푼스 씨 : 미래에 대해 얼마나 멀리 볼 수 있나요. 케이크 부인?

    이런 초능력은 인상적이지만 매우 혼란스럽습니다.

     

    따라서 일관된 순서로 읽기와 같은 또 다른 유형의 보장이 필요합니다.

    일련의 쓰기가 특정 순서로 발생한다면 이 쓰기를 읽는 모든 사용자는 같은 순서로 쓰인 내용을 보게 됨을 보장받습니다.

     

    보통 파티셔닝 된 데이터베이스에서 발생하는 특징적인 문제입니다.

    하지만 많은 분산 데이터베이스에서 서로 다른 파티션은 독립적으로 동작하므로 쓰기의 전역 순서는 없습니다.

     

    해결책으로 서로 인관성이 있는 스기는 동일한 파티션에 기록되게끔 하는 방법이 있습니다.

     

    복제 지연을 위한 해결책

    최종적 일관성을 위해 복제 지연이 몇 분이나 몇 시간으로 증가한다면 애플리케이션 사용자의 경험을 좋지 않습니다.

     

    쓰기 후 읽기와 같은 강한 보장을 제공하기 위해 실제로는 비동기식으로 동작하지만 동기식으로 동작하는 척하는 것이 문제 해결법입니다.

     

    대게 트랜잭션으로 애플리케이션을 신뢰할 수 있도록 합니다.

    하지만 분산 데이터베이스로 전환하는 과정에서 많은 시스템에 트랜잭션을 포기했습니다.

    7장, 9장에서 트랜잭션 주제를 다시 다룹니다.

     

    다중 리더 복제

    리더를 하나 이상 둠으로써 각 리더는 동시에 다른 리더의 팔로워 역할도 수행합니다.

    보통 추가적인 복잡도가 발생하기 때문에 단일 데이터센터 내의 다중 리더 설정을 적절하지 않습니다.

     

    하지만 다중 데이터센터를 운영하는 경우, 오프라인 작업을 하는 클라이언트, 협업 편집에는 유용합니다.

     

    다중 리더 복제를 사용하는 경우에는 각 리더가 동일한 데이터에 대한 쓰기를 수행할 때 쓰기 충돌을 적절하게 해결해야 합니다.

     

    쓰기 충돌 다루기

    위키 페이지를 동시에 두 사용자가 편집한다고 가정하겠습니다.

    사용자 1은 페이지 제목을 A에서 B로 변경하고 같은 시각 사용자 2는 A에서 C 로 변경합니다.

    각 사용자의 변경은 로컬 리더에 성공적으로 적용됩니다.

    하지만 변경을 비동기로 복제할 때 충돌을 감지합니다.

    이 문제는 단일 리더 DB에서는 일어나지 않습니다.

     

    단일 리더 DB에서는 첫 번째 쓰기가 완료될 때까지 두 번째 쓰기를 차단해 기다리게 하거나 두 번째 쓰기 트랜잭션을 중단해 사용자가 쓰기를 재시도하게 합니다.

     

    반면 다중 리더에서는 두 쓰기 모두 성공하며 충돌은 비동기적으로만 감지됩니다.

    이때 사용자에게 충돌을 해소하게끔 요청하면 너무 늦을 수 있습니다.

     

    충돌 회피

    충돌을 처리하는 가장 간단한 전략은 충돌을 피하는 것입니다.

    특정 레코드의 모든 쓰기가 동일한 리더를 거치도록 애플리케이션이 보장한다면 충돌은 발생하지 않습니다.

    다중 리더 복제 구현 사례에서 충돌을 잘 처리하지 못하기 때문에 충돌을 피하는 것이 자주 권장되는 방법입니다.

     

    충돌 해소

    - 각 쓰기에 고유 ID를 부여하고 가장 높은 ID를 가진 쓰기를 고른다.

    대중적이지만 데이터 유실 위험이 있다.

    고유 ID : 타임스탬프, 키와 값의 해시 값, UUID , 임의의 숫자 등

     

    - 데이터 구조의 충돌을 기록해 모든 정보를 보존하고 사용자에게 충돌을 해소하게끔 한다.

     

    - 어떻게든 값을 병합한다

     

    다중 리더 복제 토폴로지

    토폴로지는 네트워크의 연결방식을 뜻합니다.

     

    즉, 다중 리더 복제들이 어떤 식으로 연결되었는지 알아보는 장입니다.

     

    일반적으로 원형 토폴로지, 별 모양 토폴로지, 전체 연결 토폴로지가 존재합니다.

     

    원형과 별 모양 토폴로지의 단점은 모든 복제 서버에 도달하기 전에 여러 노드를 거쳐야 합니다.

    따라서 하나의 노드에 장애가 발생하면 장애가 다른 노드 간 복제 메시지 흐름에 방해를 줍니다.

    따라서 전체 연결 토폴리지가 가장 일반적으로 사용됩니다.

     

    하지만 전체 연결 토폴로지에서도 문제가 발생할 수 있습니다.

    일부 네트워크 연결이 다른 연결보다 빠르다면 일부 복제 메시지가 다른 메시지를 추월할 수 있습니다.

     

    예를 들어 클라이언트 2명이 있습니다.

    1. 클라이언트 A가 리더 1의 테이블에 로우를 삽입합니다.

    2. 클라이언트 B가 리더 3에 로우를 갱신합니다.

    3. 이때 네트워크가 순간 느린 부분이 있어 리더 2 관점에서는 데이터베이스 아직 로우의 삽입이 반영되지 않았습니다.

    4. 근데 로우를 갱신하는 쿼리가 네트워크가 더 빨라서 먼저 리더 2에게 도착합니다.

    5. 로우의 삽입이 반영되기 전 갱신이 도착하여 무시됩니다.

     

    일관된 순서로 읽기에서 본 인과성 문제와 동일합니다.

    이런 이벤트를 올바르게 정렬하기 위해 버전 벡터라고 하는 기법을 사용할 수 있습니다.

     

    하지만 많은 다중 리더 복제 시스템에서 충돌 감지 기법은 제대로 구현되지 않았습니다.

    쓰기 시점에 PostgresQL은 쓰기의 인과적 순서를 제공하지 않으며, MySQL 텅스텐 리플리케이터는 충돌을 감지하기 위한 시도조차 하지 않습니다.

     

    다중 리더 복제 시스템을 이용하려면 이런 문제를 인지하고 문서를 주의 깊게 읽은 다음 DB를 철저하게 테스트해 실제로 믿을 만한 보장을 제공하는지 확인하는 편이 좋습니다.

     

    리더 없는 복제

    일부 데이터 저장소 시스템은 리더의 개념을 버리고 모든 복제 서버가 클라이언트로부터 쓰기를 직접 받을 수 있게 허용하는 접근 방식을 사용하기도 합니다.

     

    리더 없는 복제는 아마존이 내부 다이나모 시스템에서 사용한 후 유명해졌습니다.

    리악, 카산드라, 볼드모트는 다이나모에서 영감을 얻은 리더 없는 복제 모델의 오픈소스 데이터 스토어입니다.

     

    클라이언트가 여러 복제 서버에 쓰기를 직접 전송하는 반면 코디네이터 노드가 클라이언트를 대신해 이를 수행하기도 합니다.

    하지만 리더 데이터베이스와 달리 코디네이터 노드는 특정 순서로 쓰기를 수행하지 않습니다.

    따라서 설계에서 이런 차이는 데이터베이스 사용 방식에 중대한 영향을 미칩니다.

     

    노드가 다운되었을 때 

    만약 3개의 복제 서버가 존재했을 때 하나의 서버가 사용 불가능해 쓰기를 놓쳤습니다. (2개의 복제 서버에는 쓰기 완료)

    세 개의 복제 서버 중 두 개의 복제 서버가 쓰기를 확인하다면 충분하다고 가정하겠습니다.

     

    사용자 1234가 두 개의 ok 응답을 받고 쓰기가 성공한 것으로 간주합니다.

     

    이후에 사용 불가능한 복제 서버가 복구되고 클라이언트가 해당 서버로부터 읽기를 수행합니다.

     

    과거에 있던 데이터가 조회되는 문제가 발생합니다.

     

    이를 해결하기 위해 읽기 요청을 여러 노드에 전송하고 오래된 값과 최신 값을 버전 숫자를 이용해 결정합니다.

     

    읽기 복구와 안티 엔트로피

     

    읽기 복구

    여러 노드에서 병렬로 읽기를 수행하면 오래된 응답을 감지할 수 있습니다.

    이 상황이 발생하는 경우 오래된 복제 서버에 새로운 값을 다시 기록합니다.

     

    안티 엔트로피 처리

    백그라운드 프로세스를 두고 복제 서버 간 데이터 차이를 지속적으로 찾아 누락된 데이터를 하나의 복제 서버에서 다른 서버로 복사합니다.

     

    읽기와 쓰기를 위한 정족수

    정족수란?

    정족수는 여러 사람의 합의로 운영되는 의사 기관에서 의결을 하는데 필요한 최소한의 참석자 수이다

     

    위의 예시에서는 복제 서버 세 개 중 두 개에서만 처리해도 성공한 것으로 간주합니다.

     

    그러면 n개의 복제 서버가 존재하면 몇 개 정도에서 처리해야 성공한 것으로 간주해야 할까요?

     

    n개의 복제 서버가 있을 때 모든 쓰기는 w개의 노드에서 성공해야 쓰기가 확정되고 모든 읽기는 최소한 r개의 노드에 질의해야 한다.

     

    w + r > n이면 읽을 때 최신 값을 얻는 것으로 기대합니다.

     

    이런 r과 w를 따르는 읽기와 쓰기를 정속수 읽기와 쓰기라고 부릅니다.

     

    예를 들어 쓰기가 적고 읽기가 많은 작업 부하는 w=n, r=1로 설정하면 좋습니다.

    하지만 노드 하나가 고장 나면 모든 데이터베이스 쓰기가 실패하는 단점이 있습니다.

     

     

    정족수 일관성의 한계

    하지만 w + r > n 인 경우에도 오래된 값을 반환하는 에지 케이스가 존재합니다.

    따라서 w와 r로 오래된 값을 읽는 확률을 조정할 수 있지만 이를 절대적으로 보장할 수는 없습니다.

     

    최신성 모니터링

    운영 관점에서 볼 때 DB가 최신 결과를 반환하는지 여부는 중요합니다.

    애플리케이션이 오래된 값 읽기를 허용하더라도 복제 상태에 대해 알아야 합니다.

    복제가 명확히 뒤처진다면 원인을 조사할 수 있게 알려줘야 합니다.

     

    리더 기반 복제에서는 쓰기가 리더에 적용되고 같은 순서로 팔로워에도 적용되고 각 노드가 복제 로그의 위치를 가지기 때문에 가능합니다.

    리더의 현재 위치에서 팔로워의 현재 위치를 빼면 복제 지연량을 측정할 수 있습니다.

     

    리더 없는 복제 시스템에서는 쓰기가 적용된 순서를 고정할 수 없어 모니터링이 어렵습니다.

    오래된 복제 서버에서 반환된 값은 아주 오래된 값일 수 있습니다.

     

    리더 없는 복제 DB에서 복제 서버의 오래됨을 측정하고 매개변수 n, w, r에 따라 오래된 값을 읽는 비율을 예측하는 연구가 있었습니다.

     

    느슨한 정족수와 암시된 핸드오프

    정족수는 내결함성이 없습니다.

    네트워크 중단으로 다수의 데이터베이스 노드와 클라이언트는 쉽게 연결이 끊어질 수 있습니다.

    이런 상황에서 응답 가능한 노드가 w가 r보다 적을 가능성이 있고 클라이언트는 더 이상 정족수를 충족할 수 없습니다.

     

    이 경우 데이터베이스 설계자는 트레이드오프에 직면합니다.

    - w, r 노드 정족수를 만족하지 않는 모든 요청에 오류를 반환할까?

    - 일단 쓰기를 받아들이고 값이 보통 저장되는 n개 노드에 속하지는 않지만 연결할 수 있는 노드에 기록할까?

     

    후자를 느슨한 정족수라 부릅니다.

     

    쓰기를 지정된 n개의 "홈" 노드에 없는 노드가 포함될 수 있습니다.

    비유하자면 내 집 문이 잠겨 들어갈 수 없다면 이웃집 문을 두르려 소파에 잠시 모물 수 있는지 묻습니다.

     

    이후 네트워크 장애 상황이 해제되면 한 노드가 다른 노드를 위해 일시적으로 수용한 모든 쓰기를 해당 "홈" 노드로 전송합니다. 이 방식을 암시된 핸드오프라 합니다.

     

    비유하자면 내 집 열쇠를 다시 찾으면 이웃이 정중하게 소파에서 일어나 집으로 돌아가라고 요청합니다.

     

    동시 쓰기 감지

    다이나모 스타일 데이터베이스는 여러 클라이언트가 동시에 같은 키에 쓰는 것을 허용하기 때문에 엄격한 정족수를 사용하더라도 충돌이 발생합니다.

     

    다양한 네트워크 지연과 부분적인 장애로 이벤트가 다른 노드에 다른 순서로 도착할 수 있습니다.

    두 클라이언트 A, B가 키 X를 동시에 세 노드 데이터 스토어에 기록하는 상황 입나다.

     

    노드 1은 A로부터 쓰기를 받지만 순간적인 장애로 B로부터 쓰기를 받지 못한다.

    노드 2는 A로부터의 쓰기를 먼저 받고 그다음 B로부터의 쓰기를 받는다.

    노드 3은 B로부터의 쓰기를 먼저 받고 그다음 A로부터 쓰기를 받는다.

     

    단순하게 쓰기 요청을 받을 때마다 덮어쓴다면 영구적으로 일관성이 깨집니다.

     

    최종 쓰기 승리

    영구적인 일관성을 위해서는 각 복제본이 가진 예전 값을 버리고 가장 최신 값으로 덮어쓰는 방법이 있습니다.

    하지만 어떤 쓰기가 최신인지 명확하게 결정할 수 있어야 합니다.

     

    쓰기에 타임스탬프를 붙여 제일 큰 타임스탬프를 선택하고 다른 것은 무시할 수 있습니다.

     

    최종 쓰기 승리라 부르는 충돌 해소 알고리즘은 카산드라에서 유일하게 제공하는 충돌 해소 방법이고 리악에서는 선택적 기능입니다.

     

    이때 클라이언트에게 모두 성공이라 보고되지만 쓰기 중 하나만 채택되고 다른 쓰기는 조용히 무시됩니다.

     

     손실 데이터를 허용하지 않는다면 LWW가 충돌 해소에 적합하지 않습니다.

     

     

    인과성과 동시성

    B 작업이 A 작업 기반이라면 B는 A에 인과성이 있다고 합니다.

    어느 작업도 다른 작업에 대해 알지 못한다면 단순히 동시 작업이라 합니다.

     

    어느 것이 먼저 일어났는지 알아야 덮어쓰거나 충돌을 해소할 수 있습니다.

     

    이전 발생 관계 파악하기

    장바구니에 동시에 상품을 추가하는 예시가 주어집니다.

     

    이 예에서 클라이언트는 서버와 동일한 최신 상태로 유지하지 못합니다.

    항상 다른 작업이 동시에 수행되었었기 때문에 최종적으로는 예전 버전의 값을 덮어쓰기 때문에 손실된 쓰기는 없습니다.

    서버는 버전 번호를 보고 두 작업이 동시에 수행됐는지 여부를 결정할 수 있으므로 값 자체를 해석할 필요는 없습니다.

     

    쓰기가 이전 읽기의 버전 번호를 포함하면 쓰기가 수행되기 이전 상태를 알 수 있습니다.

    버전 번호를 포함하지 않은 쓰기는 다른 쓰기와 동시에 수행된 것이므로 아무것도 덮어쓰지 않습니다.

     

    동시에 쓴 값 병합

    어떤 데이터도 자동으로 삭제되지 않음을 보장하지만 클라이언트가 추가적으로 작업을 수행해야 합니다.

    여러 작업이 동시에 발생하면 클라이언트는 동시에 쓴 값을 합쳐 정리해야 합니다.

    리악은 이런 동시 값을 형제(sibling) 값이라 부릅니다.

     

    이는 다중 리더 복제에서 충돌을 해소하는 문제와 본질적으로 같습니다.

     

    장바구니 예제에서 형제를 병합하는 합리적인 접근 방식은 합집합을 취하는 것입니다.

     

    하지만 추가 외 제거도 할 수 있게 하려면 합집합으로는 올바른 결과를 얻을 수 없습니다.

     

    삭제를 할 때 해당 버전 번호를 표시하여 남겨둬야 하며 이런 삭제 표시를 툼스톤이라 합니다.

     

    버전 벡터

    다중 복제본의 동시 쓰기를 할 때는 키당 버전 번호뿐만 아니라 복제본당 버전 번호도 사용해야 합니다.

    이를 버전 벡터라 합니다.

    버전 벡터를 사용하면 데이터베이스는 덮어쓰기와 동시 쓰기를 구분할 수 있습니다.

     

     

    정리

    복제의 다양한 사용

    - 고가용성

    - 네트워크 중단에도 내결함성

    - 지리적으로 사용자에게 가깝게 하여 지연 시간 감소

    - 많은 읽기 부하 작업 처리

     

    복제에 대한 주요 접근 방식

    - 단일 리더 복제

    - 다중 리더 복제

    - 리더 없는 복제

     

    복제 지연 시 대처하는 방법

    - 쓰기 후 읽기 일관성

    - 단조 읽기

    - 일관된 순서로 읽기

     

    다음 장에서는 복제와 대응대는 주제로 큰 데이터셋을 파티션으로 나누는 방법을 다룹니다.

     

     

     

     

     

     

     

     

    'CS > 데이터 중심 애플리케이션 설계 요약' 카테고리의 다른 글

    7장 - 트랜잭션  (0) 2022.12.15
    6장 - 파티셔닝  (1) 2022.12.10
    4장 - 부호화와 발전  (0) 2022.11.18
    3장 - 저장소와 검색  (0) 2022.11.17
    2장 - 데이터 모델과 질의 언어  (0) 2022.11.16

    댓글

Designed by Tistory.