분산 데이터베이스: 일관성과 가용성 사이의 절충
분산 데이터베이스가 장애 시 가용성을 유지하기 위해 일관성을 어떻게 완화하는지, CAP와 쿼럼의 작동 원리, 그리고 각 접근법을 언제 선택해야 할지 배우세요.

일관성과 가용성은 실제로 무엇을 의미하는가
데이터베이스가 여러 대의 기계(복제본)에 나뉘면 속도와 복원력을 얻는 대신, 그 기계들이 완벽히 동의하지 않거나 서로 안정적으로 통신하지 못하는 기간이 생깁니다.
일관성(평범한 의미)
일관성은 이렇게 말합니다: 성공적인 쓰기 이후에는 모든 읽기가 동일한 값을 반환한다. 프로필 이메일을 업데이트하면 다음 읽기—어떤 복제본이 응답하든—새 이메일을 반환해야 합니다.
실무에서는 일관성을 우선시하는 시스템이 실패 시 일부 요청을 지연하거나 거부하여 서로 모순되는 답을 반환하지 않도록 합니다.
가용성(평범한 의미)
가용성은 이렇게 말합니다: 시스템이 모든 요청에 응답한다. 일부 서버가 다운되거나 분리되어 있어도 응답을 줍니다. 최신 데이터가 아닐 수 있지만 응답은 옵니다.
실무에서는 가용성을 우선하는 시스템이 복제본들이 불일치하는 동안에도 쓰기와 읽기를 수락하고, 나중에 차이를 조정합니다.
이런 트레이드오프가 실제 애플리케이션에 의미하는 것
트레이드오프는 모든 장애 시나리오에서 두 목표를 동시에 최적화할 수 없다는 뜻입니다. 복제본이 조정할 수 없을 때 데이터베이스는 다음 중 하나를 선택해야 합니다:
- 단일로 합의된 진실을 보호하기 위해 일부 요청을 기다리거나 실패하게 한다(일관성 우선), 또는
- 사용자에게 계속 응답하되 오래되거나 충돌하는 데이터를 감수한다(가용성 우선)
간단한 예: 장바구니 vs 은행 이체
- 장바구니: 다른 기기에서 장바구니 수량이 잠깐 하나 틀려도 짜증나지만 보통 허용됩니다. 많은 팀이 높은 가용성을 선호하고 나중에 조정합니다.
- 은행 이체: $500 을 옮겼는데 잔액이 잠깐 두 가지 값으로 보이면 심각한 문제입니다. 이 경우 강한 일관성이 때때로 "다시 시도하세요" 같은 실패를 감수할 가치가 있습니다.
단일 최선의 선택은 없다
올바른 균형은 허용할 수 있는 오류(짧은 장애 또는 짧은 기간의 잘못된/오래된 데이터)에 따라 결정됩니다. 대부분의 실제 시스템은 그 사이 어딘가 점을 택하고 그 트레이드오프를 명시합니다.
분산이 규칙을 바꾸는 이유
데이터베이스가 여러 노드에서 데이터를 저장하고 제공하면 분산된 것입니다. 애플리케이션에는 여전히 하나의 데이터베이스처럼 보일 수 있지만 내부적으로는 서로 다른 장소의 다른 노드들이 요청을 처리할 수 있습니다.
복제: 팀들이 노드를 추가하는 이유
대부분의 분산 데이터베이스는 데이터를 복제합니다: 동일한 레코드를 여러 노드에 저장합니다. 팀들이 이렇게 하는 이유는:
- 기계 하나가 죽어도 서비스를 유지하려고
- 사용자 근처의 노드에서 제공해 지연을 줄이려고
- 읽기(때로는 쓰기)를 더 많은 하드웨어로 분산하려고
복제는 강력하지만 즉시 질문을 제기합니다: 두 노드가 동일한 데이터의 복사본을 가지고 있다면, 어떻게 항상 일치하도록 보장할까요?
부분적 실패는 예외가 아니라 정상이다
단일 서버에서는 "다운"이 비교적 분명합니다. 분산 시스템에서는 실패가 종종 부분적입니다. 한 노드는 살아있지만 느릴 수 있습니다. 네트워크 링크가 패킷을 잃을 수 있습니다. 한 랙 전체가 연결을 잃을 수 있습니다.
이것이 중요한 이유는 노드들이 다른 노드가 실제로 다운인지, 일시적으로 닿지 않는지, 아니면 단순히 지연되는지 즉시 알 수 없기 때문입니다. 그들이 상황을 확인하는 동안 들어오는 읽기와 쓰기를 어떻게 처리할지 결정해야 합니다.
통신이 보장되지 않으면 보장도 바뀐다
한 서버가 있을 때는 진실의 한 소스가 있습니다: 모든 읽기는 최신의 성공적인 쓰기를 본다.
여러 노드가 있을 때는 “최신”이 조정에 달려 있습니다. 쓰기가 노드 A에서 성공했지만 노드 B에 도달하지 못하면 데이터베이스는:
- B의 확인을 기다려 쓰기를 블록할 것인가(일관성 보호), 아니면
- 쓰기를 어쨌든 받아들일 것인가(가용성 보호)?
이 긴장이 불완전한 네트워크 때문에 현실이 되는 것이 분산이 규칙을 바꾸는 이유입니다.
네트워크 분할: 핵심 문제
네트워크 분할은 하나의 데이터베이스처럼 작동해야 할 노드들 사이의 통신이 끊기는 상황입니다. 노드들은 여전히 동작 중일 수 있지만 메시지를 신뢰성 있게 교환할 수 없습니다—스위치 고장, 과부하된 링크, 잘못된 라우팅, 방화벽 설정 오류, 클라우드의 시끄러운 이웃 등 원인은 다양합니다.
대규모에서 분할은 피할 수 없다
시스템이 여러 기계(종종 랙, 가용영역, 리전에 걸쳐)로 확장되면 노드들 사이의 모든 홉을 통제할 수 없게 됩니다. 네트워크는 패킷을 버리고 지연을 일으키며 때로는 "섬"으로 나뉘어집니다. 작은 규모에서는 이런 이벤트가 드물지만 큰 규모에서는 일상적입니다. 데이터베이스는 무슨 일이 있었는지 합의하려면 지속적인 조정이 필요하기 때문에 짧은 장애도 문제가 됩니다.
분할이 어떻게 충돌하는 “최신” 데이터를 만드는가
분할 동안 양쪽 모두 요청을 계속 받습니다. 양쪽에서 쓰기를 허용하면 각 쪽이 상대 쪽이 보지 못한 업데이트를 받아들일 수 있습니다.
예: 노드 A가 사용자의 주소를 "New Street"로 업데이트하는 동시에 노드 B가 "Old Street Apt 2"로 업데이트합니다. 각 쪽은 실시간으로 비교할 방법이 없기 때문에 자신의 쓰기가 최신이라고 믿습니다.
사용자에게 보이는 증상
분할은 깔끔한 오류 메시지로 나타나지 않습니다; 혼란스러운 동작으로 드러납니다:
- 타임아웃: 데이터베이스가 쓰기나 읽기 확인을 위해 다른 노드를 기다립니다.
- 오래된 읽기: 업데이트를 놓친 복제본에 접속해 이전 데이터를 봅니다.
- 스플릿 브레인: 어느 쪽에 닿느냐에 따라 다른 사용자가 서로 다른 "진실"을 봅니다.
이것이 압박점입니다: 네트워크가 통신을 보장할 수 없을 때 분산 데이터베이스는 일관성과 가용성 중 무엇을 우선할지 결정해야 합니다.
전문 용어 없이 보는 CAP 정리
CAP는 데이터베이스가 여러 기계에 걸쳐 있을 때 무슨 일이 일어나는지 간단히 설명하는 방법입니다.
세 용어(평범한 영어)
- 일관성(C): 값을 쓰면 이후의 어떤 읽기도 그 값을 반환합니다.
- 가용성(A): 어떤 서버가 문제를 겪더라도 모든 요청이 오류가 아닌 응답을 받습니다.
- 분할 허용성(P): 네트워크가 갈라져 서버들이 신뢰성 있게 통신하지 못해도 시스템이 계속 동작합니다.
핵심 요지
분할이 없을 때는 많은 시스템이 일관성과 가용성 모두를 갖춘 것처럼 보일 수 있습니다.
분할이 있을 때는 무엇을 우선시할지 선택해야 합니다:
- 일관성 선택: 서버들이 합의할 때까지 일부 요청을 거부하거나 지연합니다.
- 가용성 선택: 분할된 각 쪽에서 요청을 받아들여 일시적으로 답이 다를 수 있게 합니다.
상상할 수 있는 간단한 타임라인
- 10:00 클라이언트가
balance = 100을 서버 A에 씁니다. - 10:01 네트워크 분할: 서버 A가 서버 B에 닿지 않습니다.
- 10:02 클라이언트가 서버 B에서 읽습니다.
- 일관성을 우선하면, 서버 B는 거부하거나 기다려야 합니다.
- 가용성을 우선하면, 서버 B는 응답하지만
balance = 80같은 오래된 값을 반환할 수 있습니다.
흔한 오해
CAP는 "영원히 두 가지만 고르라"는 의미가 아닙니다. 분할이 발생한 동안에는 일관성과 가용성을 동시에 보장할 수 없다는 뜻입니다. 분할이 없을 때는 대부분의 시스템이 둘 모두에 가깝게 동작할 수 있지만 네트워크가 잘못되면 상황이 바뀝니다.
일관성을 선택할 때 얻는 것과 잃는 것
일관성을 선택하면 데이터베이스는 "모두가 같은 진실을 본다"는 것을 "항상 응답"보다 우선합니다. 실무에서는 보통 강한 일관성(선형화 가능성, linearizable) 을 지칭합니다: 쓰기가 확인되면 그 이후의 어떤 읽기도 그 값을 반환합니다.
분할 동안에 일어나는 일
네트워크가 갈라지고 복제본들이 서로 통신할 수 없으면, 강한 일관성 시스템은 독립적인 업데이트를 양쪽에서 안전하게 받아들일 수 없습니다. 정합성을 지키기 위해 보통:
- 요청을 차단하거나, 또는
- 요구되는 복제본/리더에 닿지 않으면 요청을 거부합니다.
사용자 관점에서는 일부 기계가 여전히 동작 중인데도 서비스 중단처럼 보일 수 있습니다.
얻는 것
주된 이점은 이해하기 쉬워진다는 점입니다. 애플리케이션 코드는 여러 복제본이 서로 불일치할 수 있는 현실 대신, 하나의 데이터베이스에 말하는 것처럼 동작할 수 있습니다. 덕분에 다음과 같은 기묘한 상황이 줄어듭니다:
- 성공적인 업데이트 직후 이전 데이터를 읽는 일
- 접속한 복제본에 따라 서로 다른 값을 보는 일
- 동시 충돌로 인해 제약 조건(예: 재고 초과 판매)을 잃는 일
또한 감사, 청구 등 즉시 정확함이 중요한 곳에서 더 깔끔한 모델을 제공합니다.
잃는 것
일관성은 비용이 있습니다:
- 높은 지연 시간: 많은 작업이 노드 간 조정을 기다려야 합니다(종종 머신이나 리전을 넘나듬).
- 장애 시 더 많은 오류: 분할, 느린 복제본, 리더 문제는 타임아웃이나 "나중에 다시 시도하세요"로 이어질 수 있습니다.
제품이 부분 장애 동안 실패한 요청을 용납할 수 없다면, 강한 일관성은 비싸게 느껴질 수 있지만 정확성이 중요한 경우 옳은 선택입니다.
가용성을 선택할 때 얻는 것과 잃는 것
가용성을 선택하면 단순한 약속을 최적화합니다: 시스템이 응답한다. 고가용성은 "절대 오류 없음"이 아니라, 노드 실패, 과부하, 네트워크 단절 시에도 대부분의 요청이 답을 받는다는 뜻입니다.
네트워크 분할 동안에 일어나는 일
네트워크가 분리되면 복제본들이 서로 통신할 수 없습니다. 가용성 우선 데이터베이스는 일반적으로 접근 가능한 쪽에서 트래픽을 계속 처리합니다:
- 읽기는 현재 복제본이 가진 데이터로 로컬에서 응답합니다.
- 쓰기는 로컬에서 받아들여지고 연결이 복구되면 뒤에 복제/전달됩니다.
이렇게 하면 애플리케이션이 계속 움직이지만 서로 다른 복제본이 일시적으로 서로 다른 진실을 받아들일 수 있습니다.
얻는 것
더 나은 업타임을 얻습니다: 사용자는 리전이 격리되더라도 항목을 장바구니에 담고, 댓글을 달고, 이벤트를 기록할 수 있습니다.
장애 상황에서도 더 부드러운 사용자 경험을 제공합니다. 타임아웃 대신 앱은 "업데이트가 저장되었습니다" 같은 합리적 동작을 유지하고 나중에 동기화합니다. 많은 소비자 제품과 분석 워크로드에서 이 트레이드는 가치가 있습니다.
잃는 것
대가는 데이터베이스가 오래된 읽기를 반환할 수 있다는 점입니다. 사용자가 한 복제본에서 프로필을 업데이트하고 다른 복제본에서 바로 읽으면 이전 값을 볼 수 있습니다.
또한 쓰기 충돌의 위험이 있습니다. 두 사용자가(혹은 같은 사용자가 두 위치에서) 분할된 양쪽에서 동일 레코드를 업데이트하면, 분할이 치유될 때 시스템은 서로 다른 이력을 조정해야 합니다. 규칙에 따라 한 쪽 쓰기가 승리하거나, 필드별 병합 또는 애플리케이션 차원의 해결이 필요할 수 있습니다.
가용성 우선 설계는 제품이 응답하도록 일시적 불일치를 받아들이고, 나중에 이를 감지하고 수리하는 데 투자하는 접근입니다.
쿼럼과 투표: 중간 지대
쿼럼은 많은 복제된 데이터베이스가 일관성과 가용성의 균형을 맞추기 위해 사용하는 실용적 "투표" 기법입니다. 단일 복제본을 신뢰하는 대신 시스템은 "충분한" 복제본이 동의하는지를 묻습니다.
(N, R, W) 아이디어
쿼럼은 보통 세 숫자로 설명됩니다:
- N: 데이터 조각에 대한 복제본 수
- W: 쓰기가 성공으로 간주되려면 확인해야 하는 복제본 수
- R: 읽기 시 참조하는 복제본 수
일반 규칙은: R + W > N 이면, 각 읽기는 적어도 한 개의 복제본에서 최신 성공 쓰기와 겹치므로 오래된 데이터를 읽을 가능성이 줄어듭니다.
직관적 예시
N=3 복제본이 있을 때:
- 단일 복제본 접근(R=1, W=1): 빠르고 가용성이 높지만 오래된 복제본을 쉽게 읽을 수 있습니다.
- 과반수 투표(R=2, W=2): 쓰기가 2개의 복제본에 도달해야 하고 읽기도 2개를 조회합니다. 읽기와 쓰기 집합이 겹칠 확률이 높아져 최신 값을 볼 가능성이 커집니다.
어떤 시스템은 더 강한 일관성을 위해 W=3(모든 복제본)을 택하기도 하지만, 그럴 경우 어떤 복제본이라도 느리거나 다운되면 쓰기 실패가 더 자주 발생합니다.
분할 동안 쿼럼이 하는 일
쿼럼은 분할 문제를 제거하지 않습니다—대신 누가 진행할 수 있는가를 정의합니다. 네트워크가 2–1로 갈라지면, 2개의 노드가 있는 쪽은 R=2, W=2를 만족시켜 계속 진행할 수 있지만 고립된 단일 복제본은 그렇지 못합니다. 이는 충돌 업데이트를 줄여주지만 일부 클라이언트는 오류나 타임아웃을 보게 될 수 있습니다.
트레이드오프
쿼럼은 보통 지연 증가(더 많은 노드 접촉), 비용 증가(교차 노드 트래픽), 그리고 더 복잡한 장애 동작(타임아웃은 가용성 문제처럼 보임)을 의미합니다. 장점은 조정 가능한 중간 지대라는 점입니다: 무엇이 더 중요한지에 따라 R 과 W 를 조절해 더 신선한 읽기나 높은 쓰기 성공률을 선택할 수 있습니다.
최종적 일관성과 흔한 이상 현상
최종적 일관성은 복제본들이 일시적으로 어긋나도 시간이 지나면 같은 값으로 수렴하면 된다는 의미입니다.
구체적 비유
체인점 커피숍이 공통의 페이스트리 "품절" 표지를 업데이트한다고 생각해 보세요. 한 지점이 품절로 표시하면 그 업데이트가 다른 지점에 몇 분 뒤에 도달합니다. 그 시간 창 동안 다른 지점은 여전히 "판매 가능"으로 보일 수 있고 마지막 개를 팔아버릴 수 있습니다. 시스템이 "고장"난 건 아니고 업데이트가 따라잡는 중입니다.
관찰할 수 있는 이상 현상
데이터가 전파되는 동안 클라이언트는 놀라운 동작을 관찰할 수 있습니다:
- 오래된 읽기: 최신 쓰기를 받지 못한 복제본에서 이전 데이터를 읽습니다.
- 자신이 쓴 내용을 못 보는 현상: 업데이트 후 다른 복제본이나 장애 전환 후 바로 자신의 변경이 보이지 않습니다.
- 순서가 뒤바뀐 업데이트: 서로 다른 복제본에서 업데이트가 다른 순서로 도착해 일시적 불일치를 만듭니다.
복제본 수렴을 돕는 기법
최종적 일관성 시스템은 보통 불일치 창을 줄이기 위한 배경 메커니즘을 사용합니다:
- 읽기 수리(read repair): 읽기 중 불일치가 감지되면 백그라운드에서 오래된 복제본을 업데이트합니다.
- 힌티드 핸드오프(hinted handoff): 복제본이 다운되면 다른 노드가 써야 할 내용을 임시로 보관했다가 복구 시 전달합니다.
- 항등성 검사(anti-entropy): 머클 트리나 체크섬을 이용한 주기적 동기화로 드리프트를 찾고 고칩니다.
최종적 일관성이 잘 맞는 경우
가용성이 최신성보다 중요한 경우에 적합합니다: 활동 피드, 조회 수, 추천, 캐시된 프로필, 로그/텔레메트리 등 "잠시 후에는 정확해진다"가 허용되는 데이터입니다.
충돌 해결: 갈라진 쓰기는 어떻게 조정되는가
데이터베이스가 여러 복제본에서 쓰기를 받아들일 때 서로 다른 업데이트가 생겨 충돌이 발생할 수 있습니다. 예를 들어 사용자가 한 기기에서 배송주소를 바꾸는 동시에 다른 기기에서 전화번호를 바꾼다면, 각 업데이트가 다른 복제본에 기록되어 분리되는 동안 서로 다른 이력이 생길 수 있습니다. 복제본이 다시 교환할 때 시스템은 "진짜" 레코드가 무엇인지 결정해야 합니다.
최종 쓰기 승리(LWW): 간단하지만 위험함
많은 시스템은 최종 쓰기 승리(LWW) 방식을 사용합니다: 타임스탬프가 가장 최신인 업데이트가 나머지를 덮어씁니다.
구현이 쉽고 계산이 빠르다는 장점이 있지만, 데이터가 조용히 손실될 수 있다는 단점이 있습니다. 또한 타임스탬프가 신뢰할 만하다는 가정에 의존하기 때문에 기계 간 시계 차이로 잘못된 업데이트가 이길 수 있습니다.
이력을 보관하기: 버전 벡터와 관련 아이디어
더 안전한 충돌 처리는 인과 관계 이력을 추적하는 것을 요구합니다.
개념적으로 버전 벡터(또는 변형)는 각 레코드에 어떤 복제본이 어떤 업데이트를 보았는지를 요약한 작은 메타데이터를 붙입니다. 복제본들이 버전을 교환하면 한 버전이 다른 버전을 포함하는지(충돌 아님) 아니면 분기되었는지(해결 필요)를 감지할 수 있습니다.
일부 시스템은 물리적 시계에 덜 의존하면서 순서 힌트를 주기 위해 Lamport 클럭이나 하이브리드 논리 시계(HLC)를 사용합니다.
덮어쓰기 대신 병합하기
충돌이 감지되면 선택지가 있습니다:
- 앱 수준 병합: 애플리케이션이 필드를 결합하거나 사용자에게 묻거나 두 버전을 모두 보관합니다.
- CRDT(충돌-자유 복제 데이터 타입): 자동으로 결정론적 병합을 제공하는 자료구조(카운터, 집합, 협업 텍스트 등에 유용). 가용성을 높게 유지하면서도 "승자 독식"을 피합니다.
어떤 접근이 최적인지는 데이터에서 "정확함"이 무엇을 의미하는지에 달려 있습니다—때로는 쓰기 손실이 용납되고, 때로는 비즈니스적으로 치명적일 수 있습니다.
당신의 사용 사례에 맞춰 선택하는 방법
일관성/가용성 태도를 정하는 것은 철학적 논쟁이 아니라 제품 결정입니다. 먼저 물어보세요: 잠깐 잘못되는 비용이 얼마이고, "나중에 다시 시도"를 요구하는 비용은 얼마인가?
비즈니스 리스크를 일관성 필요성에 매핑하기
다음과 같은 영역은 작성 시점에 단일 권위 있는 답이 필요합니다:
- 금융 및 청구: 이중 청구, 초과 인출, 환불 등은 보통 강한 일관성이 필요합니다.
- 아이덴티티 및 권한: 로그인, 비밀번호 재설정, 접근 제어, 역할 변경 등은 스플릿 브레인을 피해야 합니다.
- 재고 및 용량: 초과 판매가 용납되지 않는 경우(티켓, 한정 재고)에는 일관성이나 명시적 예약 설계가 필요합니다.
일시적 불일치의 영향이 낮거나 되돌릴 수 있다면 더 가용성 쪽으로 기울여도 됩니다.
얼마나 오래된 데이터를 허용할 수 있는지 결정하기
많은 사용자 경험은 약간 오래된 읽기로도 괜찮습니다:
- 피드와 타임라인: 게시물이 몇 초 늦게 보이는 것은 보통 수용 가능합니다.
- 분석과 대시보드: 일괄 처리되는 수치와 지연은 흔히 기대됩니다.
- 캐시와 검색 인덱스: "아직 업데이트되지 않음"을 사용자가 받아들이는 경우가 많습니다.
얼마나 오래된 것이 괜찮은지(초, 분, 시간)를 명시하세요. 그 시간 예산이 복제와 쿼럼 선택을 결정합니다.
사용자가 가장 싫어할 실패 모드를 선택하라
복제본들이 합의하지 못할 때 보통 세 가지 UX 결과 중 하나가 나타납니다:
- 로딩 스피너/대기 (정확성을 우선, 느리게 느껴짐)
- 오류/재시도 (정직하지만 방해됨)
- 오래된 결과 (부드럽지만 때때로 놀람)
기능별로(전역이 아니라) 가장 해로운 옵션을 선택하세요.
빠른 체크리스트
일관성(C)을 선호하라: 잘못된 결과가 재정적/법적 위험을 만들거나 보안 문제 또는 되돌릴 수 없는 작업인 경우.
가용성(A)을 선호하라: 사용자가 응답성을 중요시하고, 일시적 불일치가 허용되며 충돌을 안전하게 해결할 수 있는 경우.
모호하면 시스템을 분리하세요: 핵심 레코드는 강한 일관성으로 유지하고 파생 뷰(피드, 캐시, 분석)는 가용성 중심으로 두는 방식이 효과적입니다.
트레이드오프로 인한 고통을 줄이는 설계 패턴
시스템 전체에 하나의 "일관성 설정"만 골라야 하는 경우는 드뭅니다. 많은 현대 분산 데이터베이스는 연산별로 일관성 수준을 선택하게 해주고, 스마트한 애플리케이션은 이를 활용해 사용자 경험을 부드럽게 유지하면서 현실을 숨기지 않습니다.
연산별 일관성 레벨 사용
일관성을 다이얼처럼 다루세요—사용자가 하는 작업에 따라 조절합니다:
- 중요한 업데이트(결제, 재고 차감, 비밀번호 변경): 더 강한 일관성(e.g., 쿼럼/선형화 가능한 쓰기)을 사용합니다.
- 비중요 읽기(피드, 대시보드, "마지막 활동" 등): 로컬/단일 복제본/최종적 읽기를 허용해 속도와 복원력을 확보합니다.
이렇게 하면 모든 것에 대해 가장 강한 일관성 비용을 지불하지 않고도 중요한 연산을 보호할 수 있습니다.
하나의 흐름에서 강한 것과 약한 것 혼합
일반 패턴은 쓰기에는 강하게, 읽기에는 약하게 입니다:
- 엄격한 수준으로 쓰기하여 권위 있는 기록을 남깁니다.
- 느슨한 수준으로 읽고, 무언가 이상하면 강한 읽기로 새로 고치거나 "업데이트 중" 안내를 표시합니다.
경우에 따라 역도 유효합니다: 빠른 쓰기(큐/최종적) + 결과 확인 시 강한 읽기("내 주문이 완료되었나?").
재시도를 대비한 설계: 멱등성(idempotency)
네트워크가 흔들리면 클라이언트는 재시도합니다. 같은 요청을 두 번 실행해도 두 건의 주문이 생성되지 않도록 멱등성 키를 사용하세요. 같은 키를 다시 받으면 최초 결과를 재사용합니다.
긴 워크플로우: 사가(saga)와 보상
서비스 간 다단계 작업에는 사가를 사용하세요: 각 단계마다 대응하는 보상 작업(환불, 예약 해제, 배송 취소)이 있어 일부가 실패해도 복구할 수 있게 합니다. 이렇게 하면 일부가 일시적으로 불일치하거나 실패해도 시스템이 회복 가능합니다.
일관성 대 가용성에 대한 테스트와 관찰성
일관성/가용성 트레이드오프를 관리하려면 그것을 볼 수 있어야 합니다. 프로덕션 이슈는 적절한 측정과 테스트 없이 "무작위 실패"처럼 보이기 쉽습니다.
무엇을 측정할지(그리고 이유)
사용자 영향에 직접 연결되는 소수의 지표부터 시작하세요:
- 지연(Latency) (p50/p95/p99): 장애 전환, 리더 변경, 쿼럼 재시도 시 스파이크를 관찰합니다.
- 오류율: "하드" 오류(타임아웃, 5xx)와 "소프트" 오류(대체에서 서빙된 결과, 부분 결과)를 구분합니다.
- 오래된 읽기 비율: 목표 시계(예: 2초)를 초과해 오래된 데이터를 반환한 읽기 비율.
- 충돌률: 동시 쓰기로 인해 조정이 필요한 빈도(최종 쓰기 승리로 덮어쓰인 경우 포함).
가능하면 지표에 일관성 모드(쿼럼 vs 로컬)와 리전/존 태그를 달아 동작이 어디서 달라지는지 파악하세요.
의도적으로 분할을 테스트하라
실제 장애를 기다리지 말고 스테이징에서 카오스 실험을 실행하세요:
- 복제본 간 패킷 손실과 높은 지연을 시뮬레이션
- 한 리전이 도달 불가능해지는 상황
- 일부 노드만 통신 가능한 부분적 분할
시스템이 "계속 살아 있는지" 뿐 아니라 어떤 보장이 유지되는지도 검증하세요: 읽기가 신선한가, 쓰기가 블록되는가, 클라이언트는 명확한 오류를 받는가?
트레이드오프를 조기에 잡아내는 알림
다음과 같은 항목에 대한 알림을 추가하세요:
- 허용 가능한 오래된 읽기 창을 넘는 복제 지연
- 쿼럼 실패(충분한 복제본에 닿지 못함)와 증가하는 재시도 횟수
- 증가하는 쓰기 충돌 또는 조정 백로그
마지막으로, 시스템이 정상 동작 시와 분할 시에 무엇을 약속하는지 문서화하고, 제품·지원팀에 사용자에게 보일 현상을 교육하세요.
CAP 선택을 빠르게 프로토타입하는 방법(모든 걸 다시 만들지 않고)
새 제품에서 이러한 트레이드오프를 탐색할 때는 특히 실패 모드, 재시도 동작, UI에서의 "오래된 데이터"가 어떻게 보이는지 일찍 검증하는 것이 도움이 됩니다.
실용적인 접근법은 워크플로우의 작은 버전(쓰기 경로, 읽기 경로, 재시도/멱등성, 조정 작업)을 프로토타입한 뒤 전체 아키텍처에 커밋하는 것입니다. Koder.ai 같은 도구를 사용하면 채팅 기반 워크플로우로 웹앱과 백엔드를 빠르게 띄워 데이터 모델과 API를 반복하면서 다양한 일관성 패턴(예: 엄격한 쓰기 + 느슨한 읽기)을 테스트할 수 있습니다. 프로토타입이 원하는 동작을 만족하면 소스 코드를 내보내 프로덕션으로 발전시킬 수 있습니다.
자주 묻는 질문
분산 데이터베이스는 왜 일관성과 가용성의 트레이드오프에 직면하나요?
복제된 데이터베이스에서는 같은 데이터가 여러 대의 기계에 존재합니다. 이는 복원력과 응답 속도를 높이지만, 노드들이 서로 조정해야 하는 문제를 만듭니다. 노드가 느리거나 닿지 않거나 네트워크로 인해 분리되면 최신 쓰기 내용을 즉시 서로 알 수 없게 되고, 이 때문에 일관성과 가용성 사이의 선택이 필요해집니다.
일관성은 간단히 무엇을 의미하나요?
일관성은 다음과 같이 의미합니다: 성공적인 쓰기 이후의 어떤 읽기도 동일한 값을 반환해야 한다는 것—어떤 복제본이 응답하든 최신 값이 보장되어야 합니다. 실제 시스템은 이를 위해 충분한 수의 복제본(또는 리더)의 확인이 있기 전까지 읽기/쓰기를 지연하거나 거부하기도 합니다.
가용성은 간단히 무엇을 의미하나요?
가용성은 다음을 의미합니다: 일부 노드가 다운되거나 통신할 수 없을 때에도 시스템이 모든 요청에 대해 오류가 아닌 응답을 반환한다는 것. 응답은 오래된 데이터이거나 부분적일 수 있지만, 장애 동안에도 사용자를 차단하지 않습니다.
네트워크 분할이란 무엇이고 왜 그렇게 중요한가요?
네트워크 분할은 하나의 시스템처럼 동작해야 할 노드들 사이의 통신이 끊기는 상황입니다. 노드들은 여전히 동작할 수 있지만 메시지를 신뢰성 있게 주고받을 수 없게 됩니다. 이때 데이터베이스는 두 가지 중 하나를 선택해야 합니다:
- 단일한 진실을 유지하기 위해 요청을 차단/거부(일관성), 또는
- 각 분할된 쪽에서 요청에 응답하고 나중에 조정(가용성).
이 선택이 매우 중요한 이유는 분할 중에 어떤 보장을 제공할지 시스템이 결정해야 하기 때문입니다.
분할이나 복제본 불일치 시 실제 사용자에게는 어떤 일이 발생하나요?
분할이 발생하면 양쪽에서 각각 업데이트를 받아들일 수 있습니다. 그 결과 나타나는 사용자 경험은 다음과 같습니다:
- 타임아웃: 도달할 수 없는 복제본을 기다리며 요청이 지연됩니다.
- 오래된 읽기: 업데이트를 놓친 복제본에서 이전 데이터를 읽습니다.
- 스플릿 브레인: 사용자가 어느 쪽에 접속했느냐에 따라 서로 다른 “진실”을 보게 됩니다.
이들은 복제본들이 일시적으로 조정할 수 없을 때 발생하는 가시적 증상입니다.
CAP 정리는 정말 세 가지 중 두 가지만 선택하라는 뜻인가요?
CAP 정리는 “영원히 세 가지 중 두 가지만 선택하라”는 뜻이 아닙니다. 분할이 일어나는 동안에는 다음 두 가지를 동시에 보장할 수 없다는 의미입니다:
- 일관성 (모두가 최신 쓰기를 읽음), 그리고
- 가용성 (모든 요청이 응답을 받음).
분할이 없을 때는 많은 시스템이 둘 다 거의 충족될 수 있지만, 네트워크가 잘못되면 둘을 동시에 보장할 수 없다는 점을 기억해야 합니다.
쿼럼(N, R, W)은 일관성과 가용성의 균형에 어떻게 도움이 되나요?
쿼럼은 복제된 데이터에서 일관성과 가용성의 균형을 맞추기 위해 사용되는 투표 기법입니다:
- N = 복제본 수
- W = 쓰기가 성공으로 간주되려면 확인해야 하는 복제본 수
- R = 읽을 때 참조하는 복제본 수
일반적인 가이드라인은 R + W > N 인데, 이렇게 하면 읽기 집합과 최신 쓰기 집합이 적어도 하나의 복제본에서 겹치므로 오래된 데이터를 읽을 확률을 줄여줍니다. 쿼럼은 분할을 제거하지 않지만, 누가 진행할 수 있는지(예: 다수 쪽) 정의해 줍니다.
최종적 일관성(eventual consistency)이란 무엇이며 어떤 이상 현상을 기대해야 하나요?
최종적 일관성은 복제본들이 일시적으로 서로 다른 상태일 수 있지만 시간이 지나면 같은 값으로 수렴하기만 하면 된다는 의미입니다. 흔히 나타나는 이상 현상은:
- 오래된 읽기
- 자신이 쓴 내용이 바로 보이지 않는 현상
- 순서가 뒤바뀐 업데이트
이들을 완화하기 위해 시스템은 보통 읽기 수리(read repair), 힌트 전달(hinted handoff), 주기적 항등성 검사(anti-entropy) 같은 배경 작업을 사용합니다.
분할 후 충돌 쓰기는 어떻게 해결되나요?
분할이 치유된 뒤 충돌은 다음과 같이 해결됩니다:
- 최종 쓰기 우선(LWW): 가장 최신 타임스탬프가 이기는 방식—간단하지만 중요한 데이터를 묵묵히 잃을 수 있고, 시계 차이(clock skew)에 취약합니다.
- 버전 벡터/인과 메타데이터: 어떤 버전이 다른 버전을 포함하는지(충돌이 아닌지) 식별해 안전하게 병합할 수 있게 합니다.
- 병합 / CRDT: 카운터나 집합 같은 특정 데이터 타입에 대해 자동으로 결정론적 병합을 제공해 높은 가용성을 유지하면서도 충돌을 피합니다.
어떤 전략을 쓸지는 데이터에 대한 “정확함”의 정의에 따라 달라집니다.
내 애플리케이션에 어떤 일관성/가용성 태도를 선택해야 하나요?
결정은 비즈니스 리스크와 사용자가 감수할 수 있는 장애 모드에 따라 내려야 합니다:
- 강한 일관성(C): 금전, 권한, 재고 등 잘못된 결과가 큰 문제를 만드는 도메인에 적합합니다.
- 가용성(A): 응답성이 중요하고 일시적 불일치가 허용되는 피드, 분석, 캐시 등에 적합합니다.
실무적으로는 운영 단위별로 일관성 수준을 달리하거나, 중요한 기록은 강하게, 파생 뷰는 가용성 중심으로 분리하는 방식이 흔합니다.