[데이터 중심 애플리케이션 설계] 09. 일관성과 합의

예니·2023년 2월 19일
0
post-thumbnail

내결함성을 지닌 시스템을 구축하는 가장 좋은 방법은 유용한 보장을 해주는 범용 추상화를 찾아 이를 구현하고, 애플리케이션에서 이 보장에 의존하게 하는 것이다.

일관성 보장

  • 복제 데이터베이스는 대부분 최소한 최종적 일관성을 제공한다. 그러나 이것은 언제 복제본이 수렴될지 모르기 때문에 매우 약한 보장이다.
  • 강한 보장을 제공하는 시스템은 성능이 나쁘거나 약한 보장을 제공하는 시스템보다 내결함성이 약할지도 모른다. 하지만 올바르게 사용하기는 더 쉽다.

선형성

  • 선형성은 원자적 일관성, 강한 일관성, 즉각 일관성, 외부 일관성 이라고도 한다.
  • 기본 아이디어는 시스템에 데이터 복사본이 하나만 있고(하나만 있는 것처럼 보이도록 하고), 그 데이터를 대상으로 수행하는 모든 연상은 원자적인 것처럼 보이게 만드는 것이다. 선형성은 최신성 보장(recency guarantee)이다.

시스템에 선형성을 부여하는 것은 무엇인가?

  • 한 클라이언트의 읽기가 새로운 값을 반환하면 이후의 모든 읽기 또한 새로운 값을 반환해야 한다. 쓰기 연산이 아직 완료되지 않았더라도!
  • 선형성의 요구사항은 연산 표시를 모은 선들이 항상 시간순으로 진행돼야 하고, 결코 뒤로 가서는 안된다는 것이다.

선형성에 기대기

선형성이 중요한 요구사항이 되는 몇 가지 영역을 살펴보자.

잠금과 리더 선출

  • 단일 리더 복제를 사용하는 시스템은 리더가 하나만 존재하도록 보장해야 한다. 리더를 선출하는 한 가지 방법은 잠금을 사용하는 것이다. 모든 노드가 시작할 때 잠금 획득을 시도하고 성공한 노드가 리더가 된다. 이 잠금은 선형적이어야 한다. 모든 노드는 어느 노드가 잠금을 소유하는지에 동의해야 한다.
  • 분산 잠금과 리더 선출을 구현하기 위해 주키퍼, etcd 같은 코디네이션 서비스가 사용된다.

제약 조건과 유일성 보장

  • 관계형 데이터베이스에서 전형적으로 볼 수 있는 엄격한 유일성 제약 조건은 선형성이 필요하다.
  • 제약 조건은 모든 노드가 동의하는 하나의 최신 값이 있기를 요구한다.

채널 간 타이밍 의존성

시스템에 부가적인 통신 채널이 있을 때는 선형성이 보장되어야 한다. 선형성의 최신성 보장이 없으면 두 채널 사이에 경쟁 조건이 발생할 수 있다.

선형성 시스템 구현하기

  • 가장 간단한 방법은 복사본 하나만 사용하는 것이지만 이 방법은 내결함성이 없다.
  • 각 복제 방법을 선형적으로 만들 수 있을지 비교
    • 단일 리더 복제(선형적이 될 가능성이 있음) 동기식 복제라면 선형적이 될 가능성이 있다. 비동기 복제에서는 불가능.
    • 합의 알고리즘(선형적) 주키퍼, etcd가 이렇게 동작한다.
    • 다중 리더 복제(비선형적) 비동기로 복제하므로 비선형적이다.
    • 리더 없는 복제(아마도 비선형적) 정족수에 따라 다르다.

선형성과 정족수

  • 다이나모 스타일처럼 엄격한 정족수를 사용한 읽기 쓰기는 선형적인 것처럼 보이지만, 네트워크 지연의 변동이 심하면 경쟁 조건이 생길 수 있다.
  • 성능을 희생하면 다이나모 스타일 정족수를 선형적으로 만들 수는 있다. 읽기를 실행하는 클라이언트는 결과를 애플리케이션에 반환하기 전에 읽기 복구를 동기식으로 수행해야 하고, 쓰기를 실행하는 클라이언트는 쓰기 요청을 보내기 전에 노드들의 정족수로부터 최신 상태를 읽어야 한다. → 다이나모 스타일 복제를 하는 리더 없는 시스템은 선형성을 제공하지 않는다고 보는 것이 안전하다.

