Skip to content

10.0 분산 시스템 — 한 머신의 가정이 깨지는 곳

한 프로세스 안에서는 당연했던 세 가지 — 연산은 성공하거나 실패한다, 시계는 하나다, 마지막에 이긴 리더가 진짜 리더다 — 가 네트워크 너머에서는 전부 무너진다. 이 인트로는 그 가정들이 왜, 어떤 조건에서 깨지는지 먼저 세우고, 챕터 10이 부분 실패에서 하나의 합의(10.1)로, 그 합의를 복제와 일관성 보장(10.2)으로 확장하는 경로를 안내한다.

학습 목표

  • 단일 프로세스의 실패·시간·상태 가정이 분산 환경에서 구체적으로 어떻게, 왜 깨지는지 설명한다.
  • 부분 실패(partial failure)를 이분법적 성공·실패가 아니라 "모른다"를 포함한 문제로 재정의한다.
  • 챕터 10의 두 문서가 각각 무엇을 세우는지 — 합의의 조건과 복제의 보장 수준 — 파악하고 학습 순서를 정한다.
  • 재시도·장애조치·읽기 일관성처럼 실무에서 매일 내리는 판단이 이 챕터의 어느 개념에 근거하는지 연결한다.

출발점: 재시도했더니 결제가 두 번 됐다

결제 API가 5초 안에 응답하지 않아 클라이언트가 타임아웃을 판단하고 같은 요청을 재시도했다. 재시도는 성공했다. 그런데 다음 날 정산에서 같은 주문에 결제가 두 번 청구된 것이 발견됐다. 원래 요청은 실패한 게 아니라 서버에서 이미 처리를 끝내고 응답만 돌아오지 못한 것이었다.

이 사고를 단일 프로세스의 사고방식으로 다시 보자. 함수 호출이 5초 동안 응답하지 않으면 스택이 어딘가에 멈춰 있다는 뜻이고, 강제 종료하면 그 호출은 확실히 없었던 일이 된다. 원격 호출에는 이 확실함이 없다. 요청이 네트워크에서 유실됐을 수도, 서버가 처리를 끝내고 응답이 유실됐을 수도, 서버가 처리 도중 죽었을 수도 있다. 클라이언트가 관찰하는 신호 — 타임아웃 — 는 이 세 경우 모두에서 동일하다.

text
클라이언트                       서버
   │──── 요청 ────────────────▶│
   │                            │ 처리 완료, 응답 전송
   │◀─── 응답(유실) ───────────│

   │  5초 경과, 타임아웃
   │  "실패했다" ← 이 판단이 틀렸다

   │──── 재시도 ───────────────▶│
   │                            │ 같은 결제를 다시 처리
   │◀─── 응답(성공) ────────────│

타임아웃은 실패의 증거가 아니라 결정을 모른다는 관찰이다. 이 문장이 챕터 10 전체의 출발점이다.

단일 머신의 가정이 깨지는 세 지점

경력 개발자가 코드를 옮기며 무의식중에 가지고 오는 가정이 있다. 분산 환경에서 그 가정이 구체적으로 어디서, 왜 깨지는지 정리하면 이 챕터가 세울 모델의 지도가 된다.

단일 머신에서의 표현분산 환경에서 확인해야 할 계약
"호출은 성공하거나 실패한다"응답이 없으면 요청·처리·응답 중 어디서 끊겼는지 알 수 없다. 재시도에는 멱등성이 필요하다.
"지금이 몇 시인지는 명확하다"노드마다 물리 시계가 어긋나고(clock skew), 어긋난 정도조차 실시간으로 알기 어렵다. 순서는 시계가 아니라 관찰된 인과관계로 정의해야 한다.
"요청을 보내면 유한한 시간 안에 답이 온다"비동기 네트워크에서는 지연의 상한이 없다. 이 가정 없이는 "결정을 내렸는지 항상 알아낼 수 있는 알고리즘"이 존재하지 않는다는 것이 증명된 결과다(FLP 불가능성).
"리더나 소유자는 하나다"네트워크 분단 동안 옛 리더가 스스로를 여전히 리더라고 믿으며 계속 쓰기를 받을 수 있다(split-brain). 새 리더를 세우는 것만으로는 부족하고 옛 리더를 막아야(fencing) 한다.
"복제본은 원본과 같다"복제는 전파에 시간이 걸리는 프로세스이며, 복제본은 항상 과거 어느 시점의 상태를 반영한다. "얼마나 오래된 상태까지 허용하는가"가 설계 결정이 된다.

