7분

에릭 브루어의 CAP 사고: 분산 시스템이 트레이드오프를 택하는 이유

에릭 브루어의 CAP 정리를 실용적 멘탈 모델로 배우고, 일관성·가용성·분할이 분산 시스템 설계 결정에 어떻게 영향을 주는지 이해하세요.

에릭 브루어의 CAP 사고: 분산 시스템이 트레이드오프를 택하는 이유

왜 CAP가 대표적인 사고 도구가 되었나

같은 데이터를 둘 이상의 머신에 저장하면 속도와 내결함성을 얻지만, 동시에 새로운 문제가 생깁니다: 불일치. 두 서버가 서로 다른 업데이트를 받거나 메시지가 늦게 도착하거나 아예 도달하지 않을 수 있으며, 사용자는 어떤 복제본을 만났느냐에 따라 다른 응답을 볼 수 있습니다. CAP는 엔지니어가 그런 지저분한 현실을 형식화해 말할 수 있게 해주기 때문에 널리 퍼졌습니다.

에릭 브루어(컴퓨터 과학자이자 Inktomi 공동 창업자)는 2000년에 실패가 있는 복제 시스템에 대한 실용적 진술로 핵심 아이디어를 소개했습니다. 이 모델은 프로덕션에서 팀들이 이미 경험하던 상황과 잘 맞았기 때문에 빠르게 확산되었습니다: 분산 시스템은 단순히 꺼지는 방식으로 실패하지 않습니다; **분리(split)**로 실패합니다.

CAP는 기능 목록이 아니라 실패를 보는 관점이다

CAP는 특히 네트워크가 이상하게 동작할 때, 즉 상황이 나빠졌을 때 유용합니다. 정상적인 날에는 많은 시스템이 충분히 일관적이고 가용해 보일 수 있습니다. 진짜 시험대는 머신들이 안정적으로 통신하지 못할 때이며, 그럴 때 읽기와 쓰기를 어떻게 처리할지 결정해야 합니다.

이 관점이 CAP를 보편적 멘탈 모델로 만든 이유입니다: CAP는 ‘최고의 관행’에 대해 주장하지 않습니다; 대신 구체적인 질문을 던집니다—분할이 일어났을 때 무엇을 희생할 것인가?

이 글을 읽고 나면 할 수 있는 것

이 글을 다 읽고 나면 다음을 할 수 있어야 합니다:

  • 진정한 CAP 상황(복제 + 통신 단절 가능성)을 인식하기
  • 복제본이 동의하지 못할 때 시스템이 의도적으로 일관성(모두가 같은 진실을 보게 하기) 또는 가용성(시스템이 계속 응답하기) 중 무엇을 우선할지 선택하기
  • 그 선택을 제품 영향과 연결하기: 사용자가 경험하는 것, 노출할 오류, 분할 후에 필요한 복구 방식 등

CAP가 지속적으로 사용되는 이유는 “분산은 어렵다”는 모호한 이야기를 방어할 수 있는 결정으로 바꿔주기 때문입니다.

설정: 복제와 불일치 문제

분산 시스템은 쉽게 말해 많은 컴퓨터가 하나처럼 행동하려고 하는 것입니다. 여러 서버가 다른 랙, 리전, 클라우드 존에 있을 수 있지만 사용자에게는 ‘앱’이나 ‘데이터베이스’ 하나로 보입니다.

왜 데이터를 복제하나

실무 규모에서 공유 시스템을 운영하려면 보통 복제를 합니다: 여러 머신에 동일한 데이터 사본을 둡니다.

복제는 실용적으로 세 가지 이점을 제공합니다:

  • 확장: 더 많은 머신이 더 많은 트래픽을 처리할 수 있음
  • 성능: 근접한 복제본이 사용자에게 서비스를 제공해 지연을 줄임
  • 신뢰성: 한 머신이 죽어도 다른 복제본이 서비스를 유지함

여기까지는 복제가 명백한 이득처럼 보입니다. 문제는 복제가 새로운 과업을 만든다는 점입니다: 모든 사본을 일치시키는 것입니다.

핵심 긴장: 사본들이 불일치할 수 있다

모든 복제본이 항상 즉시 서로 통신할 수 있다면 업데이트를 조정하고 상태를 일치시킬 수 있습니다. 하지만 현실의 네트워크는 완벽하지 않습니다. 메시지는 지연되거나 버려지거나 실패를 피해 우회될 수 있습니다.

통신이 정상일 때는 복제본들이 보통 업데이트를 교환하고 수렴합니다. 하지만 통신이 끊기면(일시적이라도) 두 개의 ‘정답처럼 보이는’ 버전이 존재할 수 있습니다.

예를 들어 사용자가 배송 주소를 바꿨습니다. 복제본 A는 업데이트를 받았고 복제본 B는 받지 못했습니다. 이제 시스템은 단순해 보이는 질문에 답해야 합니다: 현재 주소는 무엇인가?

