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

발표자 : 이호준, 조성직

이번 장에서는 내결함성을 지닌 분산 시스템을 구축하기 위해 사용하는 알고리즘과 프로토콜을 알아본다.

일관성 보장

약한보장

  • 복제 데이터 베이스는 대부분 최소한 최종적 일관성을 제공하나, 언제 수렴될지 모른다.

강한보장

  • 데이터 시스템이 선택적으로 제공

올바르게 사용하기 쉬우나 시스템 성능이 나쁘거나 내결함성이 약할수도 있다.

선형성

공통적으로 사용되는 가장 강한 일관성 모델 중 하나 단 하나의 DB 복제본만 있다고 가정 (원자적 일관성 - atomic consistency) 최신성 보장 (recency guarantee) - read 시 최신 값을 보장

비선형 시스템의 예

그림 9-1
그림 9-1. 이 시스템은 비선형이라서 축구팬들을 혼란스럽게 한다.

시스템에 선형성을 부여하는 것

그림 9-2
그림 9-2. 읽기 요청이 쓰기 요청과 동시에 실행되면 과거의 값을 반환할 수도 있고 새로운 값을 반환할 수도 있다.

  • 클라이언트는 자신의 요청이 언제 처리됐는지는 알지 못하고 요청, 응답 시간 사이에 처리됐다는 것만 앎
  • 클라이언트 C의 쓰기 요청 전의 읽기 요청은 0, 쓰기 요청이 끝난 후의 읽이 요청은 1을 반환해야 하는 것은 명백함
  • 쓰기 요청 중 읽기 요청에서는 0 또는 1이 반환될 수 있는데 선형 시스템에 기대하는 바가 아님

선형적 시스템을 위한 제약 조건 추가

그림 9-3
그림 9-3. 읽기가 새로운 값을 반환한 적이 있은 후에는 모든 후속 읽기(같은 클라이언트에서 실행되든 다른 클라이언트에서 실행되든)도 반듯이 새로운 값을 반환해야 한다.

  • 읽기가 새로운 값을 반환하면 그 이후 모든 읽기는 새로운 값을 반환해야 함

개별 연산 시각화를 위한 시점 연산 추가

그림 9-4
그림 9-4. 읽기와 쓰기의 영향이 나타나는 것으로 보이는 시점을 시각화하기, B의 마지막 읽기는 선형적이지 않다.

  • 연산 표시를 모은 선들은 항상 시간순으로 진행돼야 하고 이를 통해 최신성이 보장됨
  • cas - 클라이언트가 원자적 compare-and-set 연산을 요청
  • → cas(x, v1, v2) => r : 레지스터 x의 현재 값이 v1과 같으면 v2로 설정, 아니면 오류 반환

→ 선형성은 레지스터(개별 객체)에 실행되는 읽기, 쓰기에 대한 최신성 보장

잠금과 리더 선출

  • 단일 리더 복제에서는 리더가 하나만 존재해야 함
  • 잠금은 리더를 선출하는 방법
  • 선형적으로 모든 노드가 시작할 때 잠금 획득을 시도하고, 성공한 노드가 리더가 됨
  • 아파치 주키퍼, etcd 등 코디네이션 서비스 사용되기도 함

채널 간 타이밍 의존성

  • 시스템의 통신 채널에 의한 경쟁 조건 발생 가능
  • 크기 변경 모듈이 과거 버전 이미지를 처리해 저장하면 파일저장소의 원래 이미지와 영구적으로 불일치
  • 선형성으로 이러한 경쟁 조건을 회피 할 수 있음

그림 9-5
그림 9-5. 웹 서버와 이미지 크기 변경 모듈은 파일 저장소와 메세지 큐를 모두 써서 통신하므로 갱쟁 조건이 발생할 가능성이 열려 있다.

→ 메시지 큐와 파일 저장소가 선형적으로 동작하면 경쟁 조건의 위험에서 벗어날 수 있다

선형성 시스템 구현하기

선형성 시맨틱을 제공하는 시스템 구현

선형성은 근본적으로 “데이터 복사본이 하나만 있는 것처럼 동작하고 그 데이터에 실행되는 모든 연산은 원자적” 실제로 그러면 결함을 견뎌낼 수 없다. 내결함성을 지니기 위해 복제를 사용함

복제 방법들

  • 단일 리더 복제(선형적일 수 있음)
  • 합의 알고리즘(선형적)
  • 다중 리더 복제(비선형적)
  • 리더 없는 복제(아마도 비선형적)

선형성과 정족수

다이나모 모델에서 엄격한 정족수를 사용한 읽기 쓰기는 선형적으로 보이지만 경쟁 조건이 생길 수 있음

그림 9-5
그림 9-5. 웹 서버와 이미지 크기 변경 모듈은 파일 저장소와 메세지 큐를 모두 써서 통신하므로 갱쟁 조건이 발생할 가능성이 열려 있다.

  • n(복제 서버) = 3, w(쓰기 노드) = 3, r(읽기 노드) = 2 → 정족수 조건 만족 (w + r > n) 이지만
  • 선형적이지 않음
  • 성능상 불이익이나 선형성을 만족시킬 수 없기 떄문에 다이나모 스타일의 복제를 하는 리더 없는 시스템은 비선형적임

선형성의 비용

그림 9-7
그림 9-7. 네트워크가 끊기면 선형성과 가용성 사이에서 선택해야만 한다.

선형성

단일 리더 경우로 네트워크 중단 시 팔로워 DC로 접속한 Client 는 사용에 문제가 있다.
허나 리더 DC로 직접 접속이 가능하면 정상 동작할 수 있다.

가용성

다중 리더 경우로 비선형적이지만 DC간 네트워크 중단에도 정상 동작은 가능하다.
네트워크 복구 시 복제 요청이 전달됨

CAP 정리

  • Consistency(일관성), Availability(가용성), Partition tolerance(분단 내성)
  • 선형성 데이터베이스라면 이러한 선형성과 가용성의 트레이드오프 문제가 있음
  • 오직 하나의 일관성 모델(=선형성) 과 한 종류의 결함(네트워크 분단 or 연결이 끊긴 살아있는 노드) 만 고려함
  • 다른 부분인 네트워크 지연, 트레이드 오프 등에 대해 고려하지 않으므로 시스템 설계 시 고려할 실용적인 가치가 없음

선형성과 네트워크 지연

  • 최신 다중코어 CPU의 RAM조차 선형적이지 않을 정도로 선형적인 시스템은 드물다.
  • 이러한 트레이드 오프는 내결함성 대신 성능을 선택했기 떄문이다.
  • 여러 분산 데이터 베이스도 마찬가지이다.

