Post

CAP Theorem

CAP Theorem

Consistency

  • 정의: 분산 시스템에서 데이터가 여러 노드(replica)에 복제되어 있을 때, 어느 노드에 요청을 보내도 항상 가장 최신에 쓰여진 값을 읽을 수 있어야 하는 성질이다. CAP에서 말하는 Consistency는 ACID의 Consistency(제약조건 만족)와는 다른 개념으로, 사실상 Linearizability(선형성) — 쓰기가 완료된 시점 이후의 모든 읽기는 그 쓰기 이후의 값만 봐야 한다는 것 — 에 가깝다.
  • 쓰기가 노드 A에 들어오면 그 값이 노드 B, C에도 복제되어야 하는데, 복제가 끝나기 전에 B나 C로 읽기 요청이 오면 아직 이전 값을 반환하게 된다. 이 상태를 Consistency가 깨졌다고 본다.
1
2
3
4
5
6
7
8
Client                Node A (write)      Node B (replica)
--------------------------------------------------------------
SET x = 1  ---------->  x = 1
                         |
                         | replicate (in-flight, not yet arrived)
                         v
GET x  (routed to B) ------------------->  x = 0   -- 아직 이전 값!
                                            (stale read, Consistency 위반)
  • 복제가 끝날 때까지 읽기를 막거나(쓰기 완료 확인을 모든 노드로부터 받은 뒤 응답), 항상 하나의 노드(리더)만 읽고 쓰게 하면 Consistency를 지킬 수 있지만, 그만큼 응답 지연이나 가용성 손해를 감수해야 한다.

Availability

  • 정의: 장애가 없는 노드는 (죽지 않은 이상) 어떤 요청을 받아도 에러 없이, 무한정 대기하지 않고 응답을 반환해야 하는 성질이다. 여기서 “응답”이 반드시 최신 값이어야 하는 것은 아니다 — 오래된 값이라도 뭔가를 반환하기만 하면 Availability는 지켜진 것이다.
  • 즉 CAP의 Availability는 “정확한 데이터를 준다”가 아니라 “요청을 거부하거나 응답 없이 멈추지 않는다”는 뜻에 가깝다. 이 지점이 Consistency와 정면으로 충돌하는 부분이다: 최신 값을 보장하려면(Consistency) 복제가 끝날 때까지 응답을 미루거나 거부해야 할 수 있고(Availability 손해), 항상 응답하려면(Availability) 아직 복제되지 않은 오래된 값이라도 돌려줘야 할 수 있다(Consistency 손해).

Partition Tolerance

  • 정의: 노드 간 네트워크 통신이 끊기거나 지연되는 네트워크 파티션(Network Partition) 상황에서도, 시스템이 (일부 노드가 서로 격리되더라도) 완전히 멈추지 않고 계속 동작해야 하는 성질이다.
1
2
3
4
5
6
7
     [ Partition A ]          X          [ Partition B ]
     Node 1 --- Node 2        X          Node 3 --- Node 4
                               X  (네트워크 단절, 서로 통신 불가)

  -> Node 1/2는 Node 3/4가 살아있는지, 어떤 상태인지 알 수 없다.
     이 상태에서도 각 파티션이 요청을 계속 처리할 것인가(Availability 우선),
     아니면 서로 동기화가 안 될 수 있으니 요청을 거부할 것인가(Consistency 우선)를 선택해야 한다.
  • 분산 시스템은 여러 노드가 네트워크로 연결되어 있는 구조이기 때문에 스위치 장애, 케이블 단절, 타임아웃 등으로 인한 네트워크 파티션은 일어날 수 있는 일이 아니라 언젠가 반드시 일어나는 일로 취급해야 한다. 그래서 Partition Tolerance는 셋 중 하나를 선택적으로 포기할 수 있는 옵션이 아니라, 분산 시스템이라면 기본적으로 갖춰야 하는 전제에 가깝다.
  • 결국 CAP 이론이 실제로 묻는 질문은 “C, A, P 중 두 개를 고르라”가 아니라, “네트워크 파티션이 발생했을 때, Consistency와 Availability 중 무엇을 포기할 것인가” 이다.

CA

  • 정의: 네트워크 파티션이 발생하지 않는다는 전제 하에 Consistency와 Availability를 모두 만족시키는 조합이다.
  • 하지만 앞서 살펴봤듯 분산 시스템에서 네트워크 파티션은 선택적으로 배제할 수 있는 상황이 아니라 언젠가 반드시 발생하는 전제에 가깝다. 따라서 CA는 여러 노드가 네트워크로 연결된 진짜 분산 시스템에서는 사실상 존재할 수 없는 조합이고, 파티션이라는 개념 자체가 성립하지 않는 단일 노드(single node) 시스템(예: 단일 인스턴스로 운영되는 RDBMS)에서만 이론적으로 성립한다.
  • 그래서 CAP 이론에서 CA는 실무적으로 고려 대상이 아니며, 실제 분산 시스템 설계는 파티션이 발생했을 때 CP로 갈 것인가, AP로 갈 것인가의 선택으로 좁혀진다.

CP

  • 정의: 네트워크 파티션이 발생하면 Consistency를 지키기 위해 Availability를 포기하는 조합이다. 파티션으로 인해 최신 값을 보장할 수 없는 노드는 요청에 에러를 반환하거나 응답을 보류한다.
1
2
3
4
5
6
7
8
     [ Partition A ]          X          [ Partition B ]
     Node 1 (leader)          X          Node 3
                               X