선형성의 비용

CAP 정리

  • 애플리케이션이 선형성을 요구한다면, 가용성은 없다.
  • 애플리케이션이 선형성을 요구하지 않는다면, 가용한 상태를 유지하지만 동작이 비선형적일 수 있다.
  • 일관성, 가용성, 분단 내성 세개 중 두개를 고르라는 오해가 있는데, 네트워크 분단은 결함이므로 선택할 수 있는 것이 아니다. 무조건 발생할 수 있다. 따라서 CAP는 네트워크 분단이 생겼을 때, 일관성과 가용성 중 하나를 선택하라는 의미로 보는 것이 낫다.
  • 공식적인 CAP 정리의 정의는 범위가 매우 좁기 때문에 시스템을 설계할 때는 실용적인 가치가 없다.

선형성과 네트워크 지연

선형성은 느리다. 선형성을 제거하면 성능을 얻을 수 있다.

지연 시간에 민감한 시스템에서는 이 트레이드오프가 중요하다.

순서화 보장

순서화와 인과성

  • 순서화가 인과성을 보존하는 데 도움을 준다.
  • 인과성은 이벤트에 순서를 부과한다.
  • 시스템이 인과성에 의해 부과된 순서를 지키면 그 시스템은 인과적으로 일관적이다. (causally consistent)

인과적 순서가 전체 순서는 아니다

  • 선형성 선형성 시스템에서는 연산의 전체 순서를 정할 수 있다. 시스템이 데이터 복사본이 하나만 있는 것처럼 동작하고 모든 연산이 원자적이면 어떤 두 연산에 대해 항상 둘 중 하나가 먼저 실행됐다고 말할 수 있다.
  • 인과성 두 이벤트에 인과적인 관계가 있으면 이들은 순서가 있지만 이들이 동시에 실행되면 비교할 수 없다. 이는 인과성이 부분 순서를 정의한다는 뜻이다.
  • 선형성 데이터스토어에는 동시적 연산이 없다. 하나의 타임라인이 있고, 모든 연산은 그 타임라인을 따라서 전체 순서가 정해져야 한다.
  • 동시성은 타임라인이 갈라졌다 다시 합쳐지는 것을 의미한다. 이 경우 다른 가지에 있는 연산은 비교 불가하다.

선형성은 인과적 일관성보다 강하다

  • 선형성은 인과성을 내포한다. 선형적이라면 인과성도 올바르게 유지한다.
  • 선형성은 성능과 가용성에 해가 될 수 있지만, 절충안이 있다. 인과적 일관성은 네트워크 지연의 영향을 받지 않고, 장애가 생겨도 가용한 일관성 모델 중 가장 강한 것이다.
  • 선형성이 필요해 보이는 곳에서 사실 필요한 것은 인과적 일관성이며 구현도 더 효율적이다.

인과적 의존성 담기

  • 인과성을 유지하기 위해 어떤 연산이 어떤 다른 연산보다 먼저 실행됐는지 알아야 한다.
  • 복제 서버가 연산을 처리할 때, 인과적으로 앞서는 모든 연산이 이미 처리됐다고 보장할 수 있어야 한다.
  • 인과적 의존성은 단일 키뿐만 아니라, 전체 데이터베이스에 걸친 인과적 의존성을 추적해야 하며, 이를 위해 버전 벡터를 일반화할 수 있다.
  • 인과적 순서를 결정하기 위해 데이터베이스는 애플리케이션이 데이터의 어떤 버전을 읽었는지 알아야 한다.