순서화 보장

순서화

  • 단일 리더 복제에서는 팔로워 쓰기 순서를 결정하는게 리더의 목적이였음
  • 트랜잭션의 직렬성은 어떤 순서에 따라 실행됨을 보장하는 것
  • 분산 시스템의 타임스탬프, 시계 사용은 시간을 통해 순서를 결정하기 위한 방법들 → 순서화, 선형성, 합의 사이에 연관 관계를 통해 시스템이 무엇을 하고 할 수 없는지 이해할 수 있음

순서화와 인과성

인과성

→ 결과가 나타나기 전에 원인이 발생한다.

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

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

  • 선형성은 인과성을 내포하기에 선형적 시스템은 인과성도 올바르게 유지함
  • 다만 성능, 가용성에 해가 될 수 있음 (네트워크 지연 등으로 인한)
  • 절충을 통해 성능 손해 없이 인과적 일관성을 만족 시킬 수 있고, 연구 중

인과적 의존성 담기 (비선형성 시스템)

  • 두 연산의 인과관계를 파악해야 함
  • 데이터베이스는 애플리케이션이 어떤 데이터 버전을 읽었는지 확인

일련번호 순서화

  • 모든 인과적 의존성을 추적하는건 실용성이 떨어지고, 오버헤드가 큼
  • 따라서 일련번호, 타임스탬프로 이벤트 순서를 정하는 방법이 있음
  • 크기가 작고, 고유해서 전체 순서를 알 수 있음

비인과적 일련번호 생성기

노드 별 일련번호

  • 각 노드 개별로 독립적인 일련번호 집합 생성
  • ex) node A = {2, 4, 6, 8..} , node B = {1, 3, 5, 7…}
  • but, 노드마다 초당 연산 수가 다를 수 있음

고해상도 타임스탬프

  • 각 연산에 일 기준 시계의 타임스탬프를 붙임
  • ex) X = 40.00001, Y = 40.00003
  • but, 노드 간 타임스탬프 불일치

노드에 일련번호 블록 할당

  • 일련번호들을 노드에 미리 할당
  • ex) node A = {1 ~ 1000}, node B = {1001 ~ 2000}
  • but, 블록 일련번호 간 일관성 불일치

잘 동작하나, 인과성에 일관적이지 않음

램포트 타임스탬프

  • 인과성에 일관적인 일련번호를 생성하는 간단한 방법
  • 현재 분산 시스템 분야에서 가장 많이 인용된 논문에 나온 방법
  • 각 노드는 처리한 연산의 갯수인 카운터, 타임스탬프를 가지는 노드 ID 를 가짐
  • 두 타임스탬프에서 카운터가 큰 것이, 그리고 노드 ID가 큰 값이 타임 스탬프가 큼

그림 9-8
그림 9-8. 컴포트 타임스탬프는 인과성에 일관적인 전체 순서화를 제공한다

→ 모든 노드, 클라이언트는 최댓값을 가지는 카운터를 추적하고 요청, 응답에 따라 최댓값 갱신

타임스탬프 순서화로는 불충분

일관적 인과성을 갖는 연산의 전체 순서를 정의하지만 분산 시스템의 공통 문제를 해결하는데 불충분 연산들을 사후에 결정하는게 아닌, 요청을 받는 즉시 결정해야 할 때 문제가 생김 → 연산의 전체 순서를 모든 연산을 모은 후에 결정되는 것이 원인 전체 순서를 언제 확정할 것인지 전체 순서 브로드캐스트를 통해 다룸

전체 순서 브로드캐스트

(* 참고 : 전체 순서, 부분 순서)

전체 순서 브로드캐스트는 노드 사이에 메시지를 교환하는 프로토콜로 기술된다.
비공식적으로 두 가지 안전성 속성을 항상 만족해야 한다.

  • 신뢰성 있는 전달
    • 어떤 메시지도 손실되지 않는다.
    • 메시지가 하나의 노드에 전달되면 모든 노드에 전달되어야 한다.
  • 전체 순서가 정해진 전달
    • 메시지는 모든 노드에 같은 순서로 전달된다.
  • 참고 : https://en.wikipedia.org/wiki/Atomic_broadcast

이 두 속성은 노드나 네트워크 결함이 있어도 항상 만족되어야 한다.
물론 네트워크가 끊긴 순간에는 메시지가 전달되지 못하지만 결국 복구될 것이고 메시지 전송을 계속하여 재시도하면 이를 만족할 수 있을 것이다.
메시지가 전달되는 순서가 곧 시스템에서 연산의 전체 순서를 의미한다.
전체 순서 브로드캐스트의 중요한 측면은 메시지가 전달되는 시점에 순서가 고정된다는 것이다.
후속 메시지가 이미 전달된 경우 그 앞의 순서에 메시지를 끼워넣는 것을 허용하지 않는다.

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

합의 주키퍼나 etcd 같은 합의 서비스는 전체 순서 브로드캐스트를 실제로 구현한다.
전체 순서 브로드캐스트와 합의는 서로 크게 연관되어 있다.

데이터베이스 복제 전체 순서 브로드캐스트는 데이터베이스 복제에도 사용된다.
모든 메시지가 데이터베이스의 쓰기를 나타내고 모든 노드에 메시지가 동일한 순서로 전달된다면 각 노드는 메시지를 받은 순서대로 처리하면 모든 노드가 서로 일관성 있는 상태를 유지할 수 있다.
이 원리를 상태 기계 복제(state machine replication)라 하고 11장에서 이 주제를 다룬다.

직렬성 트랜잭션 직렬성 트랜잭션에도 전체 순서 브로드캐스트가 사용될 수 있다.
‘실제적인 직렬 실행’에서 모든 메시지가 스토어드 프로시저로 실행되는 결정적 트랜잭션을 나타내고 모든 노드가 메시지를 동일한 순서로 처리하면 데이터베이스의 파티션과 복제본은 일관된 상태를 유지한다.

로그 전체 순서 브로드캐스트를 로그를 만드는 데에 사용할 수도 있다.
메시지 전달은 로그에 추가하는 것과 비슷하다.
모든 노드가 같은 메시지를 같은 순서로 전달 받는다.

펜싱 토큰 펜싱 토큰을 제공하는 잠금 서비스를 구현하는 데에도 유용하다.
잠금을 획득하는 모든 요청은 메시지로 로그에 추가된 후 일련번호를 받는다.
일련번호는 단조증가하므로 이를 펜싱 토큰으로 사용할 수 있다.

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