정상 동작 대 실패 시 동작

이것은 다음의 차이입니다:

  • 정상 동작: 복제본들이 조정 가능; 불일치는 주로 시간 차이 문제
  • 실패 동작: 일부 복제본이 통신 불가; 불일치가 불가피함

CAP 사고는 바로 여기에서 시작합니다: 복제가 존재하면 통신 실패 시 불일치는 예외가 아니라 핵심 설계 문제가 됩니다.

평범한 영어로 CAP: C, A, P

CAP는 시스템이 여러 머신(대개 여러 위치)에 걸쳐 있을 때 사용자가 실제로 느끼는 것을 설명하는 멘탈 모델입니다. 이것은 ‘좋은’ 시스템이나 ‘나쁜’ 시스템을 판정하려는 모델이 아니라, 관리해야 할 긴장을 보여줍니다.

일관성(C): 최신 쓰기를 보나?

일관성은 합의에 관한 것입니다. 무엇을 업데이트했을 때, 그 다음 읽기(어디에서든)가 그 업데이트를 반영할까요?

사용자 관점에서는 “방금 바꿨고 모두가 새로운 값을 본다” vs “몇몇은 한동안 옛 값을 본다”의 차이입니다.

가용성(A): 응답을 받을 수 있나?

가용성은 시스템이 요청(읽기와 쓰기)에 대해 성공 결과로 응답하는 것을 의미합니다. ‘가장 빠른’ 것은 아니더라도 ‘요청을 거부하지 않는 것’입니다.

문제가 생겼을 때(서버 다운, 네트워크 문제 등) 가용한 시스템은 요청을 계속 받아들이지만, 다소 오래된 데이터를 반환할 수 있습니다.

분할 허용(P): 노드들이 대화하지 못하면?

분할은 네트워크가 갈라지는 상황입니다: 머신들은 동작 중이지만 일부 사이의 메시지가 전달되지 않거나 너무 늦게 도착합니다. 분산 시스템에서는 이를 불가능한 상황으로 간주할 수 없습니다—분할이 일어났을 때 어떻게 동작할지 정의해야 합니다.

간단한 이야기: 두 상점, 하나의 재고

같은 상품을 파는 두 소매점이 있고 “재고 1개”를 공유한다고 합시다. 고객이 상점 A에서 마지막 상품을 샀고 A는 inventory = 0을 기록했습니다. 동시에 네트워크 분할로 상점 B는 그 소식을 못 들었습니다.

상점 B가 가용성을 유지하면 분할된 상태에서 재고가 없는 상품을 팔 수 있습니다(판매를 수락). 상점 B가 일관성을 고수하면 최신 재고를 확인할 수 있을 때까지 판매를 거부할 수 있습니다(분할 동안 서비스 거부).

분할이 실제로 무엇인지(그리고 무시할 수 없는 이유)

‘분할’은 단순히 ‘인터넷이 나갔다’는 뜻이 아닙니다. 시스템의 일부가 서로 신뢰성 있게 대화하지 못하는 모든 상황을 의미합니다—각 부분은 여전히 정상 동작하는 것처럼 보일 수 있습니다.

복제 시스템에서 노드들은 끊임없이 메시지를 교환합니다: 쓰기, 확인 응답, 하트비트, 리더 선출, 읽기 요청 등. 분할은 그 메시지들이 도착하지 않거나(혹은 의미 있게 늦게 도착하는) 상황인데, 이때 현실에 대한 불일치가 생깁니다: “그 쓰기가 발생했나?” “누가 리더인가?” “노드 B는 살아 있는가?”

분할은 통신 실패다

통신 실패는 다음처럼 지저분하고 부분적으로 일어납니다:

  • 재시도와 타임아웃을 유발하는 패킷 손실
  • 트래픽이 긴 우회로로 가거나 블랙홀로 빠지는 라우팅 문제
  • 링크 과부하(또는 NIC 포화)로 인한 긴 지연
  • 특정 포트나 방향만 차단하는 잘못된 방화벽/보안 그룹 설정
  • 노드 간 발견을 방해하는 DNS 혹은 서비스 디스커버리 문제

중요한 점: 분할은 종종 성능 저하이지 깔끔한 켜짐/꺼짐이 아닙니다. 애플리케이션 관점에서 ‘충분히 느린’ 것은 ‘꺼진’ 것과 구분이 안 됩니다.

대규모에서 분할은 필연적이다

더 많은 머신, 더 많은 네트워크, 더 많은 리전, 더 많은 움직이는 부품을 추가할수록 통신 실패의 기회는 늘어납니다. 개별 컴포넌트가 신뢰할 수 있더라도, 전체 시스템은 더 많은 의존성과 노드 간 조정을 가지므로 실패를 겪게 됩니다.

정확한 실패율을 가정할 필요는 없습니다: 시스템이 충분히 오래 운영되고 인프라가 충분히 분산되어 있다면 분할이 발생합니다.

