포스트

데이터 중심 애플리케이션 설계09


일관성과 협의

분산 시스템에서 가장 어려운 문제 중 하나는 여러 노드에 걸쳐 있는 데이터를 일관성 있게 유지하는 것입니다. 네트워크 지연, 노드 장애, 메시지 손실 등의 문제가 언제든 발생할 수 있는 환경에서 모든 노드가 동일한 상태를 유지하기 위해서는 많은 사항을 고려해야합니다.

최종 일관성의 함정

대부분의 복제된 데이터베이스는 최종 일관성(Eventual Consistency)을 제공합니다. 이는 “언젠가는 모든 복제본이 같은 데이터를 가지게 될 것”이라는 약속입니다. 하지만 여기에는 중요한 함정이 있습니다.

예시: 2014년 FIFA 월드컵 결승전에서 앨리스가 경기 결과를 확인하고 밥에게 “독일이 이겼어!”라고 말했습니다. 하지만 밥이 자신의 휴대폰으로 확인해보니 경기가 아직 진행 중이라고 나왔습니다. 이는 앨리스의 앱이 빠른 복제본에, 밥의 앱이 느린 복제본에 연결되었기 때문입니다.

이런 상황에서 개발자는 혼란스러울 수밖에 없습니다. 단일 스레드 프로그램에서는 변수에 값을 쓴 직후 읽으면 방금 쓴 값을 볼 수 있지만, 분산 시스템에서는 그렇지 않을 수 있기 때문입니다.

선형성: 단일 복제본의 환상

선형성(Linearizability)은 이런 문제를 해결하기 위한 강력한 일관성 모델입니다. 핵심 아이디어는 마치 데이터의 단일 복제본만 존재하는 것처럼 시스템을 보이게 하는 것입니다.

선형성의 작동 원리

선형성 시스템에서는 다음과 같은 규칙이 적용됩니다:

  • 쓰기 시작 전: 모든 읽기는 이전 값을 반환
  • 쓰기 완료 후: 모든 읽기는 새로운 값을 반환
  • 핵심 제약: 한 클라이언트가 새 값을 읽은 후에는, 다른 모든 클라이언트도 반드시 새 값을 읽어야 함

비선형성 예시: 클라이언트 A가 값 4를 읽은 후, 클라이언트 B가 같은 시점에 이전 값인 2를 읽는다면 이는 선형성을 위반합니다. 마치 시간이 거꾸로 흘러가는 것과 같기 때문입니다.

선형성이 중요한 실제 상황들

  1. 고유성 제약: 사용자명 중복 방지, 항공권 좌석 중복 예약 방지
  2. 크로스 채널 의존성: 사용자가 이미지 업로드 후 “완료되었습니다” 메시지를 받았는데, 실제로는 아직 처리되지 않은 상황 방지
  3. 금융 거래: 계좌 잔액 확인 후 이체 처리 시 정확성 보장

실제 시스템에서의 선형성 구현

단일 리더 복제

장점: 리더가 쓰기 순서를 결정하므로 구현이 상대적으로 간단 문제점: 리더 장애 시 새로운 리더가 이전 리더의 모든 커밋된 쓰기를 가지고 있어야 함

다중 리더 복제

각 리더에서 쓰기가 다른 순서로 처리될 수 있어 선형성 보장이 어렵습니다.

리더리스 복제 (쿼럼 시스템)

오해: “쿼럼 읽기/쓰기(w + r > n)면 강력한 일관성을 얻는다” 현실:

  • 시계 스큐에 기반한 LWW(Last Write Wins)는 선형성을 보장하지 않음
  • 네트워크 지연 변동으로 인해 나중에 읽은 값이 더 오래된 경우 발생 가능

Cassandra 사례: LWW 충돌 해결 방식으로 인해 선형성을 잃지만, 높은 가용성을 제공

CAP 정리의 오해와 진실

일반적 오해: “일관성, 가용성, 분할 내성 중 2개를 선택할 수 있다”

실제 의미: 네트워크 분할은 분산 시스템에서 피할 수 없는 현실이므로, 실제로는 분할 발생 시 일관성 또는 가용성 중 하나를 선택해야 합니다.