선형성 시스템에서도 연산의 전체 순서가 있었다. 전체 순서 브로드캐스트와 선형성은 같은 의미인가? 완전히 같다고 볼 수 없으나 두 개념은 서로 밀접한 관계가 있다.

비동기 vs 최신성 보장 전체 순서 브로드캐스트는 비동기식이다. 메시지는 고정된 순서로 신뢰성 있게 전달되는 것을 보장한다.
그러나 언제 메시지가 전달될 지는 보장하지 않는다.
선형성은 최신성 보장이다. 읽기가 최근에 쓰여진 값을 보는 것이 보장된다.

전체 순서 브로드캐스트로 선형성 저장소 구현하기 전체 순서 브로드캐스트 구현을 기반으로 선형성 저장소를 만들 수 있다.
사용자명으로 사용자 계정을 유일하게 식별하도록 보장하는 경우를 보자.

사용자명의 획득은 원자적 compare-and-set(이하 cas) 연산을 통해 이루어진다.
연산 실행 시 값이 null(누구도 점유하지 않은 사용자명)인 경우 cas 연산이 성공한다.
이미 할당된 경우라면 선형성에 의해 null이 아닌 값을 조회할 것이고, cas 연산이 실패한다.
전체 순서 브로드캐스트를 추가 전용 로그로 사용하여 선형성 cas 연산을 다음과 같이 구현할 수 있다.

  • 메시지를 로그에 추가하여 점유를 원하는 사용자명을 가리킨다.
  • 로그를 읽고, 추가한 메시지가 되돌아오기를 기다린다.
  • 원하는 사용자명을 점유하려는 메시지가 있는지 확인한다.
  • 첫 번째 메시지가 자신의 메시지라면 점유에 성공한다.
  • 그러나 첫 번째 메시지가 다른 사용자의 것이라면 연산을 취소한다.

로그 항목은 모든 노드에 같은 순서로 전달된다.
모든 노드가 어떤 쓰기가 먼저 실행된 것인지 동의한다.
충돌하는 쓰기 중 첫 번째 것을 승자로 하고 나머지는 어보트된다.
이러한 절차는 선형성 쓰기를 보장하지만 선형성 읽기는 보장하지 않는다.
각 노드는 로그로부터 비동기로 쓰기 내역이 갱신되기 때문이다. 그래서 오래된 값을 읽을 수 있다.
그래서 이 절차는 엄밀히 말하면 선형성이 아닌, (그보다 조금 약한) 순차적 일관성 또는 타임라인 일관성 보장을 제공한다.

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

반대로 선형성 저장소를 기반으로 전체 순서 브로드캐스트를 구현하는 것도 가능하다.
가장 쉬운 방법은 원자적 increment-and-get(이하 iag) 연산을 사용하는 것이다. (원자적 compare-and-set 연산을 사용해도 된다)
알고리즘은 다음과 같다.

  • 메시지에 선형성 정수로 iag 연산을 수행하고 얻은 값을 일련번호로 포함한다.
  • 그 후 메시지를 모든 노드에 전송한다.
  • 수신자들은 일련번호 순서대로 메시지를 전달한다.

선형성 레지스터로 얻은 숫자는 틈이 없는 순열을 형성한다.
따라서 어떤 노드가 메시지 4를 전달하고 6을 받았다면 6을 전달하기 전에 5를 기다려야 한다는 것을 알 수 있다.
반면 램포트 타임스탬프에서는 그렇지 않았다. 이것이 전체 순서 브로드캐스트와 타임스탬프 순서화의 핵심적인 차이다.

원자적 iag 연산을 만드는 것은 쉬워보인다. 결함이 발생하지 않으면 실제로 쉽다.
그러나 노드가 죽거나 네트워크 연결이 끊긴 상황 등을 고려하면 쉬운 문제가 아니다.
선형성 일련번호 생성기에 대해 고민하다보면 필연적으로 합의 알고리즘에 도달한다.
선형성 iag(또는 cas) 레지스터와 전체 순서 브로드캐스트는 합의와 동등하다. 이 문제 중 하나를 해결할 수 있으면 다른 문제도 해결할 수 있다는 것이다.

분산 트랜잭션과 합의

합의의 목적

  • 여러 노드들이 무언가에 동의하게 만드는 것

노드가 동의하는 것이 중요한 상황

  • 리더 선출
    • 단일 리더 복제의 경우 리더가 어떤 노드인지 합의가 필요
    • 스플릿 브레인 현상을 방지
  • 분산 트랜잭션의 원자적 커밋
    • 여러 노드나 파티션에 걸친 트랜잭션이 노드 별로 성공, 실패의 결과가 다를 수 있음
    • 성공이든 실패든 모든 노드가 트랜잭션의 하나의 결과에 동의해야 함
    • 트랜잭션의 원자성 유지를 위함

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

단일 노드에서 분산 원자적 커밋으로 단일 노드에서 커밋은 어떻게 결정되는가?
디스크에 있는 트랜잭션 로그에 커밋 레코드가 추가되어야 커밋이 된다.
이후 데이터베이스가 죽더라도 디스크에 기록된 로그로 트랜잭션이 커밋된 것을 알 수 있다.
노드에 부착된 특정 디스크 드라이브의 컨트롤러가 커밋을 원자적으로 만든다.

여러 노드가 관여하는 경우는 이야기가 다르다.
예를 들면, 다음과 같은 경우다.

  • 파티셔닝된 데이터베이스에 다중 객체 트랜잭션을 사용
  • 보조 색인을 사용하는 경우. 주 색인과 보조 색인이 서로 다른 노드에 존재한다. 각 노드에서 독립적으로 트랜잭션을 커밋 하면 어떤 노드는 성공, 어떤 노드는 실패할 가능성이 다분하다.
    이는 부분적인 성공, 실패를 의미하므로 원자성을 위반하며 이를 허용하면 노드 간의 일관성도 없어진다.

일단 커밋 후 문제가 있으면 취소하면 되지 않을까? 그러나 직후 발생한 또 다른 트랜잭션에서 커밋된 데이터에 의존할 가능성이 있다.
이러한 트랜잭션도 모두 취소 되어야 한다. 그래서 커밋 후 취소하는 방법은 적절하지 않다.
따라서 트랜잭션 커밋은 되돌릴 수 없다.
노드가 트랜잭션을 커밋 하려면 다른 노드에서도 이 트랜잭션이 커밋되리라는 확신을 갖고 있어야 한다.