일련번호 순서화

  • 읽은 데이터를 모두 명시적으로 추적하는 것은 오버헤드가 크다.
  • 더 좋은 방법으로 일련번호나 타임스탬프를 써서 이벤트의 순서를 정할 수 있다. 타임스탬프는 물리적 시계가 아니라 논리적 시계에서 얻어도 된다.
  • 일련번호나 타임스탬프는 크기가 작고 전체 순서를 제공한다. 모든 연산은 고유 일련번호를 갖고 항상 두 개의 일련번호를 비교해서 어떤 것이 큰지 결정할 수 있다.
  • 인과성에 일관적인 전체 순서대로 일련번호를 생성할 수 있다. 이런 전체 순서는 인과 정보를 모두 담지만, 인과성에 꼭 필요한 것보다 순서화를 더 부과한다.

비인과적 일련번호 생성기

  • 단일 리더가 없다면 연산에 사용할 일련번호를 생성하는 방법이 명확하지 않다. 현실에서 다양한 방법이 사용된다. 하지만 이 방법들도 여러 노드에 걸친 연산들의 순서를 올바르게 담지 못하기 때문에 인과성에 일관적이지 않다.
    • 각 노드가 자신만의 독립적인 일련번호 집합을 생성할 수 있다. 각 노드의 초당 연산수가 다를 수 있다.
    • 각 연산에 일 기준 시계(물리적 시계)에서 얻은 타임스탬프를 붙일 수 있다. 물리적 시계에서 얻은 타임스탬프는 시계 스큐에 종속적이라 인과성에 일관적이지 않게 될 수 있다.
    • 일련번호 블록을 미리 할당할 수 있다. (ex. 노드A는 1~1000, 노드B는 1001~2000) 위 같이 할당한 경우, B가 A보다 먼저 실행된다면 일련번호가 인과성에 일관적이지 않다.

램포트 타임스탬프

  • 인과성에 일관적인 일련번호를 생성하는 간단한 방법이다.
  • 각 노드는 고유 식별자를 갖고 각 노드는 처리한 연산 개수를 카운터로 유지한다. 램포트 타임스탬프는 (카운터, 노드ID)의 쌍이다.
  • 두 노드는 카운터 값이 같을 수 있지만, 램포트 타임스탬프는 노드ID를 가지므로 각 타임스탬프는 유일하다.
  • 두 타임스탬프가 있으면 카운터가 큰 것이 타임스탬프가 크고, 두 카운터가 같다면 노드ID가 큰 것이 타임스탬프가 크다.
  • 모든 노드와 모든 클라이언트가 지금까지 본 카운터 값 중 최댓값을 추적하고 모든 요청에 그 최댓값을 포함시킨다. 노드가 자신의 카운터 값보다 큰 최대 카운터를 가진 요청이나 응답을 받으면 바로 자신의 카운터를 그 최댓값으로 증가시킨다.
  • 버전 벡터는 두 연산이 동시적인지, 어떤 연산이 다른 연산에 인과적으로 의존하는지 구별할 수 있다. 하지만 램포트 타임스탬프는 항상 전체 순서화를 강제한다. 램포트 타임스탬프의 전체 순서화로부터 두 연산이 동시적인지 또는 인과적으로 의존성이 있는지를 알 수 없다.

타임스탬프 순서화로는 충분하지 않다.

  • 요청의 성공/실패 여부를 당장 결정해야 할 때는 램포트 타임스탬프로 처리하는 방법은 부족하다. 연산의 전체 순서는 모든 연산을 모은 후에 드러나기 때문이다.
  • 사용자명에 대한 유일성 제약 조건 같은 것을 구현하려면 연산의 전체 순서가 있는 것으로는 충분하지 않고, 언제 그 순서가 확정되는지 알아야 한다.

전체 순서 브로드캐스트

전체 순서 브로드캐스트는 보통 노드 사이에 메시지를 교환하는 프로토콜로 기술된다.

두 가지 안전성 속성을 항상 만족해야 한다.

  • 신뢰성 있는 전달 reliable delivery 어떤 메시지도 손실되지 않는다. 메시지가 한 노드에 전달되면 모든 노드에도 전달된다.
  • 전체 순서가 정해진 전달 totally ordered delivery 메시지는 모든 노드에 같은 순서로 전달된다.