실무에서 ‘분할 허용’이 의미하는 것

분할 허용은 시스템이 분할 동안에도 동작을 유지하도록 설계되었다는 뜻입니다—노드들이 합의하지 못하거나 상대가 무엇을 봤는지 확인하지 못하더라도요. 그건 선택을 강요합니다: 요청을 계속 처리해 불일치를 감수할 것인가, 아니면 일부 요청을 중단·거부해 일관성을 지킬 것인가.

핵심 순간: 분할 동안 일관성 또는 가용성 선택하기

복제를 한 이상, 분할은 단순한 통신 단절입니다: 시스템의 두 부분이 일정 시간 동안 신뢰성 있게 대화할 수 없습니다. 복제본은 여전히 실행 중이고, 사용자는 여전히 버튼을 누르며, 서비스는 요청을 받지만 복제본들은 최신 진실에 대해 합의하지 못합니다.

이것이 CAP 긴장의 한 문장 요약입니다: 분할 동안에는 일관성(C) 또는 가용성(A) 중 무엇을 우선할지 선택해야 한다. 둘을 동시에 얻을 수는 없습니다.

일관성(C)을 선택하면

“응답성보다 정확함을 택하겠다”는 뜻입니다. 시스템이 어떤 요청이 모든 복제본을 동기화할지 확인할 수 없을 때, 그 요청은 실패하거나 기다려야 합니다.

실무 효과: 일부 사용자는 오류, 타임아웃 또는 ‘다시 시도’ 메시지를 보게 됩니다—특히 데이터 변경 연산에서. 이는 두 번 결제되는 것을 피하거나 좌석을 초과 판매하는 것을 막고자 할 때 흔히 선택되는 방식입니다.

가용성(A)을 선택하면

“막지 않고 응답하겠다”는 뜻입니다. 분할 양쪽 모두 요청을 계속 받아들일 것이고, 조정할 수 없을 때도 응답을 반환합니다.

실무 효과: 사용자는 성공 응답을 받지만 읽는 데이터는 오래된 것일 수 있고 동시 업데이트는 충돌할 수 있습니다. 이후에 병합 규칙, 마지막 쓰기 승리(LWW), 수동 검토 등으로 정리해야 합니다.

선택은 연산마다 다를 수 있다

이것은 항상 전역 설정 한 가지가 아닙니다. 많은 제품이 전략을 섞습니다:

  • 읽기 대 쓰기: 읽기는 가용하게 유지하고 쓰기는 더 엄격하게 둠
  • 중요 대 비중요 동작: 금전, 식별, 재고와 같은 중요한 동작에는 일관성을 강제하고, 피드·분석·좋아요 같은 덜 중요한 동작에는 가용성을 허용

핵심 순간은 연산별로 ‘지금 사용자를 차단하는 것’과 ‘나중에 충돌을 고치는 것’ 중 어느 쪽이 더 나쁜지를 결정하는 것입니다.

흔한 오해들: “둘 중 두 개 골라라”를 넘어서

프로덕션 유사 환경에서 동작 확인
프로토타입을 배포해 타임아웃과 폴백이 체감 가용성에 미치는 영향을 관찰하세요.

슬로건 “둘 중 두 개를 골라라”는 기억하기 쉬우나, 사람들이 CAP를 영구적인 세 가지 기능의 메뉴로 잘못 이해하게 만듭니다. CAP는 네트워크가 협력하지 않을 때 어떤 일이 벌어지는지에 관한 것입니다: 분할(또는 분할처럼 보이는 상황) 동안 분산 시스템은 일관된 답을 반환할지 아니면 모든 요청에 대해 계속 가용할지를 선택해야 합니다.

오해 1: “나는 C와 A를 골라 분할을 피하겠다”

현실 분산 시스템에서는 분할을 비활성화할 수 있는 설정이 없습니다. 시스템이 머신, 랙, 존, 리전을 가로지르면 메시지는 지연되거나 버려지거나 재정렬되거나 이상하게 라우팅될 수 있습니다. 소프트웨어 관점에서 보면 이는 노드들이 충분히 조정할 수 없게 되는 ‘분할’입니다.

물리적 네트워크가 정상이더라도 다른 실패가 같은 효과를 냅니다—과부하된 노드, GC 일시 중단, 시끄러운 이웃, DNS 문제, 불안정한 로드밸런서 등. 결과는 동일합니다: 시스템의 일부가 다른 일부와 충분히 잘 통신하지 못할 수 있습니다.

오해 2: “분할은 드문 엣지 케이스다”

애플리케이션은 분할을 깔끔한 이진 이벤트로 경험하지 않습니다. 대신 지연 스파이크와 타임아웃으로 경험합니다. 요청이 200ms에서 201ms로 도착했건 아예 도달하지 않았건 상관없이, 앱은 다음에 무엇을 할지 결정해야 합니다. 애플리케이션 관점에서 느린 통신은 종종 고장과 구분이 되지 않습니다.