2단계 커밋(2PC, 2-phase commit) 소개

2단계 커밋은 여러 노드에 걸친 원자적 트랜잭션 커밋을 달성하는 알고리즘이다.

그림 9-9
그림 9-9. 2단계 커밋(2PC)의 성공적인 실행

코디네이터와 참여자

  • 코디네이터
    • 트랜잭션 관리자라고도 한다.
    • 애플리케이션 프로세스 내에서 라이브러리 형태로 구현되거나
    • 분리된 프로세스 또는 서비스로 제공될 수 있다.
  • 참여자
    • 트랜잭션을 커밋하는 각 데이터베이스 노드

2단계 커밋의 절차

  • 애플리케이션이 여러 노드에 데이터를 기록한다.
  • 애플리케이션이 커밋할 준비가 되면 코디네이터가 1단계를 시작한다. 각 노드에 준비 요청을 보낸다.
  • 모든 참여자가 커밋 준비가 완료되면 2단계에서 커밋 요청을 한다.
  • 커밋 준비가 되지 않은 참여자가 있다면 2단계에서 어보트 요청을 한다.

약속에 관한 시스템

위에서 설명한 내용 만으로는 2단계 커밋이 원자성을 어떻게 보장하는 지 명확하지 않다.

준비 요청과 커밋 요청은 2단계에서 손실될 가능성이 있기 때문이다.

과정을 더 자세히 보자.

  • 애플리케이션은 분산 트랜잭션을 시작하기를 원할 때 코디네이터에게 트랜잭션 ID를 요청한다. 이 트랜잭션 ID는 전역적으로 유일하다.
  • 애플리케이션은 발급받은 트랜잭션 ID를 가지고 각 참여자에서 단일 노드 트랜잭션을 시작한다. 읽기와 쓰기는 이 과정에서 실행된다.
    • 이 단계에서 노드가 죽거나 타임아웃 등의 문제가 발생하면 코디네이터나 참여자 중 누군가 어보트할 수 있다.
  • 이후 코디네이터가 준비 요청을 한다. 참여자가 준비 요청을 받으면 트랜잭션 커밋이 가능한지 점검한다.
    • 가능하다고 판단이 되면 코디네이터에게 ‘네’라고 응답을 한다.
    • 이 응답은 이후 어떤 상황(노드가 죽거나, 디스크 공간이 부족한 경우 등)에서도 절대 번복될 수 없다.
  • 코디네이터가 준비 요청에 대한 응답을 받은 후, 트랜잭션을 커밋할지 어보트 할지 최종적으로 결정한다.
    • 모든 참여자가 ‘네’라고 응답해야 커밋으로 결정한다.
    • 코디네이터가 추후 죽을 수도 있으므로 결정을 디스크의 트랜잭션 로그에 기록한다. 이를 커밋 포인트라 한다.
    • 완료된 결정은 절대 되돌릴 수 없다.
  • 코디네이터가 최종 결정을 완료하였으니, 모든 참여자에게 커밋 또는 어보트 요청을 전송한다.
    • 이 요청이 실패하거나 타임아웃이 발생하면 성공할 때 까지 영원히 재시도한다.
    • 참여자가 죽은 경우라면 복구된 후에 커밋을 요청한다.

트랜잭션 커밋을 결정하는 참여자의 응답과 코디네이터의 결정은 번복될 수 없다.

이러한 약속이 2PC의 원자성을 보장한다.

코디네이터 장애

앞에서 2PC 과정 중 문제가 발생하면 어떻게 진행되는 지 설명했다.

  • 1단계에서 문제가 발생하면 코디네이터가 트랜잭션을 어보트한다.
  • 2단계에서 문제가 발생하면 코디네이터가 요청을 무한히 재시도한다.

그런데 코디네이터가 죽으면 어떻게 되는가?
코디네이터가 준비 요청을 보내기 전에 장애가 발생하면 참여자가 트랜잭션을 어보트할 수 있다.
그런데 준비 요청에 ‘네’라고 대답을 보낸 이후는 참여자가 일방적으로 어보트할 수 없다.
코디네이터로부터 2단계 요청을 받을 때 까지 기다려야 한다.
이런 상태의 트랜잭션을 의심스럽다 또는 불확실하다고 한다.

그림 9-10
그림 9-10. 참여자들이 “네"라고 투표한 후 코디네이터가 죽는다. 데이터베이스 1은 커밋할지 어보트할지 알지 못한다.

데이터베이스1은 코디네이터의 커밋 요청을 대기하고 있다.
이러한 경우 코디네이터가 복구되는 것을 기다릴 수 밖에 없다.
다행이 코디네이터는 요청을 보내기 전에 커밋 로그를 기록했다.
코디네이터가 복구되면 트랜잭션 로그를 읽어 의심스러운 트랜잭션들의 상태를 결정한다.
커밋 레코드가 없는 트랜잭션은 어보트된다. 있다면 커밋 요청을 재시도할 것이고 2단계 과정도 완료될 것이다.
2단계 커밋은 코디네이터가 복구될 때 까지 중단되므로 블로킹 원자적 커밋 프로토콜이라 한다.

3단계 커밋

3단계 커밋

  • 2PC의 대안으로 제안된 알고리즘
  • 지연에 제한이 있는 네트워크, 응답 시간에 제한이 있는 노드를 전제로 한다. (→ 현실과 동떨어짐)
  • 따라서 현실적인 분산 시스템에서 3PC를 적용하기는 어렵다.

현실의 분산 트랜잭션

분산 트랜잭션에 대한 엇갈린 평판

긍정적

  • 분산 시스템에서 원자적 커밋을 달성하여 안전성 보장을 제공

부정적

  • 운영상의 문제 유발
  • 성능 이슈의 원인
    • 장애 복구에 필요한 부가적인 디스크 강제 쓰기(fsync), 부가적인 네트워크 왕복 시간을 강요
    • ex. MySQL : 분산 트랜잭션이 단일 노드 트랜잭션보다 10배 이상 느리다.
  • 분산 트랜잭션이 제공해주는 이점에 비해 비용이 너무 큰 것 아닌가?

두 가지 종류의 분산 트랜잭션

데이터베이스 내부 분산 트랜잭션

  • 분산 시스템(복제, 파티셔닝)을 지원하는 데이터베이스는 노드 사이의 내부 트랜잭션을 지원
  • 트랜잭션에 참여하는 모든 노드는 동일한 데이터베이스 소프트웨어를 실행한다.
  • 다른 시스템과 호환될 필요가 없어 프로토콜 선택, 구현이 자유롭고 최적화가 가능하다.