전체 순서 브로드캐스트 사용하기

  • 주키퍼나 etcd 같은 합의 서비스는 전체 순서 브로드캐스트를 실제로 구현한다.
  • 모든 메시지가 데이터베이스에 쓰기를 나타내고 모든 복제 서버가 같은 쓰기 연산을 같은 순서로 처리하면 복제 서버들은 서로 일관성 있는 상태를 유지한다. 이 원리를 상태 기계 복제라고 한다.
  • 전체 순서 브로드캐스트의 중요한 측면은 메시지가 전달되는 시점에 그 순서가 고정된다는 것이다.
  • 메시지 전달은 로그에 추가하는 것과 비슷하다. 모든 노드가 같은 메시지를 같은 순서로 전달해야 하므로 모든 노드는 로그를 읽어서 순서가 동일한 메시지를 볼 수 있다.

전체 순서 브로드캐스트를 사용해 선형성 저장소 구현하기

  • 전체 순서 브로드캐스트는 비동기식이다. 메시지는 고정된 순서로 신뢰성 있게 전달되도록 보장되지만 언제 메시지가 전달될지는 보장되지 않는다. 반대로 선형성은 최신성 보장이다. 읽기가 최근에 쓰여진 값을 보는 게 보장된다.
  • 그러나 전체 순서 브로드캐스트 구현이 있다면 이를 기반으로 한 선형성 저장소를 만들 수 있다. 전체 순서 브로드캐스트를 추가 전용 로그로 사용해 선형성 CAS연산을 구현할 수 있는데, 이 방법은 선형성 쓰기를 보장하지만 선형성 읽기는 보장하지 않는다.

선형성 저장소를 사용해 전체 순서 브로드캐스트 구현하기

  • 정수를 저장하고 원자적 increment-and-get 연산이 지원되는 선형성 레지스터가 있다면 간단히 구현할 수 있다.
  • 하지만 선형성 정수를 만드는 것이 어렵다. 실패가 없다면 쉽지만, 노드에 장애가 날 때 그 값을 복구하기 위해선 필연적으로 합의 알고리즘에 도달하게 된다.
  • 선형성 compare-and-set 레지스터와 전체 순서 브로드캐스트는 둘 다 합의와 동등하다고 증명할 수 있다.

분산 트랜잭션과 합의

  • 합의의 목적은 단지 여러 노드들이 뭔가에 동의하게 만드는 것이다.
  • 노드가 동의하는 것이 중요한 상황
    • 리더 선출
    • 원자적 커밋

원자적 커밋과 2단계 커밋(2PC)

  • 원자성은 실패한 트랜잭션이 절반만 완료된 결과나 절반만 갱신된 상태로 데이터베이스를 어지럽히는 것을 막아준다.
  • 원자성은 보조 색인이 주 데이터와 일관성을 유지하도록 보장한다.

단일 노드에서 분산 원자적 커밋으로

  • 단일 데이터베이스 노드에서 실행되는 트랜잭션에게 원자성은 흔히 저장소 엔진에서 구현된다. 따라서 단일 노드에서 트랜잭션 커밋은 데이터가 디스크에 지속성 있게 쓰여지는 순서에 결정적으로 의존한다. 따라서 커밋을 원자적으로 만들어주는 것은 단일 장치이다.
  • 그러나 트랜잭션에서 여러 노드가 관여한다면, 단지 모든 노드에 커밋 요청을 보내고 각 노드에서 독립적으로 트랜잭션을 커밋하는 것으로는 충분치 않다. 어떤 노드에서는 커밋이 성공하고 다른 노드에서는 실패해서 원자성 보장을 위반하기 쉽다.
  • 노드는 트랜잭션에 참여하는 다른 모든 노드도 커밋될 것이라고 확신할 때만 커밋이 돼야 한다.