오해 3: “시스템은 CP 아니면 AP다”

많은 실제 시스템은 구성과 운영 조건에 따라 대부분 일관적이거나 대부분 가용으로 보입니다. 타임아웃, 재시도 정책, 쿼럼 크기, ‘내 쓰기 읽기(read-your-writes)’ 옵션 등이 동작을 바꿀 수 있습니다.

정상 조건에서는 데이터베이스가 강한 일관성처럼 보일 수 있고, 스트레스나 리전 간 문제에서는 요청을 거부하기 시작(일관성 우선)하거나 오래된 데이터를 반환(가용성 우선)할 수 있습니다.

CAP는 제품을 라벨링하는 것보다, 특히 지연 때문에 발생하는 불일치가 생겼을 때 당신이 어떤 트레이드를 하고 있는지 이해하도록 돕습니다.

실질적으로 선택할 수 있는 일관성 옵션들

CAP 토론은 일관성을 이분법적으로 보이게 만들기 쉽습니다: ‘완전’ 아니면 ‘무제한’. 실제 시스템은 불일치 시 사용자 경험이 다른 여러 보장을 제공합니다.

강한 일관성(그리고 실패 시 대가)

강한 일관성(종종 ‘선형화 가능(linearizable)’으로 표현)은 쓰기가 인지되면 이후의 모든 읽기가 그 쓰기를 반환한다는 뜻입니다.

대가: 분할이나 복제본 소수가 닿지 않는 경우 시스템은 읽기/쓰기 지연 또는 거부를 통해 충돌을 피할 수 있습니다. 사용자는 타임아웃, ‘다시 시도’ 또는 일시적 읽기 전용 상태로 이를 인지합니다.

최종 일관성(사용자가 겪는 현상)

최종 일관성은 신규 업데이트가 없으면 모든 복제본이 결국 수렴한다고 보장합니다. 하지만 두 사용자가 동시에 읽을 때 같은 값을 보장하지는 않습니다.

사용자가 체감하는 것: 최근에 바꾼 프로필 사진이 ‘되돌아간다’, 카운터가 지연된다, 방금 보낸 메시지가 다른 기기에서 잠깐 보이지 않는다 등입니다.

유용한 중간 보장들

완전한 강한 일관성 없이도 더 나은 경험을 제공할 수 있습니다:

  • read-your-writes: 사용자가 업데이트한 이후 자신은 더 이상 이전 버전을 보지 않음
  • monotonic reads: 한 번 버전 N을 본 이후에 버전 N-1로 돌아가지 않음
  • 인과 일관성(causal consistency): 이벤트 B가 A에 의존하면 모두가 A를 먼저 보고 B를 봄

이 보장들은 사람들이 생각하는 방식(“내 변경이 사라지지 마라”)과 잘 맞고 부분 장애 동안 유지하기도 비교적 쉽습니다.

기대에 따른 일관성 레벨 선택

전문 용어가 아니라 사용자 약속에서 시작하세요:

  • 잘못된 읽기가 되돌릴 수 없는 피해를 만든다면(돈 이동, 재고 예약, 권한 변경) 강한 일관성을 택하고 일시적 비가용을 감수하세요.
  • 단기간 불일치가 용인되는 기능(좋아요, 조회 수, 피드 랭킹)은 최종 또는 인과 일관성이 적합합니다.
  • 핵심 고통이 개인적 혼란이라면(“저장했는데 왜 안 보이지?”) read-your-writesmonotonic reads를 우선하세요.

일관성은 제품 선택입니다: 사용자에게 ‘틀림’을 어떻게 정의할지 정하고, 그 잘못을 막는 데 필요한 가장 약한 보장을 선택하세요.

가용성: 단순한 업타임 숫자가 아니라 제품 결정

두려움 없이 반복하세요
실패 시나리오를 실험하고, 문제가 생기면 빠르게 롤백하세요.

CAP에서의 가용성은 자랑할 만한 지표(‘파이브 나인’ 등)가 아니라 네트워크 불확실성 시 사용자에게 어떤 동작을 약속하는지입니다.

빠른 성공 vs 정확한 성공

복제본이 합의하지 못할 때 종종 선택은:

  • 빠른 성공: 뭔가를 빠르게 반환(오래된 값일 수 있음)
  • 정확한 성공: 답이 최신임을 증명할 수 있을 때만 반환

사용자는 이를 ‘앱이 작동한다’ vs ‘앱이 맞다’로 느낍니다. 어느 쪽이 낫다고 일반화할 수 없으며, ‘틀림’이 무엇을 의미하는지에 따라 결정됩니다. 사회형 피드의 약간 오래된 내용은 짜증나지만, 계좌 잔액이 오래된 것은 해로울 수 있습니다.

‘닫힌 실패’ vs ‘열린 실패’

