한양과학기술고등학교

에듀테크 프로젝트 대시보드

CAP 정리 (CAP Theorem)

CAP 정리 (CAP Theorem)

여러 대의 서버가 데이터를 복제하며 협력하는 분산 시스템에서, 네트워크가 끊기는 순간 시스템은 "옛날 값이라도 응답할까(가용성)" 대 "틀린 값을 줄 바엔 에러를 낼까(일관성)" 중 하나를 반드시 포기해야 한다.

한눈에 — Consistency(일관성): 모든 노드가 같은 최신 값을 보여준다. Availability(가용성): 살아있는 노드는 항상 응답한다. Partition tolerance(분단 내성): 노드 사이 네트워크가 끊겨도 시스템은 계속 동작한다. 이 셋을 동시에 다 만족할 수는 없다 — 네트워크가 갈라진 순간엔 C와 A 중 하나를 골라야 한다.

1. 직접 만져 보기 — 클러스터에 분단을 일으켜 보자

노드 3개가 값 v를 복제한다. 왼쪽 클라이언트가 리더(N1)에 쓰고, 오른쪽 클라이언트가 소수파(N3)에서 읽는다. "네트워크 분단"을 켜면 링크가 끊긴다. 그 상태에서 쓰기·읽기를 해 보고, CP / AP 모드에 따라 결과가 어떻게 갈리는지 관찰하자.

모드
● 리더 N1 — 쓰기를 받음 ― 복제 링크 / 끊긴 링크 주황 값 = 옛(stale) 데이터
현재 모드
CP
마지막 쓰기
–
마지막 읽기
–
분단이 없을 땐 C·A 둘 다 만족한다. 먼저 그냥 쓰기·읽기를 해 본 뒤, "분단 켜기"를 누르고 다시 시도해 보세요.
무슨 일이 일어났나 — 분단 중에 N1에 새 값을 쓰면 N3까지 복제가 도달하지 못한다. 이때 N3에서 읽으면:
· CP 모드: N3는 "내가 최신인지 확신 못 함" → 읽기 거부(에러). 틀린 값은 절대 안 준다. (가용성 희생)
· AP 모드: N3는 가진 옛 값을 그대로 응답. 항상 답은 하지만 최신이 아닐 수 있다. (일관성 희생)

2. 왜 셋 다는 안 되는가 — 2노드로 보는 증명 스케치

노드 G1, G2가 값을 복제한다. 둘 사이 네트워크가 끊겼다(P는 선택이 아니라 현실이다 — 케이블은 언젠가 끊긴다). 이제 클라이언트가 G1에 v=5를 쓴다. G1은 G2에게 전달할 수 없다. 곧이어 다른 클라이언트가 G2에서 읽는다.

분단이 발생한 그 순간, 어느 쪽도 포기하지 않는 제3의 선택지는 존재하지 않는다. 그래서 CAP은 실무에서 "분단이 생겼을 때 CP냐 AP냐"의 문제로 읽는다. 분단이 없을 땐 C와 A를 모두 누릴 수 있다.

3. CP vs AP — 어떤 시스템이 무엇을 골랐나

선택분단 시 동작대표 예시어울리는 곳
CP 소수파 노드는 읽기·쓰기를 거부. 과반수(quorum)만 서비스. ZooKeeper, etcd, HBase, 은행 원장, Raft 기반 저장소 잔액·재고·예약 — 틀린 값이 치명적인 곳
AP 모든 노드가 계속 응답. 값이 갈라졌다가 나중에 수렴(eventual consistency). Cassandra, DynamoDB(기본), 장바구니, DNS, CDN SNS 좋아요 수, 게시글 조회수 — 잠깐 어긋나도 되는 곳
"CA 시스템"은? — CAP에서 P를 버린다는 건 "네트워크가 절대 안 끊긴다고 가정한다"는 뜻이다. 현실의 분산 시스템에서는 불가능한 가정이라, 단일 노드 DB가 아니면 CA는 사실상 선택지가 아니다. 그래서 진짜 결정은 언제나 CP ↔ AP 사이에 있다.

4. PACELC — CAP의 확장

CAP은 "분단이 있을 때"만 말한다. PACELC는 여기에 한 줄을 더한다: Partition이면 A 또는 C, Else(정상일 때)엔 Latency(지연) 또는 Consistency.

즉 일관성의 대가는 분단 때는 가용성, 평소엔 속도다. 관련: 트랜잭션과 ACID, 일관된 해싱.

5. 코드로 보는 정족수(Quorum) 읽기·쓰기

노드 N개 중 쓰기에 W개, 읽기에 R개의 확인을 요구한다. W + R > N이면 읽기 집합과 쓰기 집합이 반드시 겹쳐 최신 값을 본다(CP 성향). 조건을 깨면 더 잘 뚫리지만 옛 값을 읽을 수 있다(AP 성향).

# N=3 복제. W=2, R=2 → W+R=4 > 3 → 강한 일관성
def write(key, value, nodes, W):
    acks = 0
    for node in nodes:
        if node.try_put(key, value, version=now()):   # 도달 가능한 노드만
            acks += 1
        if acks >= W:
            return "OK"                             # W개 확인 → 성공
    raise Unavailable("정족수 미달 — 분단 소수파일 가능성")

def read(key, nodes, R):
    replies = [n.get(key) for n in reachable(nodes)][:R]
    if len(replies) < R:
        raise Unavailable("정족수 미달")          # CP: 거부
    return max(replies, key=lambda r: r.version)   # 가장 최신 버전 채택
정족수의 핵심 — 5노드 클러스터가 3:2로 갈라지면, W=3을 요구하는 쓰기는 3노드 쪽에서만 성공한다. 2노드 쪽은 스스로 "나는 소수파"임을 알고 쓰기를 거부한다. 이렇게 한쪽만 진행시켜 데이터가 두 갈래로 갈라지는 split-brain을 막는다.
한 줄 요약 — 네트워크는 언젠가 끊긴다(P). 끊긴 순간, 옛 값이라도 응답할지(A) 정확할 때만 응답할지(C) 골라야 한다. 은행은 C, 좋아요 수는 A. 관련: Raft 합의, 트랜잭션·ACID, 로드 밸런싱.