분산 데이터베이스 CAP·2PC·정족수: 네트워크 장애 시 읽기·쓰기 판단


1장. 주문 요청이 실패한 것일까, 성공한 것일까#

고객이 상품을 주문했습니다.

애플리케이션은 주문 DB에 주문을 저장하고 결제 DB에 결제 결과를 기록합니다.

모든 처리가 끝나갈 때 네트워크가 끊겼습니다.

고객 화면에는 다음 메시지가 나타납니다.

요청 시간 초과

고객은 생각합니다.

주문이 실패했구나.

그리고 주문 버튼을 다시 누릅니다.

하지만 서버에서는 첫 번째 주문이 이미 COMMIT됐을 수도 있습니다.

그렇다면 결과는:

첫 번째 주문
→ 성공

두 번째 주문
→ 또 성공

이 되어 같은 상품이 두 번 주문될 수 있습니다.

분산 시스템에서 가장 까다로운 상태는 단순한 성공이나 실패가 아닙니다.

결과를 아직 모르는 상태입니다.


2장. 분산 시스템에서는 네트워크 자체가 실패할 수 있다#

단일 데이터베이스에서는 프로세스나 디스크 장애를 먼저 생각합니다.

분산 시스템에서는 한 가지가 더 추가됩니다.

노드 A 정상

노드 B 정상

하지만
A ↔ B 통신 불가

양쪽 서버는 살아 있습니다.

네트워크만 끊겼습니다.

이것을 네트워크 분할이라고 부릅니다.

flowchart LR
    A["Node A"] -. "통신 불가" .- B["Node B"]

문제는 이런 상태에서 양쪽이 어떤 요청을 받아들여야 하는가입니다.


3장. CAP는 평상시가 아니라 네트워크 분할 중의 선택을 다룬다#

CAP의 세 글자는 다음을 의미합니다.

C
Consistency

A
Availability

P
Partition tolerance

하지만 이 단어를 일상적인 의미 그대로 받아들이면 오해하기 쉽습니다.

CAP의 핵심 질문은 다음과 같습니다.

네트워크가 분리되어 서로 최신 상태를 확인할 수 없을 때, 최신성과 응답 가능성을 어떻게 처리할 것인가?


4장. CAP의 C는 단순히 “데이터가 깨지지 않는다”가 아니다#

CAP에서 말하는 일관성은 일반적인 데이터 무결성이라는 뜻보다 더 구체적입니다.

쉽게 표현하면:

완료된 쓰기와 이후 읽기를 하나의 원자적인 순서로 설명할 수 있는가?

라는 강한 성질과 연결됩니다.

예를 들어 잔액이 100입니다.

A에서 잔액을 80으로 변경하고 쓰기가 성공했습니다.

그 뒤 B에서 읽는다면 최신 상태인 80을 반환하는 것이 기대됩니다.

하지만 A와 B 사이 네트워크가 끊겼다면 B는 A의 변경을 확인할 수 없습니다.


5장. 분할 중 최신값을 지키려면 일부 요청을 거부해야 할 수 있다#

초기 잔액:

A = 100
B = 100

네트워크가 끊겼습니다.

A에서:

100 → 80

쓰기 요청이 들어왔습니다.

A는 이를 확정했습니다.

B에는 아직:

100

만 있습니다.

이때 B에서 읽기 요청이 들어옵니다.

최신값을 반드시 반환해야 한다면 B는 100을 반환하면 안 됩니다.

하지만 A와 통신할 수 없습니다.

따라서:

응답 지연

또는

요청 거부

가 필요할 수 있습니다.


6장. 반대로 모든 노드가 계속 응답하면 오래된 값을 줄 수 있다#

같은 상황에서 B가 가용성을 우선한다고 하겠습니다.

B는 요청에 응답합니다.

100

사용자는 정상 응답을 받았습니다.

하지만 최신 확정값은 A의 80입니다.

즉:

응답 가능성
→ 유지

최신값 보장
→ 포기

가 될 수 있습니다.

이를 표로 보면 다음과 같습니다.

선택 A의 쓰기 B의 읽기 결과
최신값 우선 80 확정 최신값 확인 불가 시 대기·거부 일부 요청 가용성 감소
응답 우선 80 확정 100 반환 가능 오래된 읽기 허용

7장. P는 선택해서 끄는 기능이 아니다#

CAP를 다음처럼 설명하는 경우가 있습니다.

C, A, P 중 두 개를 고르면 된다.

매우 단순화된 설명입니다.