불확실성 상황에서 두 가지 행동 양식이 흔히 보입니다:

  • 닫힌 실패(fail closed): 요청을 거부(오류, 타임아웃, 읽기 전용). 정확성을 지키지만 사용자가 차단됨.
  • 열린 실패(fail open): 최선의 응답 제공(캐시 데이터, 로컬 복제본, 큐잉된 쓰기). 흐름을 유지하지만 불일치 가능성 존재.

이는 순수 기술 결정이 아니라 정책 결정입니다. 제품이 무엇을 보여줄 수 있고 무엇은 절대 추측해서는 안 되는지를 정의해야 합니다.

부분적 가용성도 가용성이다

가용성은 거의 모든 경우 전부 아니면 전무가 아닙니다. 분할 동안 부분적 가용성이 발생할 수 있습니다: 특정 리전·네트워크·사용자 그룹에서는 성공하고 다른 곳은 실패합니다. 이는 의도적인 설계(로컬 복제본이 건강하면 서비스 유지)일 수도 있고 우발적(라우팅 불균형, 쿼럼 도달 불균형)일 수도 있습니다.

저하 모드(Degraded mode): 핵심을 지키고 위험을 줄이기

현실적인 중간 지점은 저하 모드입니다: 위험한 동작을 제한하면서 안전한 동작은 계속 제공합니다. 예를 들어 검색과 탐색은 허용하되 ‘자금 이체’, ‘비밀번호 변경’ 같은 작업은 일시적으로 비활성화하는 방식입니다.

구체적 사례: CAP 선택을 사용 사례에 맞추기

CAP는 추상적으로 느껴지지만, 네트워크 분할 동안 사용자가 무엇을 경험할지를 매핑하면 실용적 결정을 내리기 쉽습니다: 응답을 계속할 것인가, 아니면 충돌을 피하기 위해 멈출 것인가?

재고와 주문: 초과 판매 위험 vs 체크아웃 중단

두 데이터센터가 서로 연락하지 못하는 동안 주문을 허용하면

  • 가용성 유지 시: 각 측이 ‘마지막 상품’을 팔아 초과 판매가 발생할 수 있음. 낮은 위험 상품에서는 백오더나 사과로 처리 가능하지만 한정판에서는 치명적일 수 있음.
  • 일관성 우선 시: 전역 재고를 확인할 수 없으면 주문을 차단할 수 있음. 사용자는 ‘나중에 다시 시도하세요’ 같은 메시지를 보게 되지만 초과 판매를 피함.

결제와 잔액: 정확성 우선 패턴(그 이유)

금전은 잘못되었을 때 비용이 큰 클래식 영역입니다. 분할 중 두 복제본이 각각 인출을 받아들인다면 계정은 음수가 될 수 있습니다.

이런 곳에서는 중요한 쓰기에 대해 일관성 우선을 택하는 경우가 많습니다: 최신 잔액을 확인할 수 없으면 동작을 거부·지연합니다. 가용성 일부를 포기하더라도 정확성, 감사 가능성, 신뢰를 지키는 편이 낫습니다.

채팅·피드·분석: 약간 오래된 데이터 허용 가능

채팅과 소셜 피드에서는 사용자가 몇 초 정도의 불일치를 흔히 용인합니다: 메시지가 몇 초 늦게 도착하거나 좋아요 수가 지연되는 식입니다.

이 경우 가용성을 설계하는 것이 좋은 제품 선택일 수 있으며, 어떤 요소가 ‘결국 일치’할 것인지 명확히 하고 업데이트를 깔끔하게 병합할 수 있는 설계가 필요합니다.

요점: 당신의 트레이드오프는 비즈니스 결정이다

'옳지 않음'의 비용(환불, 법적 노출, 사용자 신뢰 손상, 운영 혼란)에 따라 적절한 CAP 선택이 달라집니다. 일시적 불일치를 허용할 수 있는 부분과 절대 실패할 수 없는 부분을 구분하세요.

선택을 구현하는 설계 패턴들

분할 시 무엇을 할지 결정했다면, 그 결정을 실제로 구현하는 메커니즘이 필요합니다. 다음 패턴들은 데이터베이스, 메시지 시스템, API 전반에 걸쳐 많이 등장합니다—제품이 ‘CAP’라는 단어를 쓰지 않더라도요.

쿼럼: 과반수 합의

쿼럼은 단순히 ‘복제본의 과반이 동의’하는 것입니다. 예: 사본이 5개라면 과반은 3개입니다.

읽기/쓰기에서 과반을 요구하면 오래된 데이터를 반환하거나 충돌을 발생시킬 확률을 줄입니다. 예를 들어 쓰기가 3개의 복제본 확인을 받아야 한다면, 두 개의 분리된 그룹에서 서로 다른 ‘진실’을 받아들이기 어려워집니다.

대가는 속도와 도달 범위입니다: 과반에 닿을 수 없으면(분할·장애로 인해) 시스템이 연산을 거부할 수 있습니다—일관성을 택하는 경우입니다.