2단계 커밋 소개

  • 2단계 커밋은 여러 노드에 걸친 원자적 트랜잭션 커밋을 달성하는, 즉 모든 노드가 커밋되거나 모든 노드가 어보트되도록 보장하는 알고리즘이다.
  • 단일 노드 트랜잭션에서처럼 하나의 커밋 요청을 하는 대신 2PC의 커밋/어보트 과정은 두 단계로 나뉜다.
  • 2PC는 단일 노드 트랜잭션에서는 보통 존재하지 않는 새로운 컴포넌트인 코디네이터(트랜잭션 관리자라고도 함)를 사용한다.
  • 데이터베이스 노드를 트랜잭션의 참여자라고 한다. 애플리케이션이 커밋할 준비가 되면 코디네이터가 1단계를 시작한다. 각 노드에 준비 요청을 보내서 커밋할 수 있는지 물어본다. 그 후 코디네이터는 참여자들의 응답을 추적한다.
    • 모든 참여자가 준비가 됐다고 응답하면 코디네이터는 2단계에서 커밋 요청을 보내고 커밋이 실제로 일어난다.
    • 하나라도 아니오로 응답하면 코디네이터는 2단계에서 모든 노드에 어보트 요청을 보낸다.

약속에 관한 시스템

  • 이 프로토콜에는 두 개의 중대한 “돌아갈 수 없는 지점”이 있다. 참여자는 “네”에 투표할 때 나중에 분명히 커밋할 수 있을 거라고 약속한다. 그리고 코디네이터가 한 번 결정하면 그 결정은 변경할 수 없다. 이런 약속은 2PC의 원자성을 보장한다.

코디네이터 장애

  • 준비 요청 중 어떤 게 실패하거나 타임아웃이 되면 코디네이터는 트랜잭션을 어보트한다. 커밋이나 어보트 요청이 실패하면 코디네이터는 무한히 재시도한다. 그러나 코디네이터가 죽으면 어떻게 되는지는 분명하지 않다.
  • 코디네이터가 죽거나 이 시점에 네트워크에 장애가 나면 참여자는 기다릴 수밖에 없다. 이 상태에 있는 참여자의 트랜잭션을 의심스럽다(in doubt) 또는 불확실하다(uncertain)고 한다.
  • 2PC가 완료할 수 있는 유일한 방법은 코디네이터가 복구되기를 기다리는 것뿐이다. 이것이 코디네이터가 참여자들에게 커밋이나 어보트 요청을 보내기 전에 디스크에 있는 트랜잭션 로그에 자신의 커밋이나 어보트 결정을 써야 하는 이유다.

3단계 커밋

  • 2단계 커밋은 2PC가 코디네이터가 복구하기를 기다리느라 멈출 수 있다는 사실 때문에 블로킹 원자적 커밋 프로토콜이라고 불린다.
  • 2PC의 대안으로 3단계 커밋이라는 알고리즘이 제안됐다.
  • 논블로킹 원자적 커밋은 완벽한 장애 감지기(노드가 죽었는지 여부를 구별할 수 있는 신뢰성 있는 메커니즘)가 필요하다. 기약 없는 지연이 있는 네트워크에서 타임아웃은 신뢰성 있는 장애 감지기가 아니다. 이런 까닭으로 코디네이터 장애 관련 문제가 있지만 2PC가 계속 쓰이고 있다.

현실의 분산 트랜잭션

  • 분산 트랜잭션은 평판이 엇갈린다.
  • 두 가지 매우 다른 종류의 분산 트랜잭션이 혼용된다.
    • 데이터베이스 내부 분산 트랜잭션

      데이터베이스 노드 사이에 내부 트랜잭션을 지원한다.

    • 이종 분산 트랜잭션

      이종 트랜잭션에서 참여자들은 둘 혹은 그 이상의 다른 기술이다.

      데이터베이스 내부 분산 트랜잭션은 흔히 매우 잘 동작하지만, 이종 기술에 걸친 트랜잭션은 훨씬 더 어렵다.

정확히 한 번 메시지 처리

  • 메시지와 그 처리 과정의 부수 효과를 원자적으로 커밋함으로써 메시지가 결과적으로 정확히 한 번 처리되도록 보장할 수 있다.
  • 하지만 이런 분산 트랜잭션은 트랜잭션의 영향을 받는 모든 시스템이 동일한 원자적 커밋 프로토콜을 사용할 수 있을 때만 가능하다.