실제 분산 시스템에서는 네트워크 분할이 발생할 수 있습니다.

분할이 발생했는데 시스템이 계속 동작해야 한다면 P를 무시할 수 없습니다.

따라서 실질적인 질문은:

분할이 발생한 상황에서 C와 A를 어떻게 다룰 것인가?

에 가깝습니다.


8장. “이 제품은 CP, 저 제품은 AP”만으로는 부족하다#

흔히 다음처럼 외우기도 합니다.

MongoDB = CP

Cassandra = AP

하지만 실제 시스템 동작은 읽기·쓰기 설정과 복제 구조에 따라 달라질 수 있습니다.

다음 요소가 중요합니다.

몇 개 복제본의 응답을 기다리는가?

어느 노드에서 읽는가?

오래된 값을 허용하는가?

리더가 필요한가?

장애 중 어느 쪽 노드가 쓰기를 받을 수 있는가?

제품 이름보다 구체적인 요청 경로와 설정을 보는 편이 정확합니다.


9장. CAP만 보면 정상 상태의 지연 문제는 설명하기 어렵다#

네트워크 분할이 없는 평상시에도 선택이 있습니다.

예를 들어 쓰기를 확정하기 전에 원격 복제본의 응답까지 기다린다고 하겠습니다.

장점:

더 강한 복제 확인

단점:

응답 지연 증가

반대로 로컬 노드만 기록하고 바로 성공 응답을 보내면 빠르지만 원격 반영이 늦을 수 있습니다.

이 문제를 함께 설명하는 틀로 PACELC가 자주 언급됩니다.


10장. PACELC는 분할이 없을 때도 선택이 있음을 강조한다#

간단히 보면 다음과 같습니다.

Partition이 있을 때
→ Availability 또는 Consistency 문제

Else
분할이 없을 때
→ Latency와 Consistency 사이 선택

즉:

장애가 없으면 아무 문제가 없다

가 아닙니다.

정상 상태에서도 더 많은 복제 확인을 기다릴수록 지연 시간이 늘 수 있습니다.


11장. CAP와 2PC는 같은 문제를 해결하는 기술이 아니다#

CAP는 네트워크 분할 중 응답과 일관성의 관계를 설명합니다.

2PC는 여러 참여자가 하나의 트랜잭션을 함께 커밋할 것인지를 조정합니다.

예를 들어 주문 하나를 처리할 때:

주문 DB

결제 DB

두 데이터베이스가 모두 성공해야 한다고 하겠습니다.

여기서 필요한 질문은:

두 DB가 모두 COMMIT하거나 모두 ROLLBACK하게 하려면 어떻게 할까?

입니다.

이것이 2PC가 다루는 영역입니다.


12장. 2PC는 Two-Phase Commit이다#

2PC는 두 단계로 나뉩니다.

1단계#

Prepare 단계입니다.

조정자가 참여자들에게 묻습니다.

이 거래를 커밋할 준비가 됐는가?

각 참여자는:

YES

또는

NO

로 응답합니다.

2단계#

모두 YES이면 조정자가 최종 COMMIT을 결정합니다.

하나라도 NO라면 ROLLBACK을 결정합니다.


13장. 주문 DB와 결제 DB를 2PC로 묶어 보자#

개념적으로 다음 구조입니다.

sequenceDiagram
    participant C as Coordinator
    participant O as Order DB
    participant P as Payment DB

    C->>O: PREPARE?
    C->>P: PREPARE?
    O-->>C: YES
    P-->>C: YES
    C->>O: COMMIT
    C->>P: COMMIT

두 참여자가 모두 준비 완료라고 응답했습니다.

그다음 최종 COMMIT 명령을 전달합니다.


14장. Prepare 성공은 COMMIT 성공이 아니다#

다음 상태를 생각해 보겠습니다.

Order DB
→ PREPARED

Payment DB
→ PREPARED

아직 최종 COMMIT 결정은 도착하지 않았습니다.

이 상태를:

트랜잭션 완료

라고 보면 안 됩니다.

준비 상태는:

내가 커밋할 수 있도록 필요한 로컬 준비는 마쳤고, 이제 최종 결정을 기다린다.

는 의미입니다.


15장. 가장 어려운 순간은 모두 Prepare한 뒤 조정자가 사라질 때다#

시간표로 보겠습니다.

시각 조정자 주문 DB 결제 DB
1 PREPARE 전송 준비 준비
2 YES 모두 수신 PREPARED PREPARED
3 장애 발생 결정 대기 결정 대기

주문 DB는 생각합니다.