타임아웃·재시도·백오프가 체감 가용성 형성

많은 ‘가용성’ 문제는 하드 실패가 아니라 느린 응답입니다. 짧은 타임아웃은 시스템을 빠르게 느끼게 하지만 느린 성공을 실패로 판단할 가능성도 키웁니다.

재시도는 일시적 실패를 회복시키지만 과도한 재시도는 이미 힘든 서비스에 부하를 줍니다. 백오프(재시도 간 대기 시간 증가)와 지터(무작위성)는 재시도들이 트래픽 스파이크로 변하는 것을 막습니다.

핵심은 약속에 맞춰 설정하는 것입니다: “항상 응답”을 우선하면 더 많은 재시도와 폴백을 설계하고, “절대 거짓말하지 않음”을 우선하면 엄격한 한계와 명확한 오류를 둡니다.

분기 허용 시 충돌 처리

분할 동안 가용성을 유지하면 복제본들이 서로 다른 업데이트를 받아 나중에 조정해야 합니다. 일반적인 접근법:

  • 마지막 쓰기 승리(LWW): 최신 타임스탬프를 가진 업데이트를 선택. 단순하지만 시계 불일치로 유효한 변경을 덮어쓸 수 있음.
  • 버전 벡터: 업데이트가 동시인지 또는 선후인지 판단하는 작은 ‘이력’ 첨부.
  • 병합 규칙: 변경 합치기 정의(예: 장바구니 항목은 합집합; 카운터는 더하기; 프로필은 비어 있지 않은 필드 우선). 데이터 모델 설계 단계에서 이런 규칙을 넣으면 효과적임.

멱등성: 재시도를 안전하게 만들기

재시도는 중복을 만들 수 있습니다: 카드가 이중 결제되거나 같은 주문이 두 번 접수되는 식. 멱등성은 이를 방지합니다.

일반적 패턴은 요청 ID인 멱등성 키를 함께 보내는 것입니다. 서버는 첫 결과를 저장하고 동일한 키의 반복 요청에는 같은 결과를 반환합니다—그래서 재시도로 가용성을 높여도 데이터가 망가지지 않습니다.

실무에서 CAP 가정을 검증하는 방법

가용성 우선 설계 탐색
재시도, 큐된 쓰기, 이후 동기화 경로로 AP 스타일 경험을 설계하세요.

많은 팀이 화이트보드에서 CAP 입장을 정한 뒤 프로덕션에서 스트레스가 걸릴 때 시스템이 다르게 동작하는 것을 발견합니다. 검증이란 의도적으로 CAP 트레이드오프가 드러나는 조건을 만들고 시스템이 설계대로 반응하는지 확인하는 것입니다.

안전하게 분할을 테스트하라

케이블이 실제로 끊기기를 기다릴 필요는 없습니다. 스테이징(그리고 신중히는 프로덕션)에서 제어된 장애 주입을 사용해 분할을 시뮬레이션하세요:

  • 특정 서비스나 노드 간 트래픽을 블랙홀 처리해 조용한 분할을 흉내
  • 리플리카/리전 간 포트나 보안 규칙을 차단해 링크를 끊기
  • 극단적 지연과 패킷 손실을 주입해 타임아웃·재시도가 분할처럼 작동하게 함
  • 리더 고립을 유도해(예: 프라이머리를 쿼럼에서 고립) 시스템이 ‘일관성 우선’인지 ‘가용성 우선’인지 관찰

목표는 구체적 질문에 답하는 것입니다: 쓰기는 거부되나 허용되나? 읽기는 오래된 값을 반환하나? 자동 복구는 어떻게 작동하고 재병합에는 얼마나 걸리나?

초기에 이런 행동을 검증하고 싶다면 현실성 있는 프로토타입을 빠르게 띄우는 것도 도움이 됩니다. 예를 들어 팀들은 Koder.ai를 사용해 작고 현실적인 서비스(주로 Go 백엔드 + PostgreSQL과 React UI)를 생성하고 재시도·멱등성 키·디그레이드 모드 같은 동작을 샌드박스에서 실험하곤 합니다.

CAP 문제를 드러내는 모니터 신호들

전통적 업타임 체크는 ‘가용하지만 틀린’ 행동을 잡아내지 못합니다. 다음을 추적하세요:

  • 연산 유형별 오류율(읽기 vs 쓰기 vs 조건부 업데이트)
  • 오래된 읽기 지표(read-your-writes 위반, 버전/ETag 불일치, 지연 메트릭)
  • 복제본 불일치(복제 지연, 적용 실패 카운트, 충돌률)
  • 타임아웃·재시도(대개 분할의 첫 신호)

런북과 사용자 커뮤니케이션

운영팀은 분할이 발생했을 때 언제 쓰기 동결, 언제 폴오버, 언제 기능 저하를 할지, 그리고 재병합 안전성을 어떻게 검증할지를 미리 결정해두어야 합니다.

