분산 시스템
여러 컴퓨터가 네트워크로 메시지만 주고받으며 하나의 시스템처럼 동작하게 만드는 일 — 장애 모델, 시계와 순서, 복제와 쿼럼, 일관성 모델, 합의와 그 불가능성, 2단계 커밋, CRDT, 비잔틴 합의 — 을 작은 예를 직접 계산한 그림으로 따라간다.
목차 (14)
왜 어려운가: 분산 컴퓨팅의 오류 8가지
분산 시스템은 공유 메모리 없이 메시지로만 협력하는 컴퓨터 묶음이다. 한 대로 충분하지 않아서(데이터가 너무 크거나, 한 대가 죽어도 서비스가 살아 있어야 하거나, 사용자가 전 세계에 흩어져 있어서) 쓰지만, 한 대에서는 생각할 필요가 없던 문제가 한꺼번에 생긴다. 메시지는 늦거나 사라지거나 두 번 오고, 일부만 죽는 부분 장애가 생기며, 모두가 같이 보는 시계가 없다.
1994년 Sun Microsystems의 L. Peter Deutsch가 정리한 일곱 가지에 James Gosling이 하나를 더한 "분산 컴퓨팅의 오류"는 처음 분산 프로그램을 짤 때 은연중에 믿는 가정의 목록이다. 하나하나가 실제 장애의 원인이 된다. 예를 들어 "네트워크는 믿을 만하다"고 믿으면 요청이 실패했을 때 그것이 서버에 닿지 않은 것인지, 처리됐는데 답만 사라진 것인지 구별하지 않는다. 그 상태로 재시도하면 결제가 두 번 된다. 그래서 재시도할 요청에는 같은 요청임을 알리는 멱등 키를 붙인다.
규모를 키우는 설계의 큰 그림(부하 분산·캐시·샤딩)은 시스템 설계 노트에 있다. 이 노트는 그 밑에 깔린 원리 — 무엇을 보장할 수 있고 무엇은 원리적으로 불가능한가 — 를 다룬다.
장애 모델: 중단·누락·비잔틴
알고리즘이 "장애를 견딘다"고 말하려면 어떤 장애인지 먼저 정해야 한다. 이것이 장애 모델이다. 가장 얌전한 것은 중단(crash-stop)이다. 노드가 멈추면 다시는 아무 말도 하지 않는다. 실제 서버는 재부팅해 돌아오므로, 디스크에 남긴 것만 기억한 채 복귀하는 중단-복구(crash-recovery) 모델을 더 자주 쓴다. 누락은 메시지 일부를 보내지 못하거나 받지 못하는 것, 타이밍 장애는 답은 맞지만 약속한 시간보다 늦는 것이다. 가장 넓은 비잔틴 장애는 노드가 무엇이든 할 수 있다고 본다. 틀린 값을 보내거나, 상대마다 다른 말을 하거나, 다른 악성 노드와 짜고 움직일 수도 있다.
네트워크에도 모델이 있다. 동기 모델은 메시지 지연과 처리 시간에 알려진 상한이 있다고 보고, 비동기 모델은 상한이 전혀 없다고 본다. 비동기에서는 답이 없는 노드가 죽은 것인지 느린 것인지 알 수 없다. 현실의 데이터센터는 대부분의 시간에는 빠르지만 가끔 GC 멈춤이나 혼잡으로 몇 초씩 늦어지므로, 그 사이인 부분 동기(언젠가부터는 상한이 지켜진다) 모델이 실무 알고리즘의 바탕이 된다. 이 구분은 아래 FLP 불가능성에서 결정적인 역할을 한다.
두 장군 문제와 전달 보장
메시지가 사라질 수 있는 통신로로 두 쪽이 "함께 하자"고 확실히 합의할 수 있을까? 이것이 두 장군 문제다. 골짜기 양쪽의 두 장군이 동시에 공격해야만 이기는데, 전령은 도중에 잡힐 수 있다. A가 "새벽에 공격"을 보내면 A는 전령이 닿았는지 모르니 답장을 기다린다. B가 답장을 보내면 이번에는 B가 답장이 닿았는지 모른다. 확인의 확인을 아무리 이어도 마지막 메시지를 보낸 쪽은 늘 불확실하다. 이 불가능성은 1975년 Akkoyunlu·Ekanadham·Huber가 처음 보였고, 1978년 Jim Gray가 지금의 이름을 붙였다(Wikipedia). 컴퓨터 통신에서 처음으로 풀 수 없다고 증명된 문제로 꼽힌다.
실무의 결론은 전달 보장을 고르는 것이다. 다시 보내지 않으면 잃을 수 있고(최대 한 번), 확인이 올 때까지 다시 보내면 중복이 생길 수 있다(최소 한 번). 통신로만으로 "정확히 한 번 전달"은 만들 수 없으므로, 최소 한 번 보내되 받는 쪽이 중복을 걸러 결과가 한 번 처리한 것과 같게 만든다. 위 오류 8가지 절의 멱등 키가 그 도구다. Kafka의 멱등 생산자·트랜잭션이 이것을 어떻게 구현하는지는 시스템 설계 › 배치와 스트림 처리의 정확히 한 번 절에 있다.
시간과 순서
각 서버의 시계는 수정 발진기로 가는데, 발진기마다 조금씩 빠르거나 느리다. 차이는 백만분율(ppm)로 말한다. 기준보다 20 ppm 빠른 시계와 30 ppm 느린 시계가 동기화 없이 하루를 보내면 둘의 차이는 4.32초가 된다. NTP 같은 시각 동기화가 이 차이를 주기적으로 줄이지만 네트워크 지연 때문에 오차가 남고, 동기화 순간 시계가 뒤로 뛸 수도 있다. 그래서 "타임스탬프가 큰 쓰기가 나중 것"이라는 규칙은 시계가 어긋난 노드가 끼면 실제로 나중에 쓴 값을 버린다. 이 함정과 단조 시계·벽시계의 차이는 시스템 설계 › 일관성과 시간에 자세히 있다.
순서만 필요하다면 실제 시각을 버리고 논리 시계를 쓴다. Lamport는 1978년 논문에서 "먼저 일어남"(happened-before, →) 관계를 정의했다. 같은 프로세스 안에서 앞선 사건, 메시지를 보낸 사건과 그 메시지를 받은 사건 사이에 →가 있고, 이 관계는 이어진다. 어느 쪽으로도 이어지지 않는 두 사건은 동시다. 실제로 어느 쪽이 먼저였든, 서로 영향을 줄 수 없었으므로 순서를 따질 의미가 없다는 뜻이다.
- 램포트 시계: 프로세스마다 정수 하나. 사건마다 1 올리고, 받을 때는 max(내 값, 받은 값) + 1. a → b이면 L(a) < L(b)다. 거꾸로는 성립하지 않는다.
- 벡터 시계: 프로세스마다 전원 몫의 칸을 가진 벡터. 받을 때 칸마다 큰 값을 고르고 자기 칸을 올린다. 모든 칸이 작거나 같으면 먼저 일어남, 섞이면 동시다. 이번에는 양방향으로 성립한다.
세 프로세스의 시간선. 시계는 모두 0에서 시작한다.
벡터는 프로세스 수만큼 길어지므로, 수천 개 클라이언트가 쓰는 시스템에서는 복제본 단위로 칸을 두는 버전 벡터나 점 버전 벡터 같은 변형을 쓴다. 다른 각본(세 프로세스가 사슬처럼 메시지를 넘기는 예)과 리스·펜싱 토큰은 시스템 설계 › 일관성과 시간에 있다.
복제: 리더–팔로워와 쿼럼
같은 데이터를 여러 노드에 두는 복제는 한 대가 죽어도 데이터를 잃지 않고, 읽기를 나눠 받기 위해 한다. 가장 흔한 방식은 리더–팔로워다. 쓰기는 리더 한 곳에서만 받아 순서를 정하고, 그 순서대로 팔로워에 보낸다. 팔로워의 확인을 기다리는 동기 복제는 안전하지만 느리고, 기다리지 않는 비동기 복제는 빠르지만 리더가 죽으면 마지막 쓰기 몇 개를 잃는다. 동기·비동기 복제와 장애 조치의 그림은 데이터베이스 › 복제·샤딩·합의에 있다.
리더 없이 아무 복제본에나 쓰고 읽는 Dynamo식 시스템은 쿼럼으로 최신 값을 보장한다. 복제본 N개 중 쓰기는 W곳, 읽기는 R곳의 확인을 받는다. R + W > N이면 어떤 읽기 집합과 쓰기 집합도 최소 R + W − N곳 겹치므로, 읽은 값 가운데 버전이 가장 큰 것이 마지막 쓰기다. 아래 표는 N = 5의 25가지 조합을 모두 셈으로 확인한 것이다.
R = 1, W = 5는 읽기가 빠르지만 한 곳만 죽어도 쓰기가 막히고, R = 5, W = 1은 그 반대다. 과반(R = W = 3)은 양쪽 모두 두 곳까지 죽어도 된다. 다만 쿼럼이 겹친다고 선형화까지 보장되지는 않는다. 쓰기가 일부 복제본에만 닿은 채 실패하면, 그 뒤의 읽기가 새 값을 볼 수도 옛 값을 볼 수도 있다. 이를 메우는 읽기 복구·힌트 넘겨주기·머클 트리 비교는 시스템 설계 › 설계의 부품에 있다.
일관성 모델: 선형화·순차·인과·최종
일관성 모델은 동시에 일어난 연산들의 결과로 어떤 기록(history)이 허용되는지를 정하는 약속이다. 강한 모델일수록 허용하는 기록이 적어 프로그래머가 생각하기 쉽지만, 그만큼 노드끼리 더 많이 기다려야 한다.
- 선형화(linearizability, Herlihy와 Wing): 모든 연산이 시작과 끝 사이 어느 한 순간에 한꺼번에 일어난 것처럼 보인다. 끝난 연산의 결과는 그 뒤에 시작한 모든 연산이 본다. 복제본이 여럿이어도 하나뿐인 것처럼 보인다.
- 순차 일관성(Lamport): 모두가 동의하는 하나의 순서가 있고 각 클라이언트의 프로그램 순서는 지키지만, 그 순서가 실제 시각과 맞을 필요는 없다.
- 인과 일관성: 먼저 일어남(→)으로 이어진 연산의 순서만 모두가 같게 본다. 동시인 연산은 노드마다 다른 순서로 봐도 된다.
- 최종 일관성(eventual): 새 쓰기가 멈추면 언젠가 모든 복제본이 같은 값이 된다. 그 전에 무엇을 읽을지는 약속하지 않는다.
선형화와 순차 일관성의 차이는 "실제 시간"을 따지느냐뿐이다. 아래 ①에서 B의 첫 읽기는 A의 쓰기가 끝난 뒤 시작했는데 옛 값 0을 본다. 실시간 순서를 지키는 합법적 순서가 없으므로 선형화가 아니다. 그러나 "B의 읽기 → A의 쓰기 → B의 둘째 읽기"라는 순서는 B의 프로그램 순서를 지키므로 순차 일관성은 만족한다. ②처럼 읽기가 쓰기와 겹쳐 있으면 옛 값을 봐도 선형화다.
사용자가 겪는 "내가 쓴 글이 새로고침하니 사라졌다" 같은 문제는 이 사이의 세션 보장(내 쓰기 읽기, 단조 읽기)으로 다룬다. 세션 보장과 인과 순서가 깨지는 예는 시스템 설계 › 일관성과 시간에, 한 데이터베이스 안의 트랜잭션 격리 수준(직렬성)은 데이터베이스 › 동시성과 격리 수준에 있다. 선형화는 단일 객체에 대한 약속이고 직렬성은 여러 객체를 건드리는 트랜잭션에 대한 약속이라, 둘을 함께 요구하면 엄격한 직렬성(strict serializability)이 된다.
CAP과 PACELC
CAP 정리는 네트워크가 갈라진(P) 동안에는 선형화(C)와 모든 살아 있는 노드가 답하는 가용성(A)을 함께 가질 수 없다는 것이다. 갈라진 소수 쪽 노드가 쓰기를 받으면 다수 쪽이 모르는 값이 생기고(C 포기), 받지 않으면 그 노드는 답하지 못한다(A 포기). 분할은 고를 수 있는 것이 아니라 일어나는 일이므로, 실제 선택은 분할이 났을 때 C와 A 중 무엇을 버리느냐다.
Daniel Abadi는 2012년 IEEE Computer에 실은 글에서 CAP이 분할이 없는 평소를 말하지 않는다는 점을 짚었다. 평소에도 복제본끼리 확인을 기다리면 일관되지만 느리고, 가까운 복제본이 바로 답하면 빠르지만 옛 값일 수 있다. 이를 더한 것이 PACELC다. "분할(P)이면 A냐 C냐, 그렇지 않으면(E) 지연(L)이냐 C냐."
CAP의 증명과 분할 그림은 데이터베이스 › 복제·샤딩·합의에 있다. 실무에서 더 쓸모 있는 질문은 "이 연산이 옛 값을 봐도 되는가"다. 장바구니 수량은 잠깐 어긋나도 되지만, 계좌 잔액과 재고 차감은 안 된다. 한 시스템 안에서도 연산마다 다른 답을 고른다.
합의: Raft와 FLP 불가능성
합의(consensus)는 여러 노드가 하나의 값에 동의하는 문제다. 세 가지를 지켜야 한다. 모두 같은 값을 고르고(합의), 고른 값은 누군가 제안한 값이며(타당성), 죽지 않은 노드는 언젠가 결정한다(종료). 합의를 되풀이하면 모든 노드가 같은 순서의 명령 목록(로그)을 갖게 되고, 같은 로그를 같은 순서로 적용하는 복제 상태 기계가 하나의 서버처럼 동작한다. 리더 선출, 분산 락, 설정 저장이 모두 이 위에 선다.
Raft는 이를 리더 선출과 로그 복제로 나눈다. 시간은 번호 붙은 임기로 나뉘고, 임기마다 리더는 많아야 하나다. 리더는 과반에 복제된 항목만 커밋하고, 표는 로그가 자기보다 뒤처지지 않은 후보에게만 준다. 그래서 커밋된 항목은 다음 리더의 로그에 반드시 있다. 아래 그림은 데이터베이스 노트의 다섯 대 예와 달리 세 대에서, 커밋되지 않은 항목이 어떻게 지워지는지에 초점을 둔다.
임기 1. 리더 S1은 항목 두 개를 과반에 복제해 커밋했고, 셋째 항목은 아직 자기 로그에만 있다.
S1의 셋째 항목(임기 1)은 과반에 닿지 못했으므로 클라이언트에게 성공이라고 답한 적이 없다. 그래서 지워도 약속을 어기지 않는다. 반대로 S2가 커밋한 셋째 항목(임기 2)은 S2와 S3에 있으므로, 다음에 누가 리더가 되든 과반의 표를 얻으려면 이 둘 중 하나의 표가 필요하고, 그 표는 이 항목을 가진 후보에게만 간다.
아래 시뮬레이터에서는 서버 다섯 대가 실제로 메시지를 주고받는다. 원 둘레의 호는 남은 선거 타이머이고, 동그란 점은 AppendEntries(큰 점은 항목을 실은 것, 작은 점은 답), 마름모는 표 요청이다. 리더를 골라 정지시키면 하트비트가 끊긴 팔로워 중 타이머가 먼저 끝난 서버가 임기를 올려 새 선거를 연다. 요청을 보내면 리더의 로그에 칸이 생기고, 과반에 복제된 뒤에야 칸이 색칠된다(커밋). 네트워크 분할을 누르면 리더와 그 옆 서버가 소수 쪽에 갇히는데, 옛 리더는 요청을 받아도 커밋하지 못하고 다수 쪽에서는 새 리더가 커밋을 이어 간다. 분할을 풀면 옛 리더의 커밋 안 된 칸이 지워진다. 원조는 Raft 저자들이 만든 raft.github.io의 RaftScope이고, 규칙은 Raft 논문(Ongaro·Ousterhout, USENIX ATC 2014)의 그림 2를 따랐다. 이 그림은 원리만 보이려고 스냅숏과 멤버십 변경은 뺐다.
- 리더
- 후보
- 팔로워
- 정지
- AppendEntries
- 표 요청
- 작은 점은 답, 속이 빈 점은 거절
재생을 누르면 시작합니다.
서버를 눌러 정지하거나 선거를 일으켜 보세요.
그런데 Fischer·Lynch·Paterson의 FLP 불가능성은 비동기 시스템에서 노드 하나만 멈출 수 있어도, 늘 끝나는 결정적 합의 알고리즘은 없다고 증명했다. 줄거리는 이렇다. 결과가 아직 0일 수도 1일 수도 있는 양가 상태에서, 결정을 굳힐 메시지 하나를 스케줄러가 계속 늦추면 시스템은 다시 양가 상태에 머물 수 있다. 비동기에서는 늦은 메시지와 죽은 노드를 구별할 수 없기 때문이다.
Raft와 Paxos는 이 정리를 어기지 않는다. 안전성은 어떤 지연에서도 지키고, 끝나는 것은 네트워크가 한동안 안정될 때만 보장한다. 선거 타이머를 무작위로 두는 것도 표가 계속 갈리는 불운을 확률적으로 피하려는 장치다. 합의가 전체 순서 브로드캐스트·선형화 가능한 비교 후 교체와 서로 환원된다는 이야기는 시스템 설계 › 일관성과 시간에 있다.
2단계 커밋과 그 한계
합의가 "여러 복제본이 같은 값"을 정하는 일이라면, 원자적 커밋은 "서로 다른 데이터를 가진 여러 참여자가 모두 커밋하거나 모두 취소"하는 일이다. 표준 해법이 2단계 커밋(2PC)이다. 조정자가 먼저 모든 참여자에게 준비를 묻고(1단계), 모두 "예"라고 하면 커밋을, 하나라도 "아니오"면 중단을 알린다(2단계). "예"라고 답한 참여자는 그 순간부터 혼자 취소할 권리를 잃고, 결정을 들을 때까지 잠금을 쥔 채 기다려야 한다.
이 막힘이 2PC의 근본 한계다. 조정자가 복구될 때까지 잠긴 행을 아무도 못 쓰니, 조정자 하나가 전체 가용성을 쥔다. 2PC는 합의가 아니어서(모든 참여자의 동의가 필요하고 과반으로는 안 된다) 조정자 장애를 스스로 넘기지 못한다. 그래서 Spanner나 CockroachDB 같은 시스템은 참여자와 조정자 각각을 Raft·Paxos 그룹으로 복제해 "죽지 않는 조정자"를 만든 뒤 그 위에서 2PC를 돌린다. 마이크로서비스처럼 잠금을 오래 쥘 수 없는 곳에서는 단계마다 커밋하고 실패하면 보상 동작으로 되돌리는 사가(saga)를 쓴다. 분산 트랜잭션의 데이터베이스 쪽 이야기는 데이터베이스 › 복제·샤딩·합의에 있다.
CRDT: 합의 없이 합쳐지는 자료 구조
합의는 비싸다. 매 쓰기마다 과반의 왕복을 기다려야 하고, 분할된 소수 쪽은 쓰지 못한다. 오프라인에서도 써야 하는 메모 앱이나 여러 사람이 함께 고치는 문서는 다른 길을 간다. 각자 자유롭게 고치고 나중에 합치되, 합치는 규칙 자체가 충돌을 일으키지 않게 자료 구조를 설계한다. 이것이 CRDT(충돌 없는 복제 자료형)이고, 2011년 Shapiro·Preguiça·Baquero·Zawirski가 형식적으로 정의했다.
핵심은 병합 함수가 교환(a ⊔ b = b ⊔ a), 결합((a ⊔ b) ⊔ c = a ⊔ (b ⊔ c)), 멱등(a ⊔ a = a) 법칙을 지키는 것이다. 그러면 메시지가 어떤 순서로 몇 번 오든 같은 상태로 수렴한다(강한 최종 일관성). 가장 간단한 예가 증가만 하는 G-카운터다. 복제본마다 자기 칸만 올리고, 병합은 칸마다 큰 값을 고르며, 값은 칸의 합이다.
감소도 하려면 G-카운터 두 개(더한 것, 뺀 것)를 쌍으로 둔 PN-카운터를 쓰고, 집합에는 추가·삭제마다 고유 태그를 붙이는 OR-집합, 공동 편집 문서에는 글자마다 위치 식별자를 두는 시퀀스 CRDT를 쓴다. Riak 같은 키-값 저장소와 Yjs 같은 공동 편집 라이브러리가 CRDT를 쓴다. 대가는 메타데이터다. 칸과 태그, 지운 자리의 흔적(톰스톤)이 쌓여 원래 데이터보다 커질 수 있어 정리 방법이 따로 필요하다. 그리고 "재고는 0 밑으로 내려가면 안 된다" 같은 전역 불변식은 각자 고치는 방식으로는 지킬 수 없어, 그런 연산에는 여전히 합의가 필요하다.
비잔틴 합의 개요
Raft와 Paxos는 노드가 멈출 수는 있어도 거짓말은 하지 않는다고 가정한다. 서로 다른 조직이 노드를 운영하거나 노드가 해킹될 수 있다면 이 가정이 무너진다. Lamport·Shostak·Pease가 "비잔틴 장군 문제"로 이름 붙인 이 상황에서는, 배신자가 상대마다 다른 말을 해도 충실한 노드들이 같은 결정을 내려야 한다.
필요한 노드 수가 늘어난다. 중단 장애 f개를 견디려면 2f + 1대면 되지만(과반 둘이 반드시 겹침), 비잔틴 장애 f개를 견디려면 3f + 1대가 필요하다. 정족수를 2f + 1로 잡으면 두 정족수가 f + 1대 이상 겹치고, 그중 배신자는 많아야 f대이니 정직한 노드가 반드시 하나는 겹친다. f = 1이면 중단 장애는 3대, 비잔틴은 4대다.
대표 알고리즘인 PBFT 계열은 리더가 순서를 제안하고 두 번의 전원 투표 단계로 확정하는데, 메시지 수가 노드 수의 제곱으로 늘어 수십 대 규모에서 주로 쓴다. 참여자를 미리 알 수 없는 공개 네트워크에서 이 문제를 작업 증명·지분 증명으로 푼 것이 블록체인이다. 그쪽의 합의와 한계는 블록체인 노트에 있다.
실무: etcd·ZooKeeper와 Jepsen 테스트
합의 알고리즘을 직접 구현하는 일은 드물다. 대신 합의를 한 곳에서 제대로 구현한 조정 서비스에 작은 설정·리더 정보·락을 맡긴다.
- etcd: Raft로 복제하는 키-값 저장소. Kubernetes API 서버가 클러스터 상태를 여기에 저장한다. 가용성보다 일관성을 택해, 분할 시 소수 쪽은 쓰기를 받지 않는다. 키를 지켜보는 watch와 일정 시간 뒤 사라지는 lease를 제공한다.
- ZooKeeper: znode라는 파일 시스템 모양의 계층 이름 공간을 제공한다. 세션이 끝나면 사라지는 임시(ephemeral) 노드와 변경 알림(watch)으로 리더 선출·멤버십·락을 만든다. 문서는 순차 일관성·원자성·단일 시스템 이미지를 보장하며, 읽기가 쓰기보다 10:1 정도로 많을 때 가장 잘 동작한다고 적는다.
이런 시스템이 약속을 실제로 지키는지 확인하는 표준 방법이 Jepsen이다. 클라이언트 여럿이 연산을 보내는 동안 "네메시스"가 네트워크를 가르고, 프로세스를 멈추거나 죽이고, 시계를 비튼다. 그리고 남은 연산 기록이 주장한 일관성 모델로 설명되는지 검사한다. 위 일관성 절의 판정 함수를 아주 크게 키운 것이라 보면 된다.
2020년 1월의 etcd 3.4.3 분석에서 키-값 연산은 멈춤·충돌·시계 어긋남·분할·멤버 변경 속에서도 기본적으로 엄격한 직렬성을 지켰지만, etcd의 락은 상호 배제를 보장하지 못했다. 건강한 클러스터에서도 두 클라이언트가 같은 락을 함께 쥐었고, 짧은 lease에서는 공유 집합에 확인받은 갱신의 약 18%를 잃었다. 락이 "lease가 끝나면 풀린다"는 시간 가정에 기대기 때문이다. 락으로 지키는 자원 쪽에서 펜싱 토큰을 검사해야 하는 이유가 이것이다. 이렇게 일부러 장애를 넣어 보는 방식이 운영 환경으로 넓어진 것이 카오스 엔지니어링이고, 분산 시스템을 설계 단계에서 모델 검사하는 방법은 형식 검증 노트에 있다.
흔한 오해
- "CAP의 C와 A는 일상어의 일관성·가용성이다": CAP의 C는 선형화, A는 "장애 없는 모든 노드가 답한다"는 아주 좁은 정의라 ACID의 C나 99.99% 가용성과 다르다. 이 구분과 "셋 중 둘"이라는 표현의 함정은 데이터베이스 › 복제·샤딩·합의의 CAP 절에 있다.
- "모든 시스템은 CP 아니면 AP다": 정의가 그만큼 좁아서 많은 시스템은 둘 다 아니다. Kleppmann은 기본 설정의 ZooKeeper가 각 노드의 데이터로 읽기에 답해 선형화 가능한 읽기를 주지 않으므로 CAP 의미로 일관되지 않고, 분할된 소수 쪽은 쓰기를 처리하지 못하니 CAP 의미로 가용하지도 않다는 예를 든다(Please stop calling databases CP or AP). 그래서 그는 CP·AP 딱지 대신 더 정확한 용어로 맞바꿈을 따지자고 권하고, CAP이 아무 말도 하지 않는 지연을 사람들이 실제로는 더 신경 쓴다고 짚는다. 위 PACELC도 같은 방향의 보완이다.
- "최종 일관성이면 곧 맞춰진다": 위 일관성 모델 절처럼 최종 일관성은 "쓰기가 멈추면 언젠가"만 약속한다. 얼마나 걸릴지, 그사이 무엇을 읽을지는 말하지 않는다. 복제 지연을 재고, 필요한 화면에는 내 쓰기 읽기 같은 세션 보장을 따로 건다.
- "합의 알고리즘이 있으면 노드가 몇 대 죽어도 된다": Raft·Paxos는 과반이 살아 있어야 진행한다. 다섯 대면 두 대까지다. 또 노드가 거짓말하지 않는다는 가정 위에 있어, 비잔틴 장애는 위 비잔틴 절의 3f + 1 구성이 따로 필요하다.
핵심 자료
규모 확장과 설계 사례는 시스템 설계 노트, 복제·샤딩을 쓰는 데이터베이스 내부는 데이터베이스 노트에 있다.
| 자료 | 종류 | 요지 | 링크 |
|---|---|---|---|
| Designing Data-Intensive Applications | 책 | 복제·분할·트랜잭션·일관성·합의를 실제 데이터베이스의 선택과 함께 설명한다. 분산 시스템 입문서로 가장 많이 권하는 책. | dataintensive.net |
| MIT 6.5840 Distributed Systems | 강의 | MapReduce, Raft, 선형화 가능한 키-값 저장소를 Go로 직접 구현하는 과제와 논문 읽기로 짜인 강의. | MIT PDOS |
| In Search of an Understandable Consensus Algorithm | 논문 | Raft 논문. 리더 선출·로그 복제·안전성을 나눠 설명하고, 이해하기 쉬움을 설계 목표로 삼았다. | raft.github.io |
| Impossibility of Distributed Consensus with One Faulty Process | 논문 | FLP 불가능성. 비동기 시스템에서는 한 대만 멈출 수 있어도 늘 끝나는 결정적 합의 알고리즘이 없다. | MIT CSAIL |
| Consistency Models | 참고 문서 | 선형화·직렬성·인과 일관성 등 모델마다 정의, 허용하는 현상, 서로의 포함 관계를 지도처럼 정리했다. | Jepsen |
| Consistency Tradeoffs in Modern Distributed Database System Design | 논문 | PACELC를 제안한 글. 분할이 없을 때도 지연과 일관성을 맞바꾼다는 점을 시스템 분류와 함께 보였다. | UMD |
- Designing Data-Intensive Applications — dataintensive.net
- MIT 6.5840 Distributed Systems — MIT PDOS
- In Search of an Understandable Consensus Algorithm — raft.github.io
- Impossibility of Distributed Consensus with One Faulty Process — MIT CSAIL
- Consistency Models — Jepsen
- Consistency Tradeoffs in Modern Distributed Database System Design — UMD
- Fallacies of distributed computing — Wikipedia
- Time, Clocks, and the Ordering of Events in a Distributed System — Lamport
- Linearizability: A Correctness Condition for Concurrent Objects — Herlihy, Wing
- Raft Consensus Algorithm — raft.github.io
- In Search of an Understandable Consensus Algorithm — USENIX ATC 2014
- The Byzantine Generals Problem — Lamport, Shostak, Pease
- Conflict-free replicated data type — Wikipedia
- CRDT.tech
- Two-phase commit protocol — Wikipedia
- Why etcd — etcd.io
- ZooKeeper Overview — Apache ZooKeeper
- Jepsen: etcd 3.4.3 — Jepsen
- Jepsen Analyses — Jepsen
- jepsen-io/jepsen — GitHub
- Two Generals' Problem — Wikipedia
- Please stop calling databases CP or AP — Martin Kleppmann