이종 분산 트랜잭션

  • 참여자들이 각기 다른 기술을 기반으로 하는 경우
  • 두 가지 서로 다른 벤더의 데이터베이스가 참여하는 경우
  • 메시지 브로커와 같은 비데이터베이스 시스템이 참여하는 경우
  • 서로 다른 시스템 간의 원자적 커밋을 보장해야 함
  • 다양한 시스템들이 강력한 방법으로 통합될 수 있게 한다.

이종 분산 트랜잭션의 절차와 제약
이종 분산 트랜잭션의 한 가지 예시를 살펴보자.

예시 : 메시지 브로커와 메시지를 처리하는 데이터베이스
메시지 브로커에서 메시지를 데이터베이스로 전송하고, 데이터베이스가 이를 처리하는 상황이다.
분산 트랜잭션의 커밋과 어보트는 다음과 같이 진행되어야 한다.

  • 커밋
    • 메시지 브로커에서 데이터베이스로 메시지 전달이 성공
    • 메시지를 처리하는 데이터베이스 트랜잭션이 성공
    • 이 두 과정이 모두 성공해야 분산 트랜잭션을 커밋한다.
  • 어보트
    • 위 커밋의 두 과정 중 하나라도 실패하면 어보트해야 한다.
    • 진행 과정에서 발생한 부수 효과도 모두 폐기되어야 한다.
    • 이렇게 함으로써 모든 메시지는 결과적으로 한 번만 처리되도록 보장할 수 있다.

이종 분산 트랜잭션의 제약
트랜잭션의 영향을 받는 시스템이 모두 동일한 원자적 커밋 프로토콜을 사용할 수 있어야 한다.

  • 메시지 처리가 이메일을 전송하는 부수 효과를 유발하는 경우 이메일 서버도 2단계 커밋을 지원해야 한다.
  • 지원하지 않으면 트랜잭션 재시도 과정에서 이메일이 두 번이상 전송될 수 있음
  • 트랜잭션 어보트 시 부수효과도 같이 폐기되어야 안전한 재시도를 보장할 수 있다.

XA 트랜잭션

XA

  • X/Open XA(eXtended Architecture)
  • 이종 분산 트랜잭션을 2단계 커밋으로 구현하는 표준
  • 여러 관계형 데이터베이스와 메시지 브로커에서 지원된다.
  • 트랜잭션 코디네이터와 연결되는 인터페이스를 제공하는 C API
    • 네트워크 프로토콜이 아니다.
    • 자바의 경우 JTA(Java Transaction API)로 XA를 구현

드라이버

  • 애플리케이션이 네트워크 드라이버나 클라이언트 라이브러리를 사용해 데이터베이스, 메시징 서비스와 통신한다고 가정한다.
  • 드라이버가 XA를 지원한다면 연산이 분산 트랜잭션에 속하는 지를 판단하기 위해 XA API를 호출한다. 분산 트랜잭션인 경우 데이터베이스 서버로 필요한 정보를 보낸다.
  • 추가로 드라이버는 코디네이터가 준비, 커밋, 어보트 요청을 할 수 있도록 코디네이터에게 콜백을 제공한다.

코디네이터

  • XA API를 구현한다.
  • 보통 트랜잭션을 시작하는 애플리케이션과 같은 프로세스에 로딩되는 단순한 라이브러리
    • 참여자에게 요청은 드라이버의 콜백을 통해 전송한다.
    • 결정을 기록하기 위해 로컬 디스크의 로그를 사용한다.

복구 : 애플리케이션 프로세스가 죽거나 장비가 죽는 경우

  • 코디네이터도 같이 죽고 몇몇 트랜잭션들이 의심스러운 상태가 된다.
  • 복구 과정은 이전의 2PC와 동일하다.

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

트랜잭션이 의심스러운 상태에 있는 동안 트랜잭션과 관련된 객체에 잠금이 설정될 수 있다.
잠금은 트랜잭션이 커밋, 어보트 되기 전까지 해제가 불가능하다. (그림 9-9의 음영 처리 영역 참고)
코디네이터의 로그가 손실된 경우 관리자가 수동으로 잠금을 해제해야 하는 경우도 있다.
잠금이 유지되면 다른 트랜잭션 실행에 지장을 초래하여 애플리케이션에도 치명적이다.

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

고아가 된 의심스러운 트랜잭션

이론과 달리 현실에서는 코디네이터가 복구 과정에서 결과를 결정할 수 없는 트랜잭션이 존재

  • 트랜잭션 로그가 손실된 경우
  • 소프트웨어 버그

이런 트랜잭션에 의한 잠금은 자동으로 해소되지 않아 관리자의 개입을 필요로 한다.

  • 데이터베이스 서버 재부팅을 해도 잠금이 유지된다.
  • 잠금이 자동으로 해소되면 원자성이 깨질 위험이 있기 때문
  • 관리자가 참여자의 상태를 조사하고 커밋, 롤백을 수동으로 결정한다.

경험적 결정(heuristic decision) 관리자가 개입하여 해결하는 것은 많은 수작업을 요구하고 (심각한 상황이므로) 높은 스트레스를 유발한다.
그래서 여러 XA 구현에서 참여자가 코디네이터의 결정 없이 트랜잭션의 커밋 여부를 일방적으로 결정할 수 있도록 하는 경험적 결정이라는 비상 탈출구를 제공한다.
그런데 이는 2PC의 약속을 깨뜨리기 때문에 원자성을 위반할 위험이 있다.
따라서 경험적 결정은 평상시에 사용하는 것이 아닌, 큰 장애 상황을 벗어나기 위한 용도로 의도된 것이다.

분산 트랜잭션의 제약

XA 트랜잭션은 여러 참여 데이터 시스템이 서로 일관성을 유지하는 문제를 해결해준다.
그러나 다음과 같은 운영상 문제를 유발한다.

코디네이터가 전체 시스템의 단일 장애점(single point of failure)

  • 코디네이터가 복제되지 않고 단일 장비에서만 실행되면 전체 시스템의 단일 장애점이 된다.
  • 코디네이터에 장애가 생기면 결과적으로 애플리케이션 전체에 영향을 주기 때문
  • 그럼에도 불구하고 여러 코디네이터 구현은 고가용성을 제공하지 않거나 기본적인 복제만 지원