또한 사용자에게 보일 메시지도 계획하세요. 일관성을 선택하면 “지금 업데이트를 확인할 수 없습니다—다시 시도하세요.” 같은 문구가 될 수 있고, 가용성을 선택하면 “업데이트가 전역에 반영되는 데 몇 분 걸릴 수 있습니다.”처럼 명확히 알려주는 편이 좋습니다. 명확한 문구는 지원 부담을 줄이고 신뢰를 유지합니다.

일상적 시스템 결정용 실용 CAP 체크리스트

시스템 결정을 할 때 CAP는 ‘분할 시 무엇이 깨지나?’를 빠르게 점검하는 도구로 가장 유용합니다—이론적 논쟁이 아니라 실무적 감사입니다. 데이터베이스 기능, 캐싱 전략, 복제 모드를 고르기 전에 이 체크리스트를 사용하세요.

1) 짧은 CAP 체크리스트

다음 질문을 순서대로 물어보세요:

  • 무엇이 반드시 정확해야 하는가?(예: 계좌 잔액은 음수가 돼서는 안 된다, 재고는 초과 판매 불가, 권한은 정확해야 함)
  • 무엇을 계속 유지해야 하는가?(예: 체크아웃 엔드포인트, 로그인, 읽기 전용 카탈로그)
  • 무엇을 일시적으로 저하시킬 수 있는가?(예: 분석, 추천, 프로필 아바타, ‘마지막 접속’)

분할이 일어나면 무엇을 먼저 보호할지를 결정하는 것입니다.

2) 데이터 유형·엔드포인트별로 결정

‘우리는 AP다’ 같은 단일 전역 설정을 피하세요. 대신:

  • 데이터 유형별: 돈 vs 좋아요 vs 로그
  • 엔드포인트별: place order vs view order vs track shipment

예: 분할 시 payments에 대한 쓰기는 차단(일관성 우선)하되 product_catalog에 대한 읽기는 캐시로 가용하게 둡니다.

3) ‘허용 가능한 불일치’를 구체적으로 정의

다음을 예시로 적어두세요:

  • 시간 범위: “카운트는 5–10분 정도 뒤쳐질 수 있음”
  • 규모: “저수요 품목은 재고 오차 ±1 허용”
  • 필드 수준: “배송 ETA는 오래될 수 있지만 주문 총액은 그래서는 안 됨”
  • 사용자 문구: “확정 상태 대신 ‘대기 중(pending)’ 표시”

불일치를 평문 예시로 설명할 수 없다면 테스트와 사고 대응이 어려워집니다.

4) 요약 + 다음에 읽을 것

  • 분할은 ‘있으면 좋은’ 보장을 강제적 선택으로 바꿉니다.
  • 엔드포인트별로 그 선택을 명확히 하고 허용 가능한 불일치를 문서화하세요.

다음에 읽기 좋은 주제: 컨센서스 (/blog/consensus-vs-cap), 일관성 모델 설명 (/blog/consistency-models-explained), 그리고 SLO/오류 예산 (/blog/sre-slos-error-budgets).

자주 묻는 질문

CAP은 엔지니어에게 어떤 문제를 생각하게 해주나?

CAP은 통신 장애 상황에서의 복제 시스템을 이해하는 멘탈 모델입니다. 네트워크가 느리거나 패킷을 잃거나 분할될 때 복제본들이 신뢰할 수 있게 합의하지 못할 수 있는데, 그럴 때 다음 중 무엇을 택할지 강요합니다:

  • 일관성: 모든 사용자가 같은 최신 값을 본다
  • 가용성: 시스템이 계속 성공 응답을 반환한다

이 모델은 “분산은 어렵다”는 막연한 말을 구체적인 제품·엔지니어링 결정으로 바꿔 줍니다.

언제 실제로 CAP 상황에 있는 걸까?

진정한 CAP 상황에는 둘 다 필요합니다:

  • 복제: 동일한 데이터를 처리하거나 제공할 수 있는 노드가 둘 이상 존재
  • 통신 실패의 현실적 가능성: 분할, 타임아웃, 장기 지연 등이 발생할 수 있음

시스템이 단일 노드이거나 상태를 복제하지 않는다면 CAP 트레이드오프는 핵심 문제가 아닙니다.

실제 시스템에서 네트워크 분할은 무엇으로 간주되나?

분할은 모든 머신이 멀쩡히 동작하는데도 시스템 일부가 요구하는 시간 내에 신뢰성 있게 통신하지 못하는 상황을 말합니다.

실무적으로 분할은 보통 다음처럼 나타납니다:

  • 타임아웃을 유발하는 지연 스파이크
  • 패킷 손실이나 블랙홀
  • 방화벽·라우팅 오작동
  • 제때 응답하지 못하는 과부하된 노드

애플리케이션 관점에서는 “너무 느린 것”이 곧 “다운된 것”과 동일한 효과를 냅니다.

