1장에서 카산드라는 CAP 이론에서 C(일관성)를 다소 희생하고 A(가용성)를 선택한 AP 시스템이라고 배웠습니다. 하지만 카산드라의 진짜 강력함은 이 '일관성'을 개발자가 직접 튜닝(Tunable Consistency)할 수 있다는 점에 있습니다. 즉, 요청의 중요도에 따라 강력한 일관성을 요구할 수도, 빠른 응답을 위해 일관성을 낮출 수도 있습니다.
카산드라의 일관성 레벨을 이해하기 전에, 두 가지 핵심적인 일관성 모델을 먼저 알아야 합니다.
카산드라는 개발자가 이 두 모델 사이에서 원하는 수준의 '일관성'을 직접 선택할 수 있는 도구를 제공하는데, 그것이 바로 '일관성 레벨'입니다.
일관성 레벨은 쓰기 또는 읽기 요청이 성공으로 간주되기 위해 응답해야 하는 복제본 노드의 수를 결정하는 설정입니다. 이 설정은 클라이언트 드라이버 레벨에서 각 쿼리마다 지정할 수 있습니다.
| 레벨 | 설명 | 장점 (언제 사용하나?) | 단점 |
|---|---|---|---|
| ANY | (쓰기 전용) 어떤 노드든 쓰기만 성공하면 응답합니다. 노드가 다운 상태여도 Hinted Handoff를 통해 쓰기가 수락됩니다. | 가장 높은 가용성과 가장 빠른 쓰기 속도. 데이터 유실 가능성을 감수할 수 있는 경우 (e.g., 임시 데이터) | 데이터 유실 위험이 가장 높음. |
| ONE | 가장 가까운 복제본 노드 하나만 응답하면 성공으로 간주합니다. | 매우 빠른 응답 속도. 데이터의 최신성이 크게 중요하지 않은 경우 (e.g., 조회수 카운트) | 최신이 아닌 낡은 데이터를 읽을 수 있음. |
| QUORUM | 복제본 노드의 과반수((RF/2) + 1)가 응답해야 성공으로 간주합니다. |
강력한 일관성과 적절한 가용성의 균형. 대부분의 애플리케이션에서 권장되는 표준 레벨. | ONE보다 느리고, 일부 노드 장애 시 요청이 실패할 수 있음. |
| LOCAL_QUORUM | (Multi-DC 환경) 현재 데이터 센터 내에서만 과반수가 응답하면 성공으로 간주합니다. | 다른 데이터 센터의 장애나 네트워크 지연에 영향을 받지 않음. | 다른 DC의 사용자는 낡은 데이터를 볼 수 있음. |
| ALL | 모든 복제본 노드가 응답해야 성공으로 간주합니다. | 가장 강력한 일관성을 보장. | 가장 느리고, 단 하나의 노드만 장애가 나도 요청이 실패하여 가용성이 매우 낮음. |
카산드라에서도 RDBMS와 같은 강력한 일관성(Strong Consistency)을 보장할 수 있습니다. 비결은 읽기와 쓰기 일관성 레벨을 특정 공식에 맞게 조절하는 것입니다.
이 공식이 성립하면, 읽기 요청이 접근하는 노드 그룹과 쓰기 요청이 접근하는 노드 그룹 사이에는 최소 하나 이상의 겹치는 노드가 존재하게 됩니다. 이 겹치는 노드는 항상 최신 데이터를 가지고 있으므로, 읽기 요청은 절대 낡은 데이터를 반환하지 않습니다.
-- 예시: RF = 3 일 때
-- 1. Write CL = QUORUM (W=2), Read CL = QUORUM (R=2)
-- R(2) + W(2) = 4 > N(3) => 강력한 일관성 보장! (가장 일반적인 조합)
-- 2. Write CL = ALL (W=3), Read CL = ONE (R=1)
-- R(1) + W(3) = 4 > N(3) => 강력한 일관성 보장! (쓰기는 느리지만 읽기는 빠름)
-- 3. Write CL = TWO (W=2), Read CL = ONE (R=1) ← 함정!
-- R(1) + W(2) = 3 = N(3) => 공식 미충족. 낡은 데이터를 읽을 수 있음.
-- "쓰기는 2개 노드에 완료됐으니 읽기를 1개만 해도 되지 않을까?" 라는 직관이 틀린 이유.
-- 4. Write CL = ONE (W=1), Read CL = ONE (R=1)
-- R(1) + W(1) = 2 < N(3) => 최종적 일관성. 낡은 데이터를 읽을 수 있음.
새로운 판결문(데이터)이 나왔습니다. W=QUORUM(2)는 비서가 3명의 판사 중 2명에게 판결문을 전달하고 확인 도장을 받으면 임무를 완수하는 것입니다. 나중에 다른 사람이 판결문을 확인할 때 R=QUORUM(2)으로 3명 중 2명에게 물어보면, 어떻게 물어보든 최소 1명은 반드시 이전에 확인 도장을 찍어준 판사이므로 항상 최신 판결문을 확인할 수 있습니다.
레거시로 인수받은 프로모션 서비스에서 어느 날 고객에게 캐시(포인트)가 이중으로 지급되는 사고가 발생했습니다.
Root cause를 추적한 결과, 코드베이스 내 Cassandra 쿼리의 Consistency Level이 일관성 없이 혼재되어 있었던 것이 원인이었습니다.
성능 최적화를 위해 일부 읽기 쿼리에 R=ONE을 적용해 둔 것이 있었는데, 하필 "이 프로모션을 이미 적용했는가?"를 체크하는 쿼리가 그 대상이었습니다.
RF=3인 환경에서 W=TWO, R=ONE으로 설정되어 있었습니다.
2번째 요청이 R=ONE으로 노드 C를 읽음 → "적용 내역 없음" → 캐시 또 지급
W(2) + R(1) = 3, > RF(3) 조건 미충족 → 쓰기 노드(A,B)와 읽기 노드(C)가 겹치지 않을 수 있음
"쓰기를 2개 노드(A, B)에 완료했으니 1개 노드만 읽어도 최신 데이터가 있지 않을까?"라는 생각이 자연스럽게 들 수 있습니다. 그러나 Cassandra는 읽기 요청을 어떤 노드로 라우팅할지를 보장하지 않습니다. R=ONE 읽기는 응답이 가장 빠른 노드 하나를 선택하는데, 그 노드가 쓰기가 완료된 A나 B가 아닌 C일 수 있습니다. R+W > RF 공식은 바로 이 "겹치는 노드가 반드시 존재함"을 수학적으로 보장하기 위한 것입니다.
R=QUORUM으로 변경 → W(2)+R(2)=4 > 3 충족R=QUORUM, 단순 조회(정합성이 절대적이지 않은 경우)만 R=ONE 허용으로 기준 명문화R=ONE은 "빠른 읽기"가 아니라 "낡은 데이터를 허용하겠다는 선언"입니다. 레이턴시 최적화 목적으로 R=ONE을 적용할 때는 반드시 "이 경로에서 stale read가 발생하면 어떤 결과가 생기는가?"를 먼저 따져야 합니다. 조회수, 로그성 데이터는 괜찮지만, 중복 지급 방지·재고 차감·결제 완료 여부 같은 금전성·멱등성 체크 경로에는 절대 사용해서는 안 됩니다.
카산드라는 노드 장애나 네트워크 문제로 인해 발생할 수 있는 데이터 불일치를 스스로 치유하는 메커니즘을 가지고 있습니다.
읽기 요청 시 발생하는 자가 치유 과정입니다. 코디네이터 노드가 여러 복제본으로부터 데이터를 읽어왔을 때, 데이터가 서로 다른 것을 감지하면 가장 최신 타임스탬프를 가진 데이터로 낡은 데이터를 가진 노드에게 백그라운드에서 비동기적으로 업데이트를 보내줍니다.
쓰기 요청 시 특정 복제본 노드가 다운되었을 때 동작합니다. 코디네이터는 다운된 노드 대신 자신(코디네이터)의 디스크에 '나중에 이 데이터를 A 노드에게 전달해줘'라는 힌트(Hint)를 남겨둡니다.
Q: "Cassandra에서 강력한 일관성을 보장하는 방법에 대해 설명해주세요."
A: "카산드라는 쿼리마다 일관성 레벨(Consistency Level)을 조절하여 일관성을 튜닝할 수 있습니다. 강력한 일관성을 보장하기 위해서는 `R + W > N` 공식을 만족시키면 됩니다. 여기서 N은 복제 계수(Replication Factor), W는 쓰기 일관성 레벨에 따른 노드 수, R은 읽기 일관성 레벨에 따른 노드 수입니다. 예를 들어, RF가 3일 때 쓰기와 읽기 모두 `QUORUM` 레벨로 설정하면, W와 R은 각각 2가 되어 `2 + 2 > 3` 공식을 만족합니다. 이렇게 하면 쓰기 작업에 참여한 노드 그룹과 읽기 작업에 참여한 노드 그룹이 항상 최소 하나 이상 겹치게 되어, 읽기 요청 시 반드시 최신 데이터를 가진 노드와 통신하게 되므로 강력한 일관성이 보장됩니다."
Q: "QUORUM 레벨은 정확히 무엇을 의미하며, 왜 대부분의 서비스에서 권장되나요?"
A: "`QUORUM`은 복제본 노드의 과반수, 즉 `(복제 계수 / 2) + 1`개의 노드로부터 응답을 받으면 요청을 성공으로 처리하는 일관성 레벨입니다. 예를 들어 RF가 3이면 2개, RF가 5이면 3개의 노드로부터 응답을 받아야 합니다. 이 레벨이 권장되는 이유는 일관성과 가용성 사이에서 가장 합리적인 균형을 제공하기 때문입니다. `ONE` 레벨처럼 너무 느슨하지 않아 데이터 정합성을 어느 정도 보장하면서도, `ALL` 레벨처럼 노드 하나만 죽어도 서비스가 멈추는 극단적인 상황을 피할 수 있습니다. 즉, 일부 노드 장애를 허용하면서도 `R+W>N` 공식을 만족시키기 용이하여 강력한 일관성을 구현할 수 있는 가장 실용적인 선택지입니다."
Q: "W=TWO, R=ONE 설정이 왜 문제가 될 수 있는지 설명해주세요."
A: "RF=3 환경에서 W=TWO, R=ONE이면 W+R=3으로 R+W > N 공식을 충족하지 못합니다. 쓰기가 완료된 2개 노드(A, B)와 읽기가 라우팅되는 1개 노드(C)가 겹치지 않을 수 있기 때문입니다. 실제로 프로모션 적용 여부를 체크하는 쿼리에 이 설정이 혼재되어 있어 이중 지급 사고가 발생한 경험이 있습니다. 원인을 추적한 결과, 레이턴시 최적화 목적으로 일부 읽기 쿼리에 R=ONE을 적용해 둔 것이 문제였습니다. 이후 금전성 데이터를 읽는 경로는 모두 R=QUORUM으로 통일하고, Redis 분산 락을 2차 방어선으로 추가했습니다."
Q: "Read Repair는 동기적으로 동작하나요, 비동기적으로 동작하나요? 그리고 그 이유는 무엇일까요?"
A: "Read Repair는 비동기적으로 동작합니다. 읽기 요청 시 코디네이터가 데이터 불일치를 감지하면, 우선 사용자에게는 가장 최신 버전의 데이터를 즉시 응답하여 읽기 지연시간(Latency)을 최소화합니다. 그 후에 백그라운드에서 낡은 데이터를 가진 노드에게 최신 데이터를 보내주는 복구 작업을 수행합니다. 만약 이 과정이 동기적으로 이루어진다면, 데이터 복구가 완료될 때까지 사용자가 응답을 기다려야 하므로 읽기 성능이 크게 저하될 것입니다. 카산드라는 가용성과 성능을 중요시하는 AP 시스템이므로, 핵심 경로인 읽기 요청 처리를 방해하지 않기 위해 자가 치유 과정을 비동기적으로 설계했습니다."