애플리케이션 서버가 상태를 갖게(stateful) 된다

  • 서버 애플리케이션은 영속적인 상태를 데이터베이스에 저장한다.
  • 이는 서버가 무상태성(stateless)이어야 확장성에 유리하기 때문
  • 코디네이터가 애플리케이션 서버의 일부가 되면 트랜잭션 로그가 서버의 로컬 디스크에 저장된다.
  • 따라서 서버가 상태를 갖게 되는 문제가 발생한다.

XA에서 제공할 수 있는 기능에도 제약이 존재

  • XA는 여러 데이터 시스템과 호환되는 만큼 공통 분모에 해당하는 기능만 제공할 수 있음
  • 여러 시스템에 걸친 교착상태를 감지할 수 없음
    • 이러한 기능을 제공하려면 서로 다른 시스템이 각 트랜잭션이 대기중인 잠금에 대한 정보를 교환할 수 있는 프로토콜을 필요로 함
  • 직렬성 스냅숏 격리(SSI)를 지원하지 못함
    • SSI를 지원하려면 여러 시스템에 걸친 충돌을 식별할 수 있는 프로토콜이 필요

시스템이 결함에 취약해지는 문제 XA가 아닌 데이터베이스 내부 분산 트랜잭션은 제약이 그리 크지 않다.
가령 분산 환경에서의 SSI를 지원한다. 2PC가 성공적으로 트랜잭션을 커밋하려면 모든 참여자가 응답해야 한다.
즉, 시스템의 어떤 부분이라도 고장이 나면 트랜잭션이 실패한다.
따라서 분산 트랜잭션은 장애를 증폭시키는 경향이 있어 시스템이 내결함성을 보장하지 못하게 한다.

내결함성을 지닌 합의

비공식적으로 합의는 여러 노드가 어떤 것에 동의하는 것을 의미한다.
여러 사용자가 동시에 동일한 좌석 예약을 하는 경우 처럼 공존할 수 없는 연산들 중 어떤 것이 승자가 되는 지를 결정하기 위해 합의 알고리즘을 사용할 수 있다.
하나 또는 그 이상의 노드들이 값을 제안하고 합의 알고리즘이 값 중 하나를 결정한다.

합의 알고리즘의 속성

이 때 합의 알고리즘은 다음 속성을 만족해야 한다.

  • 균일한 동의 : 어떤 두 노드도 다르게 결정하지 않는다.
  • 무결성 : 어떤 노드도 두 번 결정하지 않는다.
  • 유효성 : 한 노드가 값 v를 결정한다면 v는 어떤 노드에서 제안된 것이다.
  • 종료 : 죽지 않은 노드는 결국 어떤 값을 결정한다.

균일한 동의, 무결성, 유효성 균일한 동의와 무결성 속성은 합의의 핵심 아이디어를 정의한다.
모두 같은 결과로 결정하며 한 번 결정하면 결정을 번복 할 수 없다.
유효성 속성은 뻔한 해결책을 배제하기 위해 존재한다.
가령 무엇을 제안하든 상관 없이 항상 null로 결정하는 알고리즘도 있을 수 있다.
이러한 알고리즘은 균일한 동의와 무결성을 만족하지만 유효성을 만족하지는 못한다.

종료 종료는 내결함성과 관련된 속성이다.
내결함성을 만족시킬 필요가 없으면 종료를 제외한 세 개의 속성을 만족하는 것은 간단하다.
한 노드를 ‘독재자’로 결정하여 그 노드가 모든 결정을 내리게 하면 된다.
그러나 ‘독재가’ 노드에 장애가 발생하면 시스템은 어떤 결정도 내릴 수 없다.
2단계 커밋에서 코디네이터가 죽는 경우와도 같다.

종료 속성에 의해 합의 알고리즘에서 합의는 중단될 수 없다. 반드시 진행되어야 한다.
일부 노드에 장애가 발생해도 나머지 노드는 반드시 결정을 내려야 한다.
종료는 활동성 속성이고 다른 세 개는 안전성 속성이다.

합의 알고리즘의 가정

죽은 노드는 돌아오지 않는다
합의 시스템 모델은 어떤 노드가 죽는 경우 그 노드가 돌아오지 않는다고 가정한다.
지진과 같은 자연재해로 인해 노드가 파괴되는 경우를 고려하면 노드가 돌아온다고 가정할 수 없다.
이런 상황에서 노드가 복구되기를 기다리는 알고리즘은 절대로 종료 속성을 만족할 수 없다.
가령 코디네이터의 복구를 전제로 하는 2PC는 종료 속성을 만족하지 못한다.

과반수의 노드는 존재해야 한다 모든 노드가 죽으면 결정 자체가 불가능하다.
어떤 합의 알고리즘이든 노드의 과반수 이상이 올바르게 동작해야 합의를 달성할 수 있다.
과반수의 노드에 장애가 발생하여도 안전성 속성(균일한 동의, 무결성, 유효성)은 항상 만족한다.
결정을 내리진 못하더라도 유효하지 않은 결정을 내려서 합의 시스템을 오염시키지는 않는다.

비잔틴 결함은 없다고 가정한다
(* 참고 : 비잔틴 결함)
비잔틴 결함을 가정하면 합의 알고리즘의 안전성 속성을 위반할 수 있다.

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

내결함성을 지닌 합의 알고리즘의 대다수는 합의의 형식적 모델을 직접 사용하지 않는다.
(합의의 형식적 모델 : 합의의 네 개의 속성을 만족하면서 하나의 값을 제안하고 결정)
대신 값의 순차열에 대해 결정하여 전체 순서 브로드캐스트 알고리즘을 구현한다.
각 노드는 매 회마다 다음에 보낼 메시지를 제안하고, 결정한다.
전체 순서 브로드캐스트는 합의를 여러 번 반복하는 것과 동일하다.

각 합의 결정이 하나의 메시지 전달에 해당한다.

  • 균일한 동의 : 모든 노드는 같은 메시지를 같은 순서로 전달한다.
  • 무결성 : 메시지는 중복되지 않는다.
  • 유효성 : 메시지는 조작되거나, 오염되지 않는다.
  • 종료 : 메시지는 반드시 전달되며, 손실되지 않는다.

단일 리더 복제와 합의

단일 리더 복제는 합의 알고리즘의 속성을 만족하는가?
단일 리더 복제는 모든 쓰기를 리더에서 처리하고 이를 같은 순서로 모든 팔로워에게 적용한다.
본질적으로 전체 순서 브로드캐스트와 동일해보인다. 그런데 단일 리더 복제에서는 합의를 고려하지 않았다.