이 표의 왼쪽 열은 각각 챕터 9가 세운 네트워크 계층(연결·지연·손실)과 챕터 8a가 세운 동시성 계층(공유 메모리 위의 race) 위에서 성립했던 가정이다. 분산 시스템은 공유 메모리도, 전역 시계도 없는 여러 프로세스가 메시지만으로 하나의 일관된 상태를 유지해야 하는 문제이고, 이 챕터는 그 문제를 다루는 최소한의 개념 장비를 세운다.

이 지식이 필요한 세 가지 순간

1. 재시도와 타임아웃을 설계할 때

타임아웃이 실패를 뜻하지 않는다는 것을 받아들이면 질문이 바뀐다. "재시도해도 되는가"가 아니라 "같은 요청이 두 번 도착해도 결과가 같은가(멱등성)"를 먼저 묻게 된다. 이 판단 기준이 없으면 재시도는 안전장치가 아니라 새로운 버그의 원천이 된다.

2. 리더 장애조치와 split-brain을 진단할 때

"새 리더를 뽑았는데 왜 데이터가 갈렸는가" 같은 질문에는 리더 선출과 fencing을 구분하는 모델이 필요하다. 옛 리더가 네트워크 분단 반대편에서 계속 쓰기를 받는 것은 버그가 아니라 fencing 없는 장애조치의 기본 동작이다.

3. 일관성 모델을 선택할 때

"이 시스템은 최종적 일관성이라 괜찮다"는 문장은 그 자체로 판단이 아니다. 어떤 이상 현상(오래된 읽기, 쓰기 소실, 순서 역전)을 허용하고 어떤 것을 막는지 스펙트럼 위에서 짚을 수 있어야 트레이드오프를 판단할 수 있다.

이 챕터에서 세울 두 가지 모델

10.1 시간, 순서, 합의

부분 실패 앞에서 여러 노드가 어떻게 하나의 결정에 도달하는가?

부분 실패를 성공·실패·모름의 세 상태로 재정의하고, 물리 시계 대신 논리 시계(Lamport, vector clock)로 순서를 세운다. FLP 불가능성으로 비동기 합의의 근본적 한계를 확인한 뒤, 그 한계를 실무에서 우회하는 Raft와 Paxos의 안전성·가용성 조건을 추적한다.

10.2 복제와 일관성

하나의 결정을 여러 복제본에, 언제, 어떤 보장으로 반영할 것인가?

단일 리더·복수 리더·리더 없는 복제 전략을 안전성의 원천과 가용성 특성으로 비교하고, linearizability부터 eventual consistency까지 일관성 모델의 스펙트럼을 세운다. 네트워크 분단을 전제로 CAP 정리의 정확한 조건과 흔한 오독을 구분한다.

10.1이 세우는 합의는 10.2가 다루는 단일 리더 복제의 안전성 원천이 되므로, 순서대로 읽는 것을 권장한다.

두 문서는 이 커리큘럼의 다른 층과 연결된다. 아래로는 챕터 8a의 happens-before가 논리 시계의 원형을, 챕터 9의 신뢰성 없는 전송이 부분 실패가 관찰되는 물리적 층을 제공한다. 위로는 챕터 11의 데이터베이스 복제·샤딩·분산 트랜잭션이 이 챕터의 이론을 로그 위치·failover·commit 프로토콜이라는 구체적 계약으로 적용하고, 챕터 13의 아키텍처 결정챕터 14의 신뢰성 목표가 이 트레이드오프를 팀의 의사결정과 SLO로 옮긴다.

