🏗️ 아키텍처

CAP 정리

CAP Theorem (Brewer's Theorem)

분산 시스템에서 일관성(Consistency), 가용성(Availability), 분할 내성(Partition tolerance) 중 최대 2가지만 동시에 보장할 수 있다는 이론입니다.

📖 상세 설명

일관성(Consistency)은 모든 노드가 동일한 시점에 동일한 데이터를 볼 수 있음을 의미합니다. 쓰기 작업이 완료되면, 이후의 모든 읽기는 그 쓰기 결과를 반영해야 합니다. 금융 거래처럼 데이터 정합성이 중요한 시스템에서 필수적입니다.

가용성(Availability)은 모든 요청에 대해 (성공 또는 실패) 응답을 반환함을 보장합니다. 일부 노드에 장애가 발생해도 시스템은 계속 응답해야 합니다. 사용자 경험이 중요한 서비스에서 필수적이며, 응답이 없는 것보다 오래된 데이터라도 보여주는 것이 나은 경우가 많습니다.

분할 내성(Partition tolerance)은 네트워크 분할(노드 간 통신 두절)이 발생해도 시스템이 계속 동작함을 의미합니다. 분산 시스템에서 네트워크 장애는 불가피하므로, 실제로 P는 선택이 아닌 필수입니다. 따라서 실질적인 선택은 CP(일관성 + 분할 내성) 또는 AP(가용성 + 분할 내성)입니다.

CAP 정리는 2000년 Eric Brewer가 제안하고, 2002년 Seth Gilbert와 Nancy Lynch가 공식적으로 증명했습니다. 현대 분산 시스템 설계의 근간이 되는 이론으로, 데이터베이스 선택과 아키텍처 설계에 중요한 가이드라인을 제공합니다.

CP 시스템(예: MongoDB, HBase, Redis Cluster)은 네트워크 분할 시 일관성을 위해 일부 요청을 거부합니다. AP 시스템(예: Cassandra, DynamoDB, CouchDB)은 분할 시에도 응답하지만 일시적으로 오래된 데이터를 반환할 수 있습니다.

💻 코드 예제

┌─────────────────────────────────────────────────────────────────────┐
│                        CAP 정리 다이어그램                           │
├─────────────────────────────────────────────────────────────────────┤

                         Consistency (일관성)
                              ▲
                             /│\
                            / │ \
                           /  │  \
                          /   │   \
                         /    │    \
                        /     │     \
                       /      │      \
                      /   CP  │  CA   \
                     /  영역  │  영역  \
                    /         │         \
                   /__________│__________\
                  /           │           \
    Availability ◄───────────┼───────────► Partition tolerance
    (가용성)                 AP            (분할 내성)
                            영역


    ═══════════════════════════════════════════════════════════════

    세 가지 조합의 의미
    ─────────────────

    ┌─────────────────────────────────────────────────────────────┐
    │  CA (일관성 + 가용성)                                        │
    │  ─────────────────                                          │
    │  • 네트워크 분할 없는 단일 노드 시스템                        │
    │  • 전통적인 관계형 DB (단일 서버)                             │
    │  • 실제 분산 환경에서는 불가능                                │
    │  • 예: 단일 노드 PostgreSQL, MySQL                           │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  CP (일관성 + 분할 내성)                                      │
    │  ─────────────────────                                       │
    │  • 네트워크 분할 시 일관성을 위해 가용성 포기                  │
    │  • 일부 노드 접근 불가 시 요청 거부                           │
    │  • 금융, 예약 시스템 등 정확성이 중요한 경우                   │
    │  • 예: MongoDB, HBase, Redis Cluster, Zookeeper              │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  AP (가용성 + 분할 내성)                                      │
    │  ─────────────────────                                       │
    │  • 네트워크 분할 시에도 항상 응답                             │
    │  • 일시적으로 오래된 데이터 반환 가능                         │
    │  • 사용자 경험, 서비스 연속성이 중요한 경우                   │
    │  • 예: Cassandra, DynamoDB, CouchDB, Riak                    │
    └─────────────────────────────────────────────────────────────┘


    ═══════════════════════════════════════════════════════════════

    네트워크 분할 시나리오
    ────────────────────

    정상 상태                    네트워크 분할 발생
    ──────────                   ──────────────────

    ┌─────┐     ┌─────┐         ┌─────┐  ╳  ┌─────┐
    │Node1│◄───►│Node2│         │Node1│     │Node2│
    └──┬──┘     └──┬──┘         └──┬──┘     └──┬──┘
       │           │               │           │
       ▼           ▼               ▼           ▼
    ┌─────┐     ┌─────┐         ┌─────┐     ┌─────┐
    │Node3│◄───►│Node4│         │Node3│◄───►│Node4│
    └─────┘     └─────┘         └─────┘     └─────┘

    모든 노드 통신 가능         Node1-3 그룹과 Node2-4 그룹 분리


    CP 시스템의 대응                AP 시스템의 대응
    ────────────────                ─────────────────

    ┌─────┐  ╳  ┌─────┐            ┌─────┐  ╳  ┌─────┐
    │Node1│     │Node2│            │Node1│     │Node2│
    │ v=5 │     │ v=5 │            │ v=5 │     │ v=5 │
    └─────┘     └─────┘            └─────┘     └─────┘

    Client A → Node1               Client A → Node1
    Write v=6                      Write v=6
    ↓                              ↓
    Node1: 과반수 없음!            Node1: v=6 저장
    → 쓰기 거부 (에러 반환)        → 성공 응답

                                   Client B → Node2
                                   Read v
                                   ↓
                                   Node2: v=5 반환
                                   (오래된 값, 나중에 동기화)

└─────────────────────────────────────────────────────────────────────┘
# CAP 정리 시뮬레이션 - Python 예제
from enum import Enum
from typing import Optional, Dict, Any
from dataclasses import dataclass
import time
import random

class SystemType(Enum):
    CP = "Consistency + Partition tolerance"
    AP = "Availability + Partition tolerance"
    CA = "Consistency + Availability (단일 노드)"


@dataclass
class Node:
    """분산 시스템의 노드"""
    id: str
    data: Dict[str, Any]
    is_reachable: bool = True


class DistributedSystem:
    """CAP 속성을 시뮬레이션하는 분산 시스템"""

    def __init__(self, system_type: SystemType, node_count: int = 3):
        self.system_type = system_type
        self.nodes = [Node(id=f"node-{i}", data={}) for i in range(node_count)]
        self.partition_active = False

    def simulate_network_partition(self):
        """네트워크 분할 시뮬레이션"""
        self.partition_active = True
        # 절반의 노드를 접근 불가로 설정
        for i, node in enumerate(self.nodes):
            node.is_reachable = i < len(self.nodes) // 2 + 1
        print("⚠️ 네트워크 분할 발생! 일부 노드 접근 불가")

    def heal_partition(self):
        """네트워크 분할 복구"""
        self.partition_active = False
        for node in self.nodes:
            node.is_reachable = True
        self._sync_nodes()
        print("✅ 네트워크 복구됨, 노드 동기화 완료")

    def _get_reachable_nodes(self) -> list[Node]:
        """접근 가능한 노드 목록"""
        return [n for n in self.nodes if n.is_reachable]

    def _sync_nodes(self):
        """노드 간 데이터 동기화 (분할 복구 후)"""
        # 최신 데이터로 모든 노드 동기화
        all_data = {}
        for node in self.nodes:
            all_data.update(node.data)
        for node in self.nodes:
            node.data = all_data.copy()

    def write(self, key: str, value: Any) -> bool:
        """
        데이터 쓰기
        - CP 시스템: 과반수 노드 도달 필요
        - AP 시스템: 하나의 노드만 있어도 성공
        """
        reachable = self._get_reachable_nodes()

        if self.system_type == SystemType.CP:
            # CP: 과반수 노드에 쓰기 성공해야 함
            quorum = len(self.nodes) // 2 + 1
            if len(reachable) < quorum:
                print(f"❌ CP 시스템: 쓰기 거부 (과반수 {quorum} 필요, 현재 {len(reachable)})")
                return False

            for node in reachable:
                node.data[key] = value
            print(f"✅ CP 시스템: 쓰기 성공 ({len(reachable)}개 노드)")
            return True

        elif self.system_type == SystemType.AP:
            # AP: 하나의 노드만 있어도 쓰기 성공
            if reachable:
                for node in reachable:
                    node.data[key] = value
                print(f"✅ AP 시스템: 쓰기 성공 ({len(reachable)}개 노드, 나머지는 나중에 동기화)")
                return True
            return False

        return False

    def read(self, key: str) -> Optional[Any]:
        """
        데이터 읽기
        - CP 시스템: 과반수 노드 응답 필요
        - AP 시스템: 접근 가능한 아무 노드에서 읽기
        """
        reachable = self._get_reachable_nodes()

        if self.system_type == SystemType.CP:
            quorum = len(self.nodes) // 2 + 1
            if len(reachable) < quorum:
                print(f"❌ CP 시스템: 읽기 거부 (과반수 필요)")
                return None

            # 과반수 노드에서 동일한 값 확인
            values = [n.data.get(key) for n in reachable]
            result = values[0] if values else None
            print(f"✅ CP 시스템: 읽기 성공 (일관성 보장)")
            return result

        elif self.system_type == SystemType.AP:
            # 접근 가능한 첫 번째 노드에서 읽기
            if reachable:
                result = reachable[0].data.get(key)
                print(f"✅ AP 시스템: 읽기 성공 (오래된 값일 수 있음)")
                return result

        return None


# 시뮬레이션 실행
if __name__ == "__main__":
    print("=" * 60)
    print("CP 시스템 시뮬레이션 (예: MongoDB)")
    print("=" * 60)

    cp_system = DistributedSystem(SystemType.CP, node_count=3)

    # 정상 상태에서 쓰기
    cp_system.write("balance", 1000)
    print(f"잔액 읽기: {cp_system.read('balance')}")

    # 네트워크 분할 발생
    cp_system.simulate_network_partition()

    # 분할 중 쓰기 시도 (실패)
    cp_system.write("balance", 2000)
    cp_system.read("balance")

    # 분할 복구
    cp_system.heal_partition()
    print(f"복구 후 잔액: {cp_system.read('balance')}")

    print("\n" + "=" * 60)
    print("AP 시스템 시뮬레이션 (예: Cassandra)")
    print("=" * 60)

    ap_system = DistributedSystem(SystemType.AP, node_count=3)

    # 정상 상태에서 쓰기
    ap_system.write("likes", 100)

    # 네트워크 분할 발생
    ap_system.simulate_network_partition()

    # 분할 중에도 쓰기 가능
    ap_system.write("likes", 150)
    print(f"좋아요 읽기: {ap_system.read('likes')} (일부 노드만 업데이트됨)")

    # 분할 복구 후 동기화
    ap_system.heal_partition()
    print(f"복구 후 좋아요: {ap_system.read('likes')}")
┌─────────────────────────────────────────────────────────────────────┐
│                    데이터베이스별 CAP 특성 비교                       │
├─────────────────────────────────────────────────────────────────────┤

    데이터베이스        유형      네트워크 분할 시 동작
    ─────────────────────────────────────────────────────────────────

    ┌─────────────────────────────────────────────────────────────────┐
    │                        CP 시스템                                 │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                 │
    │  MongoDB          CP      과반수 노드 없으면 Primary 선출 불가   │
    │                           → 쓰기 작업 거부                       │
    │                                                                 │
    │  HBase            CP      Region Server 장애 시 해당 영역       │
    │                           → 접근 불가 (복구까지 대기)            │
    │                                                                 │
    │  Redis Cluster    CP      과반수 마스터 없으면                   │
    │                           → 클러스터 다운 (CLUSTERDOWN 에러)     │
    │                                                                 │
    │  Zookeeper        CP      과반수 노드 필요                       │
    │                           → 리더 선출 실패 시 서비스 불가        │
    │                                                                 │
    │  etcd             CP      Raft 합의 필요                        │
    │                           → 과반수 없으면 쓰기 거부              │
    │                                                                 │
    └─────────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────────┐
    │                        AP 시스템                                 │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                 │
    │  Cassandra        AP      어떤 노드든 읽기/쓰기 가능             │
    │                           → 일관성 수준 조절 가능 (ONE~ALL)      │
    │                                                                 │
    │  DynamoDB         AP      멀티 리전 복제                         │
    │                           → 최종 일관성/강한 일관성 선택 가능    │
    │                                                                 │
    │  CouchDB          AP      오프라인에서도 작업 가능               │
    │                           → 나중에 충돌 해결 (MVCC)              │
    │                                                                 │
    │  Riak             AP      Ring 구조로 분산                       │
    │                           → 모든 노드가 동등, 항상 응답          │
    │                                                                 │
    └─────────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────────┐
    │                      튜닝 가능한 시스템                           │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                 │
    │  Cassandra        조절    일관성 수준으로 CP ↔ AP 스펙트럼       │
    │                           • ONE: AP에 가까움 (빠름)              │
    │                           • QUORUM: 균형                        │
    │                           • ALL: CP에 가까움 (느림)             │
    │                                                                 │
    │  MongoDB          조절    Write Concern으로 조절                 │
    │                           • w:1 (빠름) ~ w:majority (안전)       │
    │                                                                 │
    │  DynamoDB         조절    ConsistentRead 옵션                   │
    │                           • false: 최종 일관성 (빠름)            │
    │                           • true: 강한 일관성 (느림)             │
    │                                                                 │
    └─────────────────────────────────────────────────────────────────┘


    ═══════════════════════════════════════════════════════════════════

    사용 사례별 권장 시스템
    ────────────────────

    사용 사례               권장        이유
    ─────────────────────────────────────────────────────────────────
    금융 거래/결제         CP         데이터 정확성이 최우선
    재고 관리              CP         중복 판매 방지
    분산 락/리더 선출      CP         일관된 상태 필수
    사용자 인증            CP         보안상 일관성 중요

    소셜 미디어 피드       AP         약간의 지연 허용
    좋아요/조회수          AP         정확하지 않아도 됨
    쇼핑 카트 (결제 전)    AP         가용성 우선
    로그/분석 데이터       AP         데이터 유실보다 수집 중요
    DNS                    AP         항상 응답 필요

└─────────────────────────────────────────────────────────────────────┘

🗣️ 실무에서 이렇게 말하세요

💬 데이터베이스 선택 회의에서
"CAP 정리에 따르면 분산 환경에서 P는 필수입니다. 결제 시스템이니까 CP인 MongoDB가 적합해요. 네트워크 분할 시 잘못된 잔액을 보여주는 것보다 에러를 반환하는 게 낫습니다."
💬 장애 대응 논의에서
"지금 데이터센터 간 네트워크 이슈가 있어요. 우리 시스템은 CP라서 과반수 노드에 접근 못하면 쓰기가 거부됩니다. AP 시스템이었다면 서비스는 됐겠지만 데이터 불일치가 생겼을 거예요."
💬 시스템 설계 리뷰에서
"Cassandra의 장점은 CAP에서 위치를 조절할 수 있다는 겁니다. 일반 조회는 ONE으로 AP처럼 빠르게, 중요한 쓰기는 QUORUM으로 CP에 가깝게 동작시킬 수 있어요."

⚠️ 주의사항 & 베스트 프랙티스

"3개 중 2개" 단순 해석 주의

네트워크 분할(P)은 분산 시스템에서 불가피합니다. 실제 선택은 CP vs AP이며, CA는 단일 노드에서만 가능합니다.

이분법적 사고 경계

현대 시스템은 CAP 스펙트럼 위에서 동작합니다. Cassandra처럼 일관성 수준을 조절할 수 있는 시스템이 많으며, 상황에 따라 다른 전략을 사용할 수 있습니다.

지연 시간(Latency) 고려

CAP은 지연 시간을 다루지 않습니다. PACELC 정리를 함께 고려하세요: 분할 없을 때(Else) 지연(Latency) vs 일관성(Consistency) 트레이드오프도 중요합니다.

기능별 차별화 전략

하나의 시스템에서 기능별로 다른 CAP 전략을 사용하세요. 결제는 CP, 추천은 AP로 분리하면 각 요구사항에 맞는 최적의 성능을 얻을 수 있습니다.

🔗 관련 용어

📚 더 배우기