합의를 고려할 필요가 없었던 것일까? 이는 리더가 어떻게 선택되는 지와 관련이 있다.
만약 운영 측에서 리더를 수동으로 설정한다면 이는 ‘독재자’ 노드가 모든 것을 결정하는 합의와 같다.
이 방식은 합의의 안전성 속성을 만족하지만, 종료 속성을 만족하지 못한다고 앞서 언급했다.

합의를 해결하기 위해 합의가 필요하다? 단일 리더 복제에서 리더가 죽으면 새로운 리더를 선출하는 장애 복구 과정이 수행될 수 있다.
장애 복구가 수행되는 것을 가정하면 단일 리더 복제는 내결함성을 지닌 전체 순서 브로드캐스트와 동일해 보인다.
전체 순서 브로드캐스트와 합의의 동등성에 대해 앞서 언급했다.
내결함성을 지닌 전체 순서 브로드캐스트를 구현하면 합의의 문제를 해결할 수 있어 보인다.

그런데 한 가지 문제가 있다. 리더를 선출하는 과정에서 합의가 필요하다.
이 과정이 잘못되면 둘 이상의 노드가 자신을 리더로 생각하는 스플릿 브레인 현상이 발생할 수 있다.
합의는 전체 순서 브로드캐스트 문제와 동등하고, 전체 순서 브로드캐스트는 단일 리더 복제와 동일해 보인다.
단일 리더 복제는 리더가 필요하다. 리더를 선출하기 위해 리더가 필요하다는 결론으로 이어진다.
합의를 해결하기 위해 합의가 필요하다는 난제에 부딪히게 된다.

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

에포크 번호와 리더 선출
지금까지 설명한 합의 프로토콜은 리더가 유일함을 보장하지는 않는다.
대신에 특정 시점에 진행된 투표에서 선출된 리더가 유일함을 보장하는 것은 가능하다.
이 특정 시점을 구별하기 위해 번호를 매기는데, 이를 에포크 번호라 한다.

현재의 리더가 죽었다고 판단되면 새 리더를 선출하기 위해 노드 사이에서 투표가 시작된다.
이 선출은 에포크 번호를 증가시킨다. 에포크 번호는 전체 순서가 있으며 단조 증가한다.
스스로 리더라 생각하는 둘 이상의 노드에 의견 충돌이 있는 경우 에포크 번호가 높은 리더가 승리한다.

리더는 자신이 유효한 리더임을 어떻게 확신할 수 있나? 리더의 입장에서는 자신보다 에포크 번호가 높은 리더가 있는지 확신할 수 없다.
다른 노드도 자신을 리더로 생각할까? 리더는 결정을 내리기 전에 이를 먼저 확인해야 한다.
리더는 모든 결정 마다 제안한 값에 대해 다른 노드에게 찬반 요청을 보낸다.
요청을 받은 노드는 요청을 보낸 노드보다 에포크 번호가 높은 리더를 알지 못한다면 찬성한다.
그리고 리더는 정족수의 찬성 표를 얻어야 한다.

노드 정족수의 찬성표를 얻으면 결정할 수 있는 것일까? 한 가지 조건이 더 필요하다.
찬성에 투표한 노드 중에서 최소 하나의 노드는 가장 최근에 진행된 리더 선출에 참여했어야 한다.
그 노드가 찬성했다는 것은 에포크 번호가 더 높은 리더가 없다는 것을 의미하고, 따라서 리더 스스로 자신이 여전히 유효한 리더라고 확신할 수 있음을 의미한다.
유효한 리더라고 확신한 이후, 리더는 자신있게 결정을 내릴 수 있다.

2단계 커밋과 내결함성 합의 알고리즘의 차이 방금 살펴본 알고리즘이 내결함성을 지닌 합의 알고리즘의 대표적인 방식이다.
내결함성을 지닌 합의 알고리즘과 2단계 커밋은 다소 비슷해 보이지만 다르다.
2단계 커밋은 모든 참여자로부터 ‘네’라는 응답을 요구한다.
내결함성을 지닌 합의 알고리즘은 노드의 과반수로부터만 투표를 받으면 된다.
그리고 합의 알고리즘은 새로운 리더가 선출된 이후 노드를 일관적인 상태로 만들어주는 복구 과정을 정의한다.
복구 과정을 통해 안전성 속성이 항상 만족되도록 보장한다.
이러한 차이점이 합의 알고리즘의 정확성과 내결함성의 핵심이다.

합의의 제약

합의 알고리즘을 통해 분산 시스템에서 일관성과 내결함성을 모두 달성할 수 있다. 그러나 합의 알고리즘을 유지하기 위한 비용이 적지 않다.

동기식 복제가 강제됨

제안이 결정되기 전에 노드가 제안에 투표하는 과정은 일종의 동기식 복제다.
동기식 복제가 비동기 복제보다 일관성, 지속성 측면에서 안전함에도 불구하고 성능을 이유로 선호되지 않는다.

과반수에 대한 제약

합의 시스템은 항상 엄격한 과반수가 동작하는 것을 요구한다.
노드 한 대의 장애를 견디려면 최소한 세 대의 노드, 두 대의 장애를 견디려면 최소 다섯 대의 노드가 필요하다.
네트워크 장애로 일부 노드가 다른 노드와 연결이 끊기면 과반수의 부분만 동작이 가능하고 나머지는 차단된다.

노드 수 변경에 대한 제약

대부분 합의 알고리즘은 투표에 참여하는 노드 집합이 고정되어 있다고 가정한다.
따라서 클러스터에 노드를 그냥 추가하거나 제거할 수 없다.
합의 알고리즘의 동적 멤버십 확장은 클러스터에 있는 노드 집합이 시간이 지남에 따라 바뀌는 것을 허용하지만 정적 멤버십 알고리즘보다 훨씬 이해하기 어렵다.

네트워크 문제에 민감

합의 시스템은 장애를 감지하기 위해 타임아웃에 의존한다.
타임아웃은 노드의 문제와 네트워크 문제를 구별하지 못해 네트워크 문제 시에도 노드가 죽었다고 판단할 수 있다.
이러한 오류로 인해 리더가 자주 새로 선출되면 시스템이 본연의 일이 아닌 리더를 선출하는 데 더 집중하게 된다.
이러한 현상은 성능 문제로 연결된다.

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