이 챕터를 읽는 방법

  1. "실패했다"를 "모른다"로 바꿔 읽는다. 타임아웃, 응답 없음, 연결 끊김을 만나면 먼저 세 가지 가능성(요청 유실, 처리 후 응답 유실, 처리 중 중단)을 구분한다.
  2. 시간을 순서의 근거로 쓰지 않는다. "이게 나중에 일어났다"는 문장은 관찰자가 누구인지, 어떤 인과관계로 알았는지를 먼저 확인한 뒤에야 성립한다.
  3. 안전성(safety)과 진행(liveness)을 분리해서 판단한다. "틀린 결정을 내리지 않는다"와 "언젠가는 결정을 내린다"는 서로 다른 보장이고, 분산 시스템은 흔히 후자를 희생해 전자를 지킨다.
  4. 일관성 모델은 이름이 아니라 허용하는 이상 현상으로 판단한다. "최종적 일관성"이라는 이름보다 "어떤 상황에서 어떤 값을 볼 수 있는가"가 실제 계약이다.

정리

  • 분산 환경에서는 부분 실패, 시계의 어긋남, 무한한 지연이 단일 프로세스의 가정을 무너뜨리며, 타임아웃은 실패 판정이 아니라 결정을 모른다는 관찰이다.
  • 10.1은 부분 실패·논리 시계·FLP 불가능성을 세운 뒤 그 한계 안에서 합의(Raft·Paxos)가 어떻게 안전성을 지키는지 다룬다.
  • 10.2는 합의를 복제 전략과 일관성 모델의 스펙트럼으로 확장하고, CAP 정리로 네트워크 분단 아래의 근본적 선택을 정리한다.
  • 이 챕터의 개념은 챕터 9의 네트워크 계층과 챕터 8a의 동시성 모델 위에 서 있으며, 챕터 11의 데이터베이스 복제·트랜잭션이 구체적으로 적용하는 이론적 바탕이다.

확인 문제

1. 동료가 "타임아웃이 났으니 결제는 실패한 것으로 보고 재시도해도 된다"고 주장한다. 이 추론의 빈틈을 이 문서의 관점으로 지적하라.

정답과 해설

타임아웃은 요청·처리·응답 중 어느 단계에서 끊겼는지 구분하지 못하는 관찰이며, 처리가 이미 완료됐지만 응답만 유실된 경우를 배제하지 못한다. "실패했다"고 단정하고 재시도하면 처리가 두 번 일어날 수 있다. 안전한 재시도는 실패 단정이 아니라 멱등성(같은 요청 식별자로 재시도해도 결과가 한 번만 반영되도록 서버가 보장하는 것)을 전제해야 한다. 이 설계는 10.1에서 구체화한다.

2. "리더가 죽으면 새 리더를 뽑으면 된다"는 문장이 split-brain을 막기에 왜 충분하지 않은지, 이 문서가 세운 가정 붕괴의 관점으로 설명하라.

정답과 해설

네트워크 분단이 일어나면 옛 리더는 "죽은" 것이 아니라 분단의 반대편에 살아 있으면서 자신이 여전히 리더라고 믿을 수 있다. 새 리더를 뽑는 절차만으로는 옛 리더의 활동을 멈추지 못하므로, 두 리더가 동시에 쓰기를 받아 서로 다른 역사를 만들 수 있다. "리더는 하나"라는 가정이 깨지는 지점이 바로 이것이며, 옛 리더를 명시적으로 무력화하는 fencing이 함께 있어야 안전하다. 자세한 메커니즘은 10.1의 합의10.2의 복제에서 다룬다.

참고 자료