COMMIT해야 하나?

ROLLBACK해야 하나?

하지만 다른 참여자의 상태와 조정자의 결정을 모릅니다.

임의로 결정하면 전체 원자성이 깨질 수 있습니다.


16장. 이것이 2PC의 블로킹 문제다#

준비된 참여자는 최종 결정을 받을 때까지 자원을 보유할 수 있습니다.

Prepared transaction
↓
락·자원 유지 가능
↓
조정자 결정 대기

조정자가 오래 복구되지 않으면 해당 자원이 오랫동안 묶일 수 있습니다.

따라서 2PC는 다음 장점과 비용을 함께 가집니다.

여러 참여자의 원자적 커밋
↔
장애 시 준비 상태 대기 가능

17장. 2PC는 분산 트랜잭션의 격리 수준까지 자동으로 해결하지 않는다#

주문 DB와 결제 DB가 함께 COMMIT되도록 만들었다고 하겠습니다.

그렇다고 다음 문제가 자동으로 해결되는 것은 아닙니다.

두 사용자가 동시에 같은 재고를 차감

같은 쿠폰 중복 사용

여러 지역에서 서로 다른 최신값 읽기

2PC는 최종 커밋 결정을 맞추는 프로토콜입니다.

참여자 내부의 동시성 제어나 복제 일관성은 별도의 문제입니다.


18장. PostgreSQL에는 prepared transaction 기능이 있다#

PostgreSQL에서는 개념적으로 다음 명령을 사용할 수 있습니다.

BEGIN;

-- 로컬 변경 수행

PREPARE TRANSACTION 'order-742-participant-a';

이제 트랜잭션은 prepared 상태가 됩니다.

조정자가 최종 COMMIT을 결정하면:

COMMIT PREPARED 'order-742-participant-a';

ROLLBACK 결정이라면:

ROLLBACK PREPARED 'order-742-participant-a';

를 사용할 수 있습니다.


19장. prepared transaction을 일반 트랜잭션처럼 가볍게 사용하면 안 된다#

준비된 트랜잭션은 오랫동안 남을 수 있습니다.

그동안 잠금과 관련 자원을 유지할 수 있습니다.

따라서:

PREPARE TRANSACTION

을 단순히:

COMMIT 전에 한 번 더 저장

정도로 생각하면 안 됩니다.

실제 분산 트랜잭션에서는 별도의 조정자와 장애 복구 절차가 필요합니다.


20장. 복제는 같은 데이터를 여러 노드에 두는 것이다#

이번에는 주문 정보를 세 노드에 복제한다고 하겠습니다.

Replica A

Replica B

Replica C

초기값:

order O1 = pending

입니다.

결제 완료 후:

order O1 = paid

로 변경해야 합니다.

모든 복제본의 응답을 기다릴 수도 있고 일부만 기다릴 수도 있습니다.


21장. 정족수는 몇 개의 복제본 응답을 요구할지 정한다#

대표 기호를 사용해 보겠습니다.

N
→ 전체 복제본 수

W
→ 쓰기 성공에 필요한 복제본 응답 수

R
→ 읽기 성공에 필요한 복제본 응답 수

예를 들어:

N = 3

W = 2

R = 2

라고 하겠습니다.


22장. N=3, W=2이면 두 복제본이 쓰기를 확인해야 한다#

복제본:

A
B
C

가 있습니다.

쓰기 요청이:

O1 = paid

입니다.

A와 B가 반영 완료 응답을 보냈습니다.

C는 아직 이전 값일 수 있습니다.

A = paid

B = paid

C = pending

W=2 조건은 충족했습니다.

쓰기 성공을 응답할 수 있습니다.


23장. 읽기 R=2이면 두 복제본의 응답을 받는다#

이후 읽기 요청에서 B와 C를 읽었다고 하겠습니다.

B = paid

C = pending

두 값이 다릅니다.

단순히 응답을 두 개 받았다고 끝나는 것이 아닙니다.

시스템은 다음을 알아야 합니다.

어느 버전이 더 최신인가?

동시에 쓰인 값은 없는가?

충돌은 어떻게 해결할 것인가?

24장. R+W>N은 읽기와 쓰기 집합의 교집합을 만든다#

현재:

N = 3
W = 2
R = 2

입니다.

계산하면:

R + W
=
4

4 > 3

입니다.

크기 2인 쓰기 집합과 크기 2인 읽기 집합은 최소 하나의 복제본에서 겹칠 수밖에 없습니다.

예:

복제본 쓰기 확인 읽기 확인
A ✓
B ✓ ✓
C ✓

B가 교집합입니다.


25장. 하지만 교집합이 있다고 자동으로 최신값이 반환되는 것은 아니다#

읽기 응답은:

B = paid

C = pending

입니다.

시스템이 아무 기준 없이 C의 값을 선택한다면:

pending

을 반환할 수도 있습니다.

따라서 정족수만으로는 부족합니다.

다음이 필요합니다.

버전 식별

최신값 선택 규칙

동시 쓰기 충돌 처리

장애 노드 재합류 규칙

R+W>N이라는 공식 하나만으로 모든 시스템의 강한 일관성을 증명할 수는 없습니다.


26장. 복제본 다섯 개에서도 같은 계산을 할 수 있다#

복제본 수:

N = 5

쓰기:

W = 3

읽기:

R = 3

이면:

R + W = 6 > 5

입니다.

따라서 쓰기와 읽기 집합은 최소 한 복제본에서 겹칩니다.

하지만 여기서도 최신 버전 판정 규칙은 별도로 필요합니다.


27장. 네트워크가 3개와 2개로 갈라지면#

5개 복제본이 다음처럼 분리됐다고 하겠습니다.

Group A
3개

Group B
2개

설정:

W = 3
R = 3

입니다.

Group A는 쓰기 정족수를 만들 수 있습니다.

Group B는 2개밖에 없으므로 W=3을 만족하지 못합니다.

따라서 Group B에서는 쓰기를 확정하지 못합니다.


28장. 강한 정족수는 분할 중 일부 요청을 포기하는 대가가 있다#

Group B에서 사용자가 쓰기를 요청합니다.

노드는 살아 있습니다.

하지만 정족수를 모을 수 없습니다.

따라서:

쓰기 실패

또는

대기

가 됩니다.

이것은 시스템이 고장난 노드에서조차 무조건 응답해야 한다는 의미의 가용성과 충돌할 수 있습니다.


29장. W=1, R=1로 낮추면 더 많이 응답할 수 있다#

설정:

N = 5
W = 1
R = 1

이라면 네트워크 분할 중 한 복제본만 살아 있어도 쓰거나 읽을 수 있습니다.

가용성은 높아집니다.

하지만 다음 문제가 커집니다.

오래된 읽기

동시 쓰기 충돌

분할 종료 후 값 병합

따라서 낮은 정족수가 무조건 좋은 것도 아닙니다.


30장. 동기 복제라는 말도 구체적으로 정의해야 한다#

다음 중 어디까지 완료되면 쓰기 성공으로 인정할까요?

복제본이 네트워크로 수신

복제본 메모리에 기록

복제본 로그가 안정 저장소에 기록

복제본 데이터에 적용 완료

모두 의미가 다릅니다.

따라서:

동기 복제

라고만 쓰지 말고 무엇을 기다리는지를 명확하게 해야 합니다.


31장. 비동기 복제에서는 성공 응답 후에도 다른 복제본이 뒤처질 수 있다#

Primary가 쓰기를 받습니다.

O1 = paid

Primary는 바로 성공 응답을 보냅니다.

Replica 반영은 아직입니다.

Primary = paid

Replica = pending

사용자가 Replica에서 읽으면 오래된 값을 볼 수 있습니다.

이것을 복제 지연과 함께 이해해야 합니다.


32장. 비동기 복제는 장애 전환의 RPO에도 영향을 준다#

Primary는 다음 쓰기를 성공 처리했습니다.

14:00:01
O1 = paid

Replica는 아직 13:59:59까지만 반영했습니다.

그 순간 Primary가 완전히 손실됐습니다.

뒤처진 Replica를 승격하면 O1 변경이 사라질 수 있습니다.

따라서 복제 지연은 단순 읽기 최신성뿐 아니라 장애 전환 시 데이터 손실 범위와도 연결됩니다.


33장. 복제본이 많다고 데이터가 무조건 안전한 것은 아니다#

복제본이 세 개 있다고 하겠습니다.

운영자가 잘못된 DELETE를 실행합니다.

DELETE FROM orders;

정상적인 복제라면 삭제도 세 노드에 전달될 수 있습니다.

A
주문 삭제

B
주문 삭제

C
주문 삭제

복제는 하드웨어 장애에 유용하지만 잘못된 논리 변경까지 복제할 수 있습니다.

백업과 다른 이유입니다.


34장. CAP·2PC·정족수는 서로 다른 질문에 답한다#

정리하면:

CAP#