실제 시스템 사례

CP 시스템 (일관성 + 분할 내성)

  • MongoDB: 프라이머리 노드가 접근 불가능해지면 새로운 프라이머리가 선출될 때까지 쓰기를 중단하여 일관성을 보장
  • HBase: 리전 서버 장애 시 해당 리전을 다른 서버로 이전할 때까지 서비스 중단

AP 시스템 (가용성 + 분할 내성)

  • Cassandra: 네트워크 분할 상황에서도 각 노드가 읽기/쓰기를 계속 수행하지만, 일관성이 일시적으로 깨질 수 있음
  • DynamoDB: 최종 일관성 모델을 통해 높은 가용성 제공

순서의 중요성: 인과성과 선형성

분산 시스템에서 순서는 매우 중요합니다. 두 가지 관점에서 살펴볼 수 있습니다:

인과성 (Causality)

정의: 한 이벤트가 다른 이벤트에 영향을 미치는 관계 특징: 부분 순서(Partial Order) - 모든 이벤트가 비교 가능하지 않음

실제 예시:

  • 사용자 A가 게시글을 작성 → 사용자 B가 댓글 작성 (인과 관계 존재)
  • 서로 다른 사용자가 동시에 다른 게시글 작성 (인과 관계 없음)

선형성의 총체적 순서

정의: 모든 작업이 하나의 시간선상에서 원자적으로 실행되는 것처럼 보임 특징: 총체적 순서(Total Order) - 모든 작업이 비교 가능

람포트 타임스탬프: 논리적 시간 구현

물리적 시계의 문제점을 해결하기 위해 람포트 타임스탬프를 사용할 수 있습니다.

람포트 타임스탬프의 작동 방식

1
2
3
4
5
6
타임스탬프 = (카운터, 노드ID)

규칙:
1. 각 노드는 지금까지 본 최대 카운터 값을 추적
2. 요청 시마다 최대값을 포함하여 전송
3. 받는 노드는 자신의 카운터를 최대값으로 업데이트 후 증가

한계: 순서는 알 수 있지만, 커밋 이후에만 순서를 확인할 수 있다. 커밋 중에는 내 순서를 보장할 수 없음. 예를 들면 고유 닉네임을 내가 점유한게 맞는지??

전역 순서 브로드캐스트: 실용적 해결책

전역 순서 브로드캐스트(Total Order Broadcast)는 모든 노드가 메시지를 동일한 순서로 받도록 보장하는 메커니즘입니다.

핵심 속성

  • 신뢰할 수 있는 전달: 메시지 손실 없음
  • 완전히 순서화된 전달: 모든 노드에서 동일한 순서

실제 활용 사례

펜싱 토큰을 이용한 분산 잠금:

1
2
3
1. 잠금 요청이 순서대로 로그에 기록됨
2. 로그상의 위치가 펜싱 토큰이 됨
3. 높은 토큰을 가진 요청만 처리하여 분산 잠금 구현

상태 머신 복제: 모든 노드가 동일한 순서로 명령을 실행하여 일관된 상태 유지

2단계 커밋: 분산 트랜잭션의 현실

2PC(Two-Phase Commit)는 분산 트랜잭션에서 원자성을 보장하는 대표적인 방법입니다.

작동 과정

준비 단계:

  • 코디네이터가 모든 참여자에게 “준비” 요청 전송
  • 참여자는 트랜잭션 데이터를 디스크에 쓰고 “예/아니오” 응답

커밋 단계:

  • 모든 참여자가 “예”면 “커밋” 요청 전송
  • 하나라도 “아니오”면 “중단” 요청 전송

2PC의 근본적 문제

블로킹 문제: 코디네이터가 커밋 결정 후 충돌하면 참여자들이 “미확정” 상태에서 영구 대기

실제 사례: XA 트랜잭션에서 애플리케이션 서버가 충돌한 경우, 데이터베이스들이 트랜잭션 상태를 알 수 없어 무한 대기하는 상황

장애 허용 합의 알고리즘

