Skip to content
피스타치오는 맛있어
Instagram

9장. 일관성과 합의

, 아키택처, 프로그래밍, 분산시스템, 시스템디자인2 min read

일관성 보장

분산 시스템 환경에서는 여태 살펴보았던 것처럼 어떤 일이든지 벌어질 수 있습니다. 예를 들어 복제 지연 문제가 발생하면 두 노드는 서로 다른 데이터를 가지고 있게 됩니다. 복제 데이터베이스는 대부분 최소한 eventual consistency를 보장합니다. 시간이 지나면 결국 같은 데이터로 수렴하는 것을 기대한다는 의미입니다.

보통은 성능을 위해 이런 일관성을 포기하긴 하지만, 때로는 강력한 일관성이 필요할 때도 있습니다.

선형성

선형성은 바로 그 강력한 일관성 중 하나입니다. 기본 아이디어는 시스템에 데이터 복사본이 하나만 있고 그 데이터를 대상으로 수행하는 모든 연산은 원자적인 것처럼 보이게 만드는 것입니다. A 가 새로운 값을 조회했다면 B 역시 새로운 값을 조회할 수 있어야 하지, 예전 값을 보아선 안된다는 의미입니다. 즉 최신성을 보장(recency guarantee)해야 한다는 뜻입니다.

그럼 선형성은 언제 필요할까요?

잠금과 리더 선출

리더를 선출하는 한 가지 방법은 lock을 잡는 것입니다. 모든 노드가 시작할 때 lock을 잡기 시작하고 성공한 node가 리더가 되는 식입니다. 이렇게 하는 이유는 동시에 여러 리더가 나오는 상황을 방지하기 위해서입니다. 이 때 lock을 구현한 방식이 선형적이지 않다면 단일한 노드를 선출하기 어려워집니다.

제약 조건과 유일성 보장

RDB에서 전형적으로 볼 수 있는 unique constraint 는 선형성이 필요합니다. 두 사람이 같은 이름으로 계정을 생성하려고 하면 둘 중 한 명은 오류를 받아야 합니다. 은행 계좌 잔고가 음수가 되는 것을 방지하거나 창고에 있는 재고보다 더 팔지 못하게 방어하는 것 또한 선형성이 요구됩니다.

채널 간 타이밍 의존성

어떤 파일이 피드에 올라가면 저해상도 이미지로 바꿔 썸네일을 만들어 주는 시스템이 있다고 해봅시다. 이미지는 크기가 크기 떄문에 보통 메시지 큐에 담기지 않고, '이 이미지를 저해상도로 바꿔' 라는 지시가 담긴 메시지만이 메시지 큐에 들어갑니다. 이를 컨슘하는 컨슈머는 이미지를 큐어 들어온 순서대로 파일저장소에서 파일을 읽어와 저해상도로 바꿔 다시 파일저장소에 저장합니다.

이 과정이 선형적이지 않으면 메시지 큐에 들어간 이미지가 썸네일 이미지가 다르게 되는 상황이 발생합니다. 썸네일 이미지가 과거의 이미지를 바라보기 떄문입니다.

선형적 시스템을 구현하기

그럼 선형성이 요구되는 상황에서 어떻게 선형성을 구현해볼 수 있을까요?

우선 시스템의 내결함성에 중요한 복제에 대해 살펴봅시다.

(다이나모 DB 스타일의) 리더 없는 복제 방식을 먼저 살펴보면, 엄격한 정족수(w + r > n)를 사용하더라도 선형성을 위반하는 경우(r번 읽더라도 복제가 완료되지 않은 레플리카에서 조회하는 경우)가 발생합니다. 따라서 리더 없는 복제방식은 선형성을 제공하지 않는다고 봅니다.

다중 리더 복제 방식에서의 선형성에 대해 고민해 봅시다. 다중 리더 복제 방식은 종종 여러 데이터 센터를 운용하는 경우에 쓰입니다. 그러나 이 데이터 센터끼리 연결이 끊기는 경우가 발생합니다. 다중 리더 복제 방식의 경우 이런 네트워크 분단이 있을 경우 문제 없이 잘 동작합니다. 네트워크가 복구되면 계속 큐에 쌓이던 쓰기 로그가 비로소 전달되면서 해결되기 때문입니다. 그러나 단일 리더 복제 방식의 경우 쓰기나 선형적 읽기는 리더 데이터 센터로 향해야 하고, 네트워크 분단시 클라이언트의 선형적인 읽기나 쓰기는 불가능해 중단을 경험합니다(단, 팔로워에게 쿼리하면 뒤쳐진 결과를 받게 됩니다).

선형성을 보장해야 하는 상황이 아니라면, 네트워크 분단같은 문제에 직면해도 요청 받은 서버가 독립적으로 처리해 가용성을 높일 순 있다는 점에서, 선형성과 가용성은 일종의 트레이드 오프 관계인 것처럼 보여집니다.

램포트 타임스탬프

선형성이야기에서 넘어가, 잠시 일관성에 대한 이야기를 해봅니다. 이벤트에 순서를 할당해서 일관성을 보장하려는 시도를 해볼 수 있습니다. 현실에서는 다음 2가지 방식 중 하나가 주로 쓰입니다.

  • 노드마다 독립적인 일련 번호 집합을 가집니다. 예를 들어 노드 1은 홀수, 노드 2는 짝수만 사용해 순서를 할당합니다.
  • 이벤트에 타임스탬프를 할당합니다.

그러나 이 방식들은 분산 환경에선 인과성을 보장할 수 없습니다. 나중에 실행된 이벤트의 로컬 타임스탬프가 더 일찍 찍힐 수도 있고, 3번 이벤트가 2번 이벤트보다 먼저 실행될 수 있기 때문입니다.

이 문제를 해결하기 위해 램포트 타임스탬프라는 개념이 도입되었고, 핵심 아이디어는 다음과 같습니다.

  1. 카운터는 노드에서 요청이 처리될 때마다 1씩 증가시킵니다.
  2. 모든 이벤트에 모든 노드와 모든 클라이언트가 지금까지 본 카운터의 최댓값을 추적하고 모든 요청에 그 최댓값을 포함시킵니다.
  3. 노드가 자신의 카운터 값보다 큰 최대 카운터를 가진 요청이나 응답을 받으면 바로 자신의 카운터를 그 최댓값으로 증가시킵니다.

이를 통해 모든 이벤트는 카운터 값을 달고 처리될 수 있게 되고, 동시 쓰기 충돌이 발생하는 경우 카운터 값이 더 큰 요청을 버림으로써 충돌 해소를 해볼 수 있습니다.

그러나 이 방법 조차 요청의 성공/실패를 당장 결정해야 하는 경우에는 별로 적합하다고 볼 수 없습니다.

© 2023 by 피스타치오는 맛있어. All rights reserved.