네트워크가 갈라졌을 때
요청 응답과 최신성을 어떻게 할 것인가?

2PC#

여러 참여자가
하나의 트랜잭션을 함께 확정할 것인가?

정족수#

복제본 중 몇 개의 응답을 받아
읽기·쓰기를 완료할 것인가?

이 세 개를 하나의 개념으로 섞으면 안 됩니다.


35장. 재고 5개를 두 지역에서 동시에 판매해 보자#

서울 노드와 부산 노드에 재고가 복제되어 있습니다.

초기값:

서울
5개

부산
5개

네트워크가 끊겼습니다.

서울에서 3개를 판매합니다.

남은 재고
2

부산에서도 독립적으로 3개를 판매합니다.

남은 재고
2

각 지역만 보면 정상입니다.

하지만 전체로는:

3 + 3
=
6개 판매

입니다.

실제 재고는 5개뿐입니다.


36장. 모든 노드가 응답하는 것과 업무 불변식을 지키는 것은 다른 문제다#

서울과 부산 모두 사용자에게:

주문 성공

이라고 응답했습니다.

가용성은 높습니다.

하지만:

판매 수량 ≤ 총재고

라는 업무 규칙은 깨졌습니다.

따라서 분산 시스템에서 중요한 질문은 단순히:

요청에 응답할 수 있는가?

가 아닙니다.

응답을 성공으로 확정해도 업무 규칙이 유지되는가?

를 봐야 합니다.


37장. 분할 중 한쪽 판매를 막으면 초과 판매는 줄일 수 있다#

서울을 쓰기 가능 노드로 정하고 부산에서는 새 주문을 받지 않는다고 하겠습니다.

그러면:

서울
판매 가능

부산
쓰기 거부

가 됩니다.

초과 판매 위험은 줄어듭니다.

대신 부산 사용자는 장애 동안 주문할 수 없습니다.

이것이 일관성과 가용성 사이의 실제 업무 선택입니다.


38장. 모든 재고를 자유롭게 수정하게 하는 것만이 방법은 아니다#

재고 5개를 네트워크 분할 전에 다음처럼 배정할 수도 있습니다.

서울 판매 권한
3개

부산 판매 권한
2개

이제 분할 중에도 서울은 자신의 3개 범위에서만 판매합니다.

부산은 2개까지만 판매합니다.

3 + 2
=
최대 5

총재고를 초과하지 않습니다.

이런 사고방식은 escrow 계열 설계와 연결해서 이해할 수 있습니다.


39장. 미리 권한을 나누면 가용성은 높아지지만 자유도는 줄어든다#

부산의 2개 재고 권한이 모두 소진됐다고 하겠습니다.

서울에는 아직 1개가 남아 있습니다.

하지만 네트워크가 끊겨 있습니다.

부산은 서울의 남은 1개를 즉시 가져다 사용할 수 없습니다.

즉:

분할 중 안전한 독립 처리

를 얻는 대신:

지역 간 자유로운 재고 이동

을 제한한 것입니다.

분산 설계는 이런 업무 제약의 재구성이기도 합니다.


40장. 요청 성공 여부와 클라이언트가 성공을 아는 것은 다르다#

주문 API에서 다음 순서가 발생했다고 하겠습니다.

1. 서버가 주문 저장
2. COMMIT
3. 성공 응답 전송
4. 네트워크 단절

클라이언트는 응답을 받지 못했습니다.

클라이언트 입장:

실패?

서버 입장:

성공

입니다.

이 상태를 반드시 처리해야 합니다.


41장. 타임아웃은 실패를 의미하지 않는다#

타임아웃에는 여러 가능성이 있습니다.

요청이 서버에 도착하지 않음

요청은 도착했지만 아직 처리 중

처리는 완료됨

COMMIT까지 완료됨

응답만 네트워크에서 유실됨

따라서:

timeout
=
실패

라고 단정할 수 없습니다.


42장. 같은 주문을 새 ID로 다시 보내면 중복이 만들어질 수 있다#

첫 주문:

order_id = O100

은 서버에서 성공했습니다.

클라이언트는 응답을 못 받았습니다.

재시도할 때:

order_id = O101

이라는 새 주문을 보내면 서버 입장에서는 별개의 두 주문입니다.

결과:

O100
성공

O101
성공

중복 결제가 발생할 수도 있습니다.


43장. 업무 요청에 안정적인 멱등 키를 사용할 수 있다#

클라이언트가 요청마다 다음 키를 생성한다고 하겠습니다.

request_id
=
PAY-20261001-000123