합의 알고리즘의 핵심 속성

  • 균일한 합의: 모든 노드가 같은 값으로 결정
  • 무결성: 한 번만 결정
  • 유효성: 제안된 값만 결정 가능
  • 종료: 모든 정상 노드가 결국 결정

성능과 일관성의 트레이드오프

선형성의 비용

선형성을 보장하려면 동기식 복제가 필요하고, 이는 성능에 큰 영향을 미칩니다.

멀티코어 CPU도 선형적이지 않음: 최신 다중코어 CPU의 RAM조차 선형적이지 않을 정도로 선형적인 시스템은 드뭅니다. 이는 성능을 위해 일관성을 포기한 결과입니다.

실용적 접근: 약한 일관성 모델

대부분의 실제 시스템은 성능과 확장성을 위해 약한 일관성 모델을 사용합니다.

스냅샷 격리: 인과적 일관성을 제공하면서도 네트워크 지연에 영향받지 않는 가장 강력한 일관성 모델

최종 일관성 + 보상 트랜잭션:

  • 전자상거래 예시: 재고를 엄격하게 검사하지 않고 주문을 받은 후, 재고 소진 시 주문 취소 및 환불 처리
  • 장점: 높은 처리량과 가용성
  • 단점: 고객 경험에 일시적 영향

실제 시스템에서의 선택 기준

일관성이 중요한 경우

금융 시스템:

  • 계좌 잔액, 거래 내역
  • 선택: CP 시스템 (MongoDB, 관계형 DB)

예약 시스템:

  • 항공권 좌석, 호텔 방 예약
  • 선택: 강한 일관성 필요

가용성이 중요한 경우

소셜 미디어:

  • 게시글, 댓글, 좋아요
  • 선택: AP 시스템 (Cassandra, DynamoDB)

CDN, 캐시:

  • 정적 콘텐츠 배포
  • 선택: 최종 일관성으로 충분

ZooKeeper: 합의 서비스의 실제 활용

ZooKeeper는 다음과 같은 분산 시스템 문제를 해결합니다:

핵심 기능들

  • 리더 선출: 분산된 서비스들 중 하나를 리더로 지정
  • 서비스 디스커버리: 클러스터 내 서비스 위치 추적
  • 구성 관리: 설정값의 일관된 배포
  • 분산 잠금: 펜싱 토큰을 이용한 안전한 잠금

제한사항

처리량 한계: 매초 수천~수백만 번 변경되는 애플리케이션에는 적절하지 않고, 느리게 변하는 메타데이터 관리에 특화

합의 알고리즘의 한계와 현실

성능 오버헤드

모든 합의 알고리즘은 과반수 원칙을 따릅니다:

  • 1개 노드 장애 허용: 최소 3개 노드 필요
  • 2개 노드 장애 허용: 최소 5개 노드 필요

네트워크 분할 시: 과반수 그룹만 동작 가능, 나머지는 차단

비동기 시스템의 불가능성

이론적으로는 단 하나의 노드 장애만으로도 비동기 시스템에서 합의가 불가능합니다. 하지만 실제 알고리즘들은 부분 동기성을 가정합니다 - 네트워크가 대부분의 시간에는 타임아웃 내에 작동한다는 가정입니다.

결론: 균형 잡힌 접근

분산 시스템 설계에서는 완벽한 해답이 없습니다. 대신 다음을 고려해야 합니다:

요구사항 분석

  • 일관성이 절대적으로 필요한가? (금융, 예약 시스템)
  • 가용성이 더 중요한가? (소셜 미디어, 로그 수집)
  • 지연 시간에 얼마나 민감한가?

하이브리드 접근

  • 핵심 데이터: 강한 일관성 (관계형 DB, 합의 서비스)
  • 파생 데이터: 최종 일관성 (캐시, 검색 인덱스)
  • 이벤트 소싱: 불변 이벤트 로그 + 뷰 재구성

점진적 복잡성 증가

  1. 단순 시작: 단일 노드 시스템
  2. 읽기 확장: 읽기 전용 복제본 추가
  3. 쓰기 확장: 샤딩 도입
  4. 고가용성: 다중 리더 또는 리더리스 복제
  5. 강한 일관성: 합의 알고리즘 도입
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.