Client -> Node 3: GET x
Node 3: "리더(Node 1)와 통신할 수 없어 최신 값을 보장 못함" -> 요청 거부/에러 응답

  -> Consistency는 지켜지지만(오래된 값을 절대 돌려주지 않음),
     Node 3 쪽 클라이언트는 파티션이 해소될 때까지 서비스를 이용할 수 없다.
  • 정족수(Quorum) 기반으로 과반수 노드와 통신 가능한 쪽만 쓰기/읽기를 계속 처리하고, 소수 파티션은 스스로 요청을 거부하는 방식이 대표적이다.
  • 대표 시스템: ZooKeeper, etcd, Consul, HBase, MongoDB(기본 설정 — 프라이머리와 통신 불가 시 세컨더리는 쓰기를 거부), RDBMS의 동기(synchronous) 리플리케이션 구성.
  • 이런 곳에 적용하면 좋다 — “틀린 값을 보여주느니 잠깐 멈추는 게 낫다”는 도메인:
    • 분산 락(Distributed Lock) / 리더 선출: 두 노드가 동시에 자신이 리더라고 착각하면(split-brain) 둘 다 쓰기를 수행해 데이터가 꼬인다. 파티션 중에는 아예 락을 내주지 않는 편이 안전하다. (ZooKeeper, etcd 기반 락 매니저)
    • 설정/서비스 디스커버리 저장소: 전체 클러스터가 참조하는 설정값이나 서비스 주소 목록이 노드마다 다르면 라우팅 오류, 잘못된 설정 적용으로 이어진다. (etcd — Kubernetes의 클러스터 상태 저장소가 대표적)
    • 결제/재고/좌석 예약처럼 중복·초과 처리가 치명적인 도메인: 계좌 잔액이나 재고 수량, 좌석 상태를 오래된 값으로 읽고 처리하면 이중 출금, 초과 판매, 중복 예약 같은 되돌리기 어려운 사고로 이어진다. 차라리 요청을 잠깐 실패시키고 재시도를 유도하는 편이 낫다.
    • 금융 거래 원장(ledger): 거래 순서와 잔액이 노드마다 다르게 보이면 감사(audit)나 정합성 검증 자체가 무너지므로 강한 일관성이 필수다.

AP

  • 정의: 네트워크 파티션이 발생해도 Availability를 지키기 위해 Consistency를 포기하는 조합이다. 모든 노드가 자신이 가진 값(오래된 값일 수도 있음)으로라도 응답을 계속하고, 파티션이 해소되면 그때 값을 동기화한다(Eventual Consistency, 최종적 일관성).
1
2
3
4
5
6
7
8
9
     [ Partition A ]          X          [ Partition B ]
     Node 1 (x=1)             X          Node 3 (x=0, stale)
                               X
Client -> Node 3: GET x
Node 3: "파티션 중이라 최신 값인지 확신은 없지만" -> x = 0 응답 (거부하지 않음)

  -> Availability는 지켜지지만(항상 응답),
     파티션이 해소되기 전까지 Node 1과 Node 3은 서로 다른 값을 반환할 수 있다.
     (파티션 해소 후 conflict resolution을 거쳐 값이 수렴한다)
  • 대표 시스템: Cassandra, DynamoDB, CouchDB, Riak, MongoDB(세컨더리에서 읽기를 허용하도록 설정한 경우), DNS, 대부분의 CDN.
  • 이런 곳에 적용하면 좋다 — “잠깐 오래된 값을 보여주더라도 서비스가 멈추면 안 된다”는 도메인:
    • SNS 피드 / 좋아요·조회수 카운트: 게시물 좋아요 수가 노드마다 잠깐 다르게 보여도 사용자 경험에 큰 문제가 없고, 결국 수렴한다. 반면 파티션 중에 피드 자체가 안 뜨면 서비스 임팩트가 훨씬 크다.
    • 장바구니 / 위시리스트: Amazon DynamoDB가 대표적으로 AP를 택한 사례로 널리 알려져 있다. 파티션 중 장바구니에 상품 하나 더 담는 요청을 거부하느니, 일단 받아두고 나중에 병합(conflict resolution)하는 편이 구매 전환에 유리하다.
    • DNS: 전 세계에 분산된 네임서버가 항상 100% 동일한 최신 레코드를 보장하지는 않지만(TTL 동안 캐시된 값 사용), 조회 요청 자체가 실패하는 일은 절대 없어야 한다.
    • CDN / 캐시 계층: 오리진과 잠깐 값이 달라도(캐시 미갱신) 응답을 내려주는 것이 훨씬 중요하다.
    • IoT 센서 데이터 수집, 로그/메트릭 수집: 개별 데이터 포인트 하나가 약간 늦게 반영되는 것보다, 파티션 상황에서도 수집 자체가 끊기지 않는 것이 더 중요하다.

정리

구분파티션 발생 시 우선순위대가대표 사례
CAConsistency + Availability파티션 자체를 전제하지 않음 → 진짜 분산 시스템에서는 사실상 불가능단일 노드 RDBMS
CPConsistency파티션 중 일부 노드는 응답을 거부(Availability 희생)ZooKeeper, etcd, HBase
APAvailability파티션 중 오래된 값을 반환할 수 있음(Consistency 희생)Cassandra, DynamoDB, CouchDB

파티션은 피할 수 없는 전제이므로, 결국 시스템을 설계할 때 실질적으로 고민해야 하는 것은 CA를 포함한 세 가지 중 하나를 고르는 게 아니라, 파티션이 발생한 그 순간에 오래된 데이터를 보여줄지, 아니면 응답을 멈출지 중 무엇을 선택하느냐이다.

This post is licensed under CC BY 4.0 by the author.

Trending Tags