이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • CAP를 “셋 중 둘”이 아니라 기능별 응답 정책으로 어떻게 해석하는가?
  • 네트워크 단절이나 timeout 때 어떤 기능은 실패시키고 어떤 기능은 stale 응답을 허용하는가?
  • CAP 선택은 성능, 정합성, 장애 격리, 운영 복잡도 중 무엇을 바꾸는가?

개요

CAP는 면접용 약어가 아니라 장애 중 사용자 경험을 정하는 언어다. 분산 시스템에서는 어느 순간 노드끼리 같은 사실을 확인하지 못한다. 이때 시스템은 기다리거나, 실패시키거나, 최신이 아닐 수 있는 값으로 응답한다. 실무에서 중요한 질문은 “우리는 CP인가 AP인가”가 아니다. “이 API는 partition 상황에서 틀린 성공을 줄 수 있는가”다.

이 문서가 바꾸는 설계 축

바뀌는 내용
성능강한 확인을 기다리면 latency가 늘고, 느슨한 읽기는 빠르지만 stale 가능성이 생긴다.
정합성데이터별로 틀린 성공, 중복 성공, 오래된 읽기를 어디까지 허용할지 정한다.
장애 격리일부 기능을 실패시키거나 degraded 응답으로 격리해 전체 장애를 줄인다.
운영 복잡도timeout, retry, fallback, stale 지표, conflict 처리 기준이 필요해진다.

Partition을 현실적으로 본다

partition은 데이터센터가 둘로 갈라지는 극단만 뜻하지 않는다. 실무에서는 다음 상황도 coordination 실패다.

상황사용자에게 보이는 현상
DB primary와 replica lag방금 쓴 값이 목록에 안 보인다.
외부 결제 API timeout승인됐는지 실패했는지 모른다.
message broker 지연주문 완료 후 알림과 집계가 늦다.
region 간 네트워크 지연다른 지역 사용자가 오래된 상태를 본다.
cache invalidation 실패삭제된 상품이 계속 노출된다.
이 상황에서 모든 기능을 같은 정책으로 처리하면 안 된다.
결제 승인과 추천 피드는 실패 비용이 다르다.

대표 시나리오

커머스 서비스의 세 기능을 비교한다.

기능partition 상황바람직한 선택
결제 승인결제사 응답 timeoutPENDING으로 두고 중복 승인을 막는다.
재고 차감DB primary 확인 불가실패시키거나 대기열로 보낸다.
상품 추천추천 read model 지연stale 추천을 보여주고 freshness를 낮춘다.
좋아요 수counter event 지연임시 count를 보여주거나 지연 반영한다.
결제와 재고는 틀린 성공이 더 위험하다.
추천과 좋아요 수는 완벽히 최신이 아니어도 사용자 피해가 작다.
CAP 선택은 데이터 종류별로 나뉜다.

CP와 AP를 API 계약으로 바꾼다

CP 성향의 API는 확실하지 않으면 실패하거나 pending으로 남긴다.

POST /payments
202 Accepted
{
  "paymentId": "pay-100",
  "status": "PENDING_CONFIRMATION",
  "retryable": false
}

여기서 202는 결제가 완료됐다는 뜻이 아니다. “요청을 접수했고 결과 확인이 필요하다”는 계약이다. 이 계약이 없으면 client가 같은 결제를 다시 시도해 중복 승인 위험을 만든다. AP 성향의 API는 가능한 응답을 주되 freshness를 드러낸다.

GET /feeds
200 OK
X-Data-Freshness: 2026-07-01T10:00:00+09:00

이 응답은 최신이 아닐 수 있음을 숨기지 않는다.

기능별 선택 기준

질문CP 쪽으로 기운다AP 쪽으로 기운다
틀린 성공이 돈이나 권한을 바꾸는가?아니오
사용자가 잠깐 못 쓰는 것이 더 나은가?아니오
오래된 값이 사용자에게 설명 가능한가?아니오
나중에 자동 보정 가능한가?어렵다가능하다
중복 처리가 치명적인가?아니오
이 표는 기술 선택보다 먼저 와야 한다.
DB, cache, queue는 이 판단의 결과다.

Spring 경계 예시

서비스 코드도 선택을 드러내야 한다.