XA 트랜잭션

  • X/Open XA는 이종 기술에 걸친 2단계 커밋을 구현하는 표준이다.
  • XA는 네트워크 프로토콜이 아니다. 트랜잭션 코디네이터와 연결되는 인터페이스를 제공하는 CAPI일 뿐이다.
  • XA는 애플리케이션이 네트워크 드라이버나 클라이언트 라이브러리를 사용해 참여자 데이터베이스나 메시징 서비스와 통신한다고 가정한다. 드라이버가 XA를 지원한다는 것은 연산이 분산 트랜잭션의 일부가 돼야 하는지 알아내기 위해 XA API를 호출한다는 것을 뜻한다.
  • 트랜잭션 코디네이터는 XA API를 구현한다. 트랜잭션의 참여자를 추적하고 참여자들에게 준비 요청을 보낸 후, 그들의 응답을 수집하고 각 트랜잭션에 대한 커밋/어보트 결정을 추적하기 위해 로컬 디스크에 있는 로그를 사용한다.

의심스러운 상태에 있는 동안 잠금을 유지하는 문제

  • 데이터베이스는 트랜잭션이 커밋하거나 어보트할 때까지 이런 잠금을 해제할 수 없다. 그러므로 2단계 커밋을 사용할 때 트랜잭션은 의심스러운 상태에 있는 동안 내내 잠금을 잡고 있어야 한다.
  • 이는 의심스러운 트랜잭션이 해소될 때까지 애플리케이션의 많은 부분을 사용할 수 없게 한다.

코디네이터 장애에서 복구하기

  • 고아가 된 의심스러운 트랜잭션, 즉 코디네이터가 어떤 이유 때문인지 그 결과를 결정할 수 없는 트랜잭션이 생길 수 있다. 이런 트랜잭션은 자동으로 해소될 수 없어서 잠금을 유지하고 다른 트랜잭션을 차단하면서 데이터베이스에 영원히 남는다.
  • 여기서 빠져나가는 유일한 방법은 관리자가 수동으로 트랜잭션을 커밋하거나 롤백할지 결정하는 것뿐이다.
  • 여러 XA 구현에는 참여자가 코디네이터로부터 확정적 결정을 얻지 않고 의심스러운 트랜잭션을 어보트하거나 커밋할지를 일방적으로 결정할 수 있도록 하는 경험적 결정이라고 부르는 비상 탈출구가 있다. 이는 ‘아마도 원자성을 깰 수 있다’를 완곡하게 표현한 것이다.

분산 트랜잭션의 제약

  • 핵심 구현은 트랜잭션 코디네이터 자체가 일종의 데이터베이스여야 한다는 점이고 따라서 다른 중요한 데이터베이스와 동일하게 신경써서 접근해야 한다.

내결함성을 지닌 합의

  • 하나 또는 그 이상의 노드들이 값을 제안할 수 있고 합의 알고리즘이 그 값 중 하나를 결정한다.
  • 합의 알고리즘은 다음 속성을 만족해야 한다.
    • 균일한 동의 어떤 두 노드도 다르게 결정하지 않는다.
    • 무결성 어떤 노드도 두 번 결정하지 않는다.
    • 유효성 한 노드가 값 v를 결정한다면 v는 어떤 노드에서 제안된 것이다.
    • 종료 죽지 않은 모든 노드는 결국 어떤 값을 결정한다.
  • 균일한 동의, 무결성 속성은 합의의 핵심 아이디어를 결정한다. 유효성 속성은 뻔한 해결책을 배제하기 위해 존재한다. 종료 속성은 내결함성의 아이디어를 형식화한다.

합의 알고리즘과 전체 순서 브로드캐스트

  • 합의 알고리즘에 공통으로 있는 고수준의 아이디어를 이해하는 것으로 충분하다.
  • 값의 순차열에 대해 결정해서 전체 순서 브로드캐스트 알고리즘을 만든다.
  • 전체 순서 브로드캐스트를 하려면 모든 노드에게 메시지가 정확히 한 번, 같은 순서로 전달돼야 한다. 이것은 합의를 몇 회 하는 것과 동일하다. 각 회마다 노드들은 다음에 보내기 원하는 메시지를 제안하고 전체 순서 상에서 전달될 다음 메시지를 결정한다.
  • 합의의 동의 속성 때문에 모든 노드는 같은 메시지를 같은 순서로 전달하도록 결정한다. 무결성 속성 때문에 메시지는 중복되지 않는다. 유효성 속성 때문에 메시지는 오염되지 않고 난데없이 조작되지 않는다. 종료 속성 때문에 메시지는 손실되지 않는다.