첫 요청이 성공하면 서버는 다음을 저장합니다.

request_id
→ 처리 완료
→ order_id O100

응답이 유실되어 같은 요청이 다시 들어오면:

새 주문 생성

이 아니라:

기존 O100 결과 반환

으로 처리할 수 있습니다.


44장. 멱등 키를 붙였다고 자동으로 중복 문제가 끝나는 것은 아니다#

다음도 설계해야 합니다.

동일 request_id가 동시에 두 번 들어오면?

첫 요청이 아직 처리 중이라면?

결과 저장 전 장애가 나면?

후속 결제 이벤트도 중복되지 않는가?

멱등 키는 얼마나 오래 보관하는가?

따라서 멱등성은 단순 컬럼 하나 추가로 끝나지 않습니다.


45장. 결과 확인 경로도 필요하다#

타임아웃이 발생하면 사용자가 같은 작업을 무조건 다시 만들기보다 기존 요청 상태를 조회할 수 있어야 합니다.

예:

GET /orders/by-request-id/PAY-20261001-000123

결과:

처리 중

성공

실패

확인 불가

등을 반환할 수 있습니다.

분산 환경에서는 결과 조회 경로가 재시도만큼 중요합니다.


46장. 결과 조회도 복제 최신성 문제를 만난다#

첫 요청은 Primary에서 성공했습니다.

하지만 상태 조회 API가 뒤처진 Replica를 읽는다고 하겠습니다.

Replica에는 아직 요청 기록이 없습니다.

Primary
request_id 존재

Replica
아직 없음

클라이언트는:

요청 없음

이라고 판단하고 다시 주문할 수 있습니다.

따라서 멱등성 상태 조회에서도 읽기 최신성 정책이 중요합니다.


47장. 2PC의 Prepared 상태와 클라이언트 성공 응답도 구분해야 한다#

주문 DB와 결제 DB가 모두 PREPARED입니다.

하지만 조정자가 아직 최종 COMMIT을 내리지 않았습니다.

클라이언트에게:

주문 완료

라고 응답하면 위험합니다.

준비 상태는 최종 확정 상태가 아닙니다.

PREPARED
≠
COMMITTED

입니다.


48장. 네트워크 장애는 한 요청을 네 가지 상태로 나눌 수 있다#

업무 요청을 다음처럼 생각하면 도움이 됩니다.

1. 요청 미도착

2. 처리 중

3. 서버에서는 완료됐지만 클라이언트는 모름

4. 클라이언트도 완료를 확인

이 가운데 3번이 가장 까다롭습니다.

분산 시스템에서는 이런 결과 불명 상태를 정상적인 장애 시나리오로 설계해야 합니다.


49장. 리더 기반 복제는 쓰기 순서를 단순하게 만들 수 있다#

여러 노드가 자유롭게 동시에 쓰는 대신 하나의 리더만 쓰기를 받는 구조를 사용할 수 있습니다.

flowchart TD
    C["Client"] --> L["Leader"]
    L --> R1["Replica 1"]
    L --> R2["Replica 2"]

장점은 쓰기 순서를 한 곳에서 정하기 쉽다는 것입니다.

하지만 리더와 통신할 수 없는 쪽에서는 새 쓰기를 받기 어려울 수 있습니다.


50장. 다중 리더 구조에서는 충돌 해결 책임이 증가할 수 있다#

지역 A와 지역 B가 각각 쓰기를 받는다고 하겠습니다.

분할 중 같은 고객 정보를 각각 수정했습니다.

A
전화번호 → 1111

B
전화번호 → 2222

네트워크가 복구되면 어느 값이 최종값이어야 할까요?

마지막 타임스탬프?

지역 우선순위?

사용자 병합?

두 값을 모두 보존?

충돌 해결 정책이 필요합니다.


51장. Last Write Wins는 간단하지만 데이터 의미를 잃을 수 있다#

최신 타임스탬프의 값만 남기는 방식을 생각할 수 있습니다.

A timestamp = 100

B timestamp = 105

→ B 채택

간단합니다.

하지만 A의 변경이 업무적으로 더 중요했더라도 사라질 수 있습니다.

또 분산 시스템에서 시계 자체가 완벽하게 일치한다고 가정하기도 어렵습니다.

따라서 LWW는 편리하지만 모든 데이터에 적합하지 않습니다.


52장. 모든 충돌을 자동 병합할 수 있는 것도 아니다#

쇼핑카트 상품 목록처럼 집합 병합이 가능한 데이터가 있습니다.