public PaymentResponse requestPayment(PaymentCommand command) {
    Payment payment = paymentRepository.savePending(command.idempotencyKey());
 
    PaymentGatewayResult result = gateway.authorize(command);
    if (result.isUnknown()) {
        return PaymentResponse.pending(payment.id());
    }
 
    payment.confirm(result.approvalId());
    return PaymentResponse.approved(payment.id());
}

timeout을 실패로 확정하지 않는다. unknown은 unknown으로 저장하고, reconciliation이나 webhook으로 확정한다. 이것이 정합성을 지키는 대신 운영 복잡도를 늘리는 선택이다.

장애 격리와 CAP

partition 상황에서 모든 기능이 같은 primary를 기다리면 전체 장애가 된다. 핵심 기능과 부가 기능을 나눈다.

기능장애 중 정책
주문 생성확실히 저장되거나 명시적으로 실패한다.
주문 목록최근 주문은 primary, 오래된 목록은 replica 허용
추천stale read 허용
통계일시 중단 또는 이전 집계 표시
관리자 다운로드차단 가능
가용성을 높인다는 말은 모든 응답을 성공으로 만든다는 뜻이 아니다.
덜 중요한 기능을 낮추어 중요한 기능의 정합성을 보호하는 것이다.

운영 지표

CAP 선택은 지표로 검증해야 한다.

지표의미
unknown_payment_count확정되지 않은 외부 호출 결과
stale_read_rate오래된 값을 응답한 비율
replica_lag_p95replica read 허용 범위
pending_duration_p95pending 상태가 사용자에게 보이는 시간
conflict_count나중에 충돌로 판정된 작업 수
사용자 불만은 기술 지표와 다르게 나타난다.
pending_duration_p95가 길면 시스템은 살아 있어도 사용자는 실패로 느낀다.

개인 프로젝트 기준

  • 기능별로 “틀린 성공보다 실패가 나은가”를 한 줄씩 적는다.
  • 결제, 주문, 재고, 권한은 stale 허용 여부를 보수적으로 둔다.
  • 추천, 검색, 통계는 stale 허용 시간과 사용자 안내를 적는다.
  • timeout을 성공/실패로 단정하지 않고 unknown 상태를 둘 수 있는지 검토한다.
  • 포트폴리오에서는 CAP 약어보다 기능별 선택과 trade-off를 설명한다.

기업 운영 기준

  • CP/API와 AP/API의 SLO, alert, fallback 기준을 분리한다.
  • unknown 상태 reconciliation owner와 주기를 정한다.
  • stale 응답은 header, response field, dashboard 지표로 드러낸다.
  • 장애 중 중단 가능한 기능과 보호해야 할 기능을 runbook에 둔다.
  • product, CS, backend가 pending/stale 문구를 같은 의미로 이해해야 한다.

위험 신호

  • CAP를 “우리 시스템은 CP”처럼 전체 시스템 한 단어로 설명한다.
  • 결제 timeout을 단순 실패로 처리하고 client retry를 허용한다.
  • stale 응답을 숨기고 항상 성공처럼 보여준다.
  • 장애 중 추천, 통계, 관리자 기능이 주문 primary와 같은 자원을 경쟁한다.
  • pending 상태의 owner와 만료 정책이 없다.
  • 지표가 replica lag만 있고 사용자 stale 경험을 보지 않는다.

확인 질문

확인 질문

  • 확인 질문: CAP를 실무 언어로 바꾸면 첫 질문은 무엇인가?
    • 답변: 이 기능은 확실하지 않을 때 틀린 성공을 줄 것인지, 명시적으로 실패하거나 pending으로 둘 것인지 묻는 것이다.
  • 확인 질문: 가용성을 높인다는 말은 항상 성공 응답을 준다는 뜻인가?
    • 답변: 아니다. 중요하지 않은 기능을 degraded로 낮추고 핵심 기능의 정합성과 장애 격리를 지키는 것도 가용성 전략이다.
  • 확인 질문: partition 상황에서 결제 API가 PENDING을 반환하는 이유는 무엇인가?
    • 답변: 성공과 실패를 확정할 수 없는 상태를 구분해 중복 승인과 잘못된 사용자 안내를 막기 위해서다.