단일 리더 복제와 합의

  • 단일 리더 복제에서 리더를 사람이 수동으로 선택한다면, 리더 노드가 죽었을 때 사람의 개입이 필요하므로 합의의 종료 속성을 만족하지 않는다.
  • 기존 리더에 장애가 나면 팔로워 하나를 리더로 승격시켜 자동 리더 선출과 장애 복구를 수행하는 것은 내결함성을 지닌 전체 순서 브로드캐스트에 가까워지고 합의를 해결하는 데도 가까워진다.

에포크 번호 붙이기와 정족수

  • 합의 프로토콜은 모두 내부적으로 어떤 형태로든 리더를 사용하지만 리더가 유일하다고 보장하지는 않는다. 이 프로토콜들은 에포크 번호를 정의하고 각 에포크 내에서는 리더가 유일하다고 보장한다.
  • 현재 리더가 죽었다고 생각될 때마다 새 노드를 선출하기 위해 노드 사이에서 투표가 시작된다. 이 선출은 에포크 번호를 증가시키며 따라서 에포크 번호는 전체 순서가 있고 단조 증가한다. 리더 사이에 충돌이 있으면 에포크 번호가 높은 리더가 이긴다.
  • 리더가 내리려는 모든 결정에 대해 제안된 값을 다른 노드에게 보내서 노드의 정족수가 그 제안을 찬성한다고 응답하기를 기다려야 한다.
  • 따라서 두 번의 투표가 있다. 한 번은 리더 선출, 두 번째는 리더의 제안에 투표하기 위해서다. 중요한 것은 두 번의 투표를 하는 정족수가 겹쳐야 한다.
  • 2PC는 모든 참여자로부터 “네” 투표가 필요하지만, 내결함성을 지닌 합의 알고리즘은 노드의 과반수로부터만 투표를 받으면 된다.

합의의 제약

  • 제안이 결정되기 전에 노드가 제안에 투표하는 과정은 일종의 동기식 복제다.
  • 합의 시스템은 항상 엄격한 과반수가 동작하기를 요구한다. 노드 한 대의 장애를 견디려면 최소한 세 대의 노드가 필요하다.
  • 합의 알고리즘은 네트워크 문제에 특히 민감하다.

멤버십과 코디네이션 서비스

  • 주키퍼와 etcd는 완전히 메모리 안에 들어올 수 있는 작은 양의 데이터를 보관하도록 설계됐다. 이 소량의 데이터는 내결함성을 지닌 전체 순서 브로드캐스트 알고리즘을 사용해 모든 노드에 걸쳐 복제된다. 개별 메시지가 데이터베이스에 쓰기를 나타낸다면 같은 쓰기를 같은 순서로 적용함으로써 복제본들이 서로 일관성을 유지할 수 있다.
  • 주키퍼가 제공하는 흥미로운 기능들
    • 선형성 원자적 연산 원자적 CAS 연산을 사용해 잠금을 구현할 수 있다.
    • 연산의 전체 순서화 주키퍼는 모든 연산에 전체 순서를 정하고 각 연산에 단조 증가하는 트랜잭션 ID와 버전 번호를 할당한다.
    • 장애 감지 클라이언트와 서버는 주기적으로 하트비트를 교환한다.
    • 변경 알림

작업을 노드에 할당하기

주키퍼는 고정된 수의 노드에서 실행되고 이 노드들 사이에서 과반수 투표를 수행하면서 많아질 수 있는 클라이언트를 지원한다. 주키퍼는 노드들을 코디네이트하는 작업의 일부를 외부 서비스에 위탁하는 방법을 제공한다.

0개의 댓글