사용자 관점에서 일관성과 가용성의 차이는?

**일관성(C)**은 읽기가 모든 위치에서 최신으로 인정된 쓰기를 반영하는지를 의미합니다. 사용자는 “내가 바꿨고 모두가 같은 값을 본다”라고 느낍니다.

**가용성(A)**은 모든 요청이 성공 응답(반드시 최신은 아님)을 받는다는 뜻입니다. 사용자는 “앱이 계속 동작한다”라고 느끼지만 결과가 오래된 경우가 있을 수 있습니다.

분할이 발생하면 모든 연산에 대해 두 가지를 동시에 보장하기는 어렵습니다.

왜 일관성과 가용성을 선택하고 분할을 무시할 수 없나?

복제된 시스템이 머신, 랙, 존, 리전으로 확장되면 통신 문제는 피할 수 없는 현실입니다. 따라서 분할을 무시하고 단순히 일관성·가용성만 선택하는 것은 불가능합니다.

‘분할을 허용한다’는 것은 통신이 깨졌을 때 시스템이 정의된 방식으로 동작하게 만든다는 뜻입니다—일부 동작을 거부(일관성 우선)하거나 최선의 응답을 제공(가용성 우선)하는 식으로요.

분할 시 일관성 우선(CP) 선택은 어떻게 보이나?

일관성을 우선한다면 보통:

  • 합의를 확인할 수 없을 때 연산을 거부하거나 지연
  • 읽기/쓰기에서 과반수(쿼럼)를 요구
  • 타임아웃, ‘다시 시도하세요’, 읽기 전용 모드 같은 오류를 노출

이 패턴은 금전 이체, 재고 예약, 권한 변경처럼 잘못되면 큰 피해가 나는 영역에서 흔히 채택됩니다.

분할 시 가용성 우선(AP) 선택은 어떻게 보이나?

가용성을 우선한다면 보통:

  • 분할의 양쪽에서 계속 읽기/쓰기 허용
  • 복제본들이 일시적으로 달라질 수 있음 허용
  • 나중에 병합(충돌 해결, 최종 동기화, 수동 검토)으로 조정

사용자는 강한 오류는 덜 보지만, 데이터가 오래되거나 중복된 효과가 생기거나 충돌을 정리해야 하는 상황이 발생할 수 있습니다.

작업별로 일관성과 가용성 선택을 섞을 수 있나?

예. 엔드포인트나 데이터 타입별로 다르게 정할 수 있습니다. 흔한 혼합 전략은:

  • 읽기는 가용하게, 쓰기는 엄격하게: 탐색은 되지만 위험한 업데이트는 실패
  • 중요 동작은 닫힌 실패: 결제·재고·인증은 일관성 우선, 피드·분석은 가용성 우선
  • 디그레이드 모드: 안전한 동작은 유지하고 위험한 동작만 잠깐 비활성화

이렇게 하면 단일 전역 레이블(예: 우리 시스템은 AP다/CP다)이 제품 요구를 제대로 반영하지 못하는 문제를 피할 수 있습니다.

‘강한’과 ‘최종’ 일관성 외에 선택할 수 있는 보장은?

다음과 같은 선택지가 유용합니다:

  • 강한 일관성(선형화 가능): 쓰기 인정 후 모든 이후 읽기는 그 쓰기를 보장. 분할 시 응답 지연/거부 가능.
  • 최종 일관성: 업데이트가 없으면 결국 모든 복제본이 수렴하지만 즉시 같은 값을 보장하지는 않음.
  • 중간 보장:
    • read-your-writes (내가 쓴 결과는 내가 이후에 볼 수 있음)
    • monotonic reads (한 번 본 버전보다 이전 것을 보지 않음)
    • causal consistency (의존 관계가 있는 이벤트의 순서 보장)

사용자에게 보이는 ‘잘못’(wrongness)을 막는 데 필요한 가장 약한 보장을 선택하세요.

실무에서 CAP 거정을 어떻게 테스트하고 모니터링하나?

행동을 검증하려면 분열이 가시화되는 상황을 의도적으로 만들고 시스템이 설계대로 반응하는지 확인하세요:

  • 스테이징(및 신중히는 프로덕션)에서 분할/지연을 시뮬레이션: 특정 서비스 간 트래픽 블랙홀, 포트 차단, 지연·패킷 손실 주입
  • 동작 검증: 쓰기가 거부되나? 읽기가 오래된 값을 반환하나? 복구와 재병합은 어떻게 진행되나?
  • 모니터링 지표: 읽기/쓰기별 오류율, 복제본 일치성 지표(지연·충돌률), 타임아웃·재시도 횟수 등

또한 운영자가 분할 시 어떤 조치를 취할지(쓰기 동결, 폴오버, 디그레이드 결정, 재병합 안전성 검증) 미리 정리한 런북과 사용자 공지 문구를 준비하세요.

Related posts