반면 은행 잔액처럼 단순히 두 값을 합치면 안 되는 데이터도 있습니다.

A 잔액
80

B 잔액
70

두 값을:

150

으로 합칠 수는 없습니다.

데이터 의미에 따라 충돌 해결 방식이 달라져야 합니다.


53장. 분산 설계는 “무엇을 잃으면 안 되는가”에서 시작해야 한다#

예를 들어 상품 추천 조회는 약간 오래된 결과를 허용할 수 있습니다.

반면:

재고 초과 판매

중복 결제

계좌 이중 출금

은 허용하기 어려울 수 있습니다.

따라서 모든 데이터에 동일한 강한 일관성을 적용하기보다 업무 중요도에 따라 다르게 설계할 수 있습니다.


54장. 정족수를 설계할 때 체크해야 할 것#

단순히:

R + W > N

만 계산하고 끝내지 않습니다.

다음도 확인해야 합니다.

  1. 어떤 값을 최신으로 판단하는가?
  2. 동시 쓰기가 발생하면 어떻게 해결하는가?
  3. 실패 노드가 복귀할 때 어떻게 동기화하는가?
  4. 읽기 복구가 있는가?
  5. 쓰기 성공은 수신·저장·적용 중 어디까지를 뜻하는가?
  6. 분할 중 어느 쪽이 쓰기를 허용하는가?
  7. 복제 지연은 어느 정도인가?
  8. 승격 시 이미 성공 응답한 데이터를 잃을 수 있는가?

55장. 2PC를 설계할 때 체크해야 할 것#

  1. 조정자는 어디에 있는가?
  2. 조정자 결정은 내구성 있게 기록되는가?
  3. 참가자가 PREPARED 상태에서 장애 나면 어떻게 복구하는가?
  4. prepared transaction이 얼마나 오래 남을 수 있는가?
  5. 관련 잠금과 자원은 무엇인가?
  6. 조정자와 통신이 끊기면 누가 최종 결정을 확인하는가?
  7. 참가자가 재시작한 뒤 미완료 거래를 어떻게 찾는가?
  8. 운영자가 수동으로 처리할 절차가 있는가?

56장. CAP를 평가할 때는 실제 요청을 하나씩 적는 것이 좋다#

“우리 시스템은 AP입니다”보다 다음처럼 쓰는 것이 더 유용합니다.

네트워크가 서울·부산으로 분리됐을 때

서울 주문 쓰기
→ 성공

부산 주문 쓰기
→ 실패

부산 상품 조회
→ 오래된 값 허용

이렇게 하면 실제 시스템 동작이 명확해집니다.


57장. 분할 중 읽기와 쓰기의 정책을 표로 만들어 보자#

예:

요청 정상 상태 네트워크 분할
주문 생성 성공 리더 접근 가능 지역만 성공
상품 설명 조회 성공 각 지역 캐시·Replica 응답 허용
재고 확정 정족수 확인 정족수 미달이면 실패
추천 목록 모든 지역 응답 오래된 결과 허용
결제 결과 조회 최신값 필요 확정 상태 확인 불가 시 대기

이런 표가 제품 분류보다 실제 설계에 더 도움이 됩니다.


58장. 분산 시스템에서 “성공”을 단계별로 정의해야 한다#

예를 들어 주문 완료를 다음 중 어디에서 사용자에게 알려줄지 정해야 합니다.

주문 DB 저장 완료

결제 DB PREPARE 완료

모든 참가자 COMMIT 결정 완료

복제본 2곳 기록 완료

이벤트 큐 발행 완료

어떤 단계를 성공 기준으로 선택하느냐에 따라 장애 시 결과가 달라집니다.


59장. 사용자 화면도 결과 불명 상태를 표현할 수 있어야 한다#

분산 환경에서는 성공과 실패만 있는 것이 아닙니다.

예를 들어:

주문 처리 중입니다.

결제 결과를 확인하고 있습니다.

잠시 후 주문 내역에서 상태를 확인해 주세요.

같은 상태가 필요할 수 있습니다.

백엔드가 결과를 모르는 순간에 프론트엔드가 무조건:

실패

라고 표시하면 사용자의 중복 재시도를 유발할 수 있습니다.


60장. 재시도 정책도 계층별로 다르다#

DB 연결 오류:

짧은 재시도 가능

직렬화 실패:

전체 트랜잭션 재시도

2PC prepared 상태:

임의 재실행보다 최종 결정 확인

클라이언트 타임아웃:

request_id로 기존 처리 상태 확인