주키퍼나 etcd 같은 프로젝트는 종종 ‘분산 키-값 저장소’나 ‘코디네이션 설정 서비스’라고 설명된다.
주키퍼와 etcd는 메모리에 적재가 가능한 작은 양의 데이터를 보관하도록 설계되었다.
이 소량의 데이터는 내결함성을 지닌 전체 순서 브로드캐스트를 통해 모든 노드에 걸쳐 복제된다.
주키퍼는 구글의 처비(Chubby) 잠금 서비스를 모델로 삼아 전체 순서 브로드캐스트 뿐만 아니라 분산 시스템을 구축 시 유용한 다른 기능들도 구현한다. 그 기능들은 다음과 같다.

선형성 원자적 연산

원자적 compare-and-set 연산을 사용해 잠금을 구현할 수 있다.
여러 노드가 동시에 같은 연산을 수행하려고 하면 그것들 중 하나만 성공한다.
합의 프로토콜은 노드에 장애가 나거나 어느 시점에 네트워크가 끊기더라도 그 연산이 원자적이고 선형적일 것을 보장한다.
분산 잠금을 보통 클라이언트에 장애가 난 경우 결국에는 해제되도록 만료 시간이 있는 임차권(lease)으로 구현된다.

연산의 전체 순서화

어떤 자원이 잠금이나 임차권으로 보호될 때는 프로세스가 중단되는 경우 클라이언트들이 서로 충돌하는 것을 막기 위해 펜싱 토큰이 필요하다.
펜싱 토큰은 잠금을 획득할 때마다 단조 증가하는 어떤 숫자다.
주키퍼는 모든 연산에 전체 순서를 정하고 각 연산에 단조 증가하는 트랜잭션 ID(zxid)와 버전 번호(cversion)를 할당하여 이를 제공한다.

장애 감지

클라이언트는 주키퍼 서버에 수명이 긴 세션을 유지하고 클라이언트와 서버는 주기적으로 하트비트(heartbeat)를 교환해서 다른 쪽이 여전히 살아 있는지 확인한다.
연결이 일시적으로 끊기거나 주키퍼 노드에 장애가 나더라도 세션은 살아있다.
그러나 세션 타임아웃보다 긴 기간 동안 하트비트가 멈추면 주키퍼는 세션이 죽었다고 선언한다.
세션에서 획득한 잠금은 세션이 타임아웃 되었을 때 자동으로 해제되도록 설정할 수 있다.
주키퍼에서는 이를 단명 노드(ephemeral node)라 한다.

변경 알림

클라이언트는 다른 클라이언트가 생성한 잠금과 값을 읽을 수 있을 뿐만 아니라 거기에 변경이 있는지 감시할 수도 있다.
따라서 클라이언트는 다른 클라이언트가 언제 클러스터에 합류했는지 혹은 다른 클라이언트에 장애가 났는지 알아챌 수 있다.
알림을 구독함으로써 클라이언트는 변경을 발견하기 위해 주기적으로 폴링해야 하는 필요를 피할 수 있다.

방금 살펴본 기능들 중 오직 선형성 원자적 연산만 실제로 합의가 필요하다.
그러나 주키퍼를 분산 코디네이션에 매우 유용하게 만들어주는 것은 이러한 기능들의 조합이다.

작업을 노드에 할당하기

주키퍼/처비 모델이 잘 동작하는 경우가 몇 가지 있다. 하나는 여러 개의 프로세스나 서비스가 있고 그 중 하나가 리더 혹은 주 구성요소로 선택되어야 하는 경우다.
리더에 장애가 발생하면 다른 노드 중 하나가 넘겨 받아야 한다. 이는 단일 리더 설정에서 유용하다.
또 다른 경우는 파티셔닝된 자원이 있고 어떤 파티션을 어떤 노드에 할당해야 할 지 결정하는 경우다.
새 노드들이 클러스터에 합류하면서 부하의 재균형화를 위해 어떤 파티션들은 새로운 노드로 이동해야 한다.
혹은 노드가 제거되거나 장애가 나면 다른 노드들이 장애가 난 노드의 작업을 넘겨받아야 한다.

이런 종류의 작업은 주키퍼에서 원자적 연산, 단명 노드, 알림을 신중하게 사용하면 잘 수행할 수 있다.
심지어 사람의 개입이 없이도 애플리케이션이 결함에서 자동으로 복구되도록 할 수 있다.
물론 쉽지는 않지만 합의 알고리즘을 밑바닥 부터 직접 구현하는 방법보다는 훨씬 낫다.

애플리케이션은 처음에는 단일 노드에서 실행될 지 모르지만 이후 수천 대의 노드로 늘어날 수도 있다.
매우 많은 노드에서 과반수 투표를 수행하는 것은 비효율적이다.
주키퍼는 대신 고정된 수의 노드에서 실행되고 이 노드들 사이에서 과반수 투표를 수행한다.
주키퍼는 노드들을 코디네이트 하는 작업의 일부를 외부 서비스에 위탁하는 방법을 제공한다.

서비스 찾기

주키퍼, etcd, 콘술(Consul)은 서비스 찾기 용도로도 자주 사용된다.
(* 서비스 찾기(service discovery) : 특정 서비스에 연결하기 위해 IP 주소를 알아내는 것)
서비스 찾기가 실제로 합의가 필요한 지는 명확하지 않다.
DNS의 경우도 선형성을 보장하지 않지만 이것이 보통 문제가 되지는 않는다.

리더 선출의 경우는 합의가 필요하고, 합의 시스템이 리더가 누구인지를 알면 다른 서비스들이 리더가 누구인지 찾는데 그 정보를 사용하는 것도 타당하다.
어떤 합의 시스템은 읽기 전용 캐시 복제 서버를 지원한다.
이 복제 서버는 합의 알고리즘의 모든 결정에 대한 로그를 비동기로 받지만 능동적으로 투표에 참여하지는 않는다.
이 복제 서버는 선형성을 보장하지 않아도 되는 읽기 요청을 서비스할 수 있다.

멤버십 서비스

주키퍼와 유사 프로젝트들은 오랜 멤버십 서비스 연구 역사의 일부로 볼 수 있다.
멤버십 서비스는 클러스터에서 어떤 노드가 현재 살아있는 멤버인지 결정한다.
장애 감지를 합의와 연결하여 노드들이 어떤 노드가 살아있고 죽었는지에 동의할 수 있다.
물론 기약 없는 네트워크 지연 때문에 노드에 장애가 발생한 것인지 확실히 알 수는 없고, 잘못 판단할 수 있다.
그럼에도 불구하고 합의는 시스템에서 어떤 노드가 현재 멤버십을 구성하는 지 동의하는데 매우 유용하다.




최종 수정 : 2022-04-15