모든 오류에 같은 재시도 코드를 적용하면 중복과 데이터 불일치를 만들 수 있습니다.


61장. 분산 장애를 시간표로 적으면 문제를 이해하기 쉽다#

예:

시각 클라이언트 주문 DB 결제 DB
1 주문 요청 저장 시작 결제 시작
2 대기 PREPARED PREPARED
3 COMMIT 결정 COMMIT 결정
4 연결 끊김 COMMIT 완료 COMMIT 완료
5 timeout 표시 완료 완료
6 같은 요청 재전송 기존 request_id 확인 기존 결과 확인

이 표를 보면:

서버 완료

와:

클라이언트 확인

사이에 간격이 있다는 것을 쉽게 알 수 있습니다.


62장. 분산 데이터베이스 설계 체크리스트#

네트워크 분할#

노드 간 통신이 끊기면
어떤 읽기·쓰기를 허용하는가?

최신성#

오래된 값을 반환해도 되는 업무인가?

복제#

몇 개 복제본까지 확인해야 성공인가?

정족수#

R과 W 집합이 어떻게 겹치는가?

충돌#

동시 쓰기를 어떻게 판정하고 병합하는가?

분산 트랜잭션#

여러 DB 변경이 반드시 함께 성공해야 하는가?

준비 상태#

2PC 조정자가 사라지면 prepared 거래를 어떻게 처리하는가?

재시도#

타임아웃 후 같은 업무인지 식별할 수 있는가?

사용자 경험#

결과 불명 상태를 화면에 어떻게 표시하는가?

63장. 핵심 정리#

분산 데이터베이스에서 가장 어려운 문제는 노드가 여러 개 있다는 사실 자체가 아닙니다.

노드는 살아 있지만 서로 통신할 수 없는 순간에 어떤 요청을 성공으로 인정할 것인가가 핵심입니다.

CAP는 이 상황에서:

최신 상태를 일관되게 보여줄 것인가?

분할된 노드에서도 계속 응답할 것인가?

라는 선택을 설명하는 이론적 틀입니다.

하지만 CAP만으로 모든 분산 문제를 설명할 수는 없습니다.

정상 상태에서도 복제 확인을 더 많이 기다리면 지연 시간이 늘 수 있으므로 PACELC와 같은 관점도 함께 볼 수 있습니다.

2PC는 다른 질문을 다룹니다.

주문 DB

결제 DB

처럼 여러 참여자가 하나의 업무 트랜잭션을 함께 확정해야 할 때:

Prepare

↓

Commit 또는 Rollback 결정

을 조정합니다.

여기서 중요한 점은:

PREPARED
≠
COMMITTED

라는 것입니다.

모든 참여자가 준비를 완료한 뒤 조정자가 장애 나면 최종 결정을 기다리며 블로킹될 수 있습니다.

복제 정족수에서는:

N
전체 복제본

W
쓰기 응답 수

R
읽기 응답 수

를 이용해 요청 범위를 정합니다.

예:

N = 3
W = 2
R = 2

R + W = 4 > 3

이면 읽기 집합과 쓰기 집합이 최소 한 복제본에서 겹칩니다.

그러나 이것만으로 모든 읽기가 자동으로 최신이라는 뜻은 아닙니다.

버전 판단

동시 쓰기 충돌

장애 노드 복귀

최신값 선택

규칙도 함께 필요합니다.

그리고 분산 시스템에서 반드시 별도로 설계해야 할 것이 결과 불명 상태입니다.

서버에서는 COMMIT 완료

하지만

클라이언트는 성공 응답을 받지 못함

이 상황에서 같은 요청을 새로운 주문으로 다시 보내면 중복 주문이나 중복 결제가 발생할 수 있습니다.

그래서 안정적인 업무 요청 ID와 멱등 처리, 기존 처리 결과를 조회하는 경로가 필요합니다.

분산 시스템을 설계할 때 가장 중요한 질문은 다음과 같습니다.

네트워크가 끊긴 순간에도 어떤 업무는 반드시 성공해야 하고, 어떤 업무는 실패시켜야 하며, 어떤 업무는 오래된 데이터를 보여줘도 되는가?

그리고 두 번째 질문은 이것입니다.

응답을 받지 못한 요청을 다시 보내도 같은 업무가 두 번 처리되지 않는가?

이 두 질문을 실제 장애 시간표로 설명할 수 있어야 CAP·2PC·정족수·복제라는 개념이 단순한 이론 용어가 아니라 운영 가능한 분산 시스템 설계로 연결됩니다.

이 페이지의 목차