본문 바로가기

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

[8] 분산 시스템의 골칫거리 3가지

분산시스템은 잘못될 수 있는 새롭고 흥미진진한 방법이 많다. 하지만 우리는 모든 게 잘못되더라도 사용자의 기대를 보장하는 시스템을 구축해야 한다.

대규모 컴퓨팅 시스템 구축 방법에 따른 장애 대응 차이

1. 고성능 컴퓨팅

  • 슈퍼컴퓨터 같이 수천 개 CPU 가진 컴퓨터
  • 작업 상태를 체크 포인트로 저장한다.
  • 부분 장애(어떤 부분은 동작하나 다른 부분은 예측 할 수 없게 나는 장애) 나면 전체 장애로 확대해서 전체가 죽게 한다.
  • 나중에 체크포인트 기준으로 복구한다.

2. 클라우드 컴퓨팅

  • 상용 컴퓨터
  • 애플리케이션은 24시간 낮은 지연시간으로 운영돼야하기 때문에 고성능 처럼 전체가 죽게 하면 안된다!

신뢰성 없는 네트워크

우리가 주로 다루는 분산시스템은 네트워크로 통신하는 비공유 시스템이다.

보통 비동기 패킷 네트워크이기 때문에 응답을 보장하지 않아 요청 손실, 원격 노드 무응답, 응답 손실로 인해 네트워크 신뢰성을 보장할 수 없다.

※ 네트워크 결함: 네트워크 일부가 다른 쪽과 차단되는 것

결함 감지를 타임아웃으로 판단할 수 있지만 얼마만큼 기다릴지 정하는게 쉽지 않다. 너무 짧으면 다른 노드로 책무가 전가되기 때문에 추가적인 부하를 주어 응답이 느려질 수 있기 때문이다.

→ 결과적으로 "올바른" 타임아웃 값은 없으며 실험을 통해 결정해야 한다.

패킷 지연이 변동적인 원인 중 하나: 큐 대기

-동시에 같은 목적지(포트)로 보낸다거나, 도착지 CPU 코어가 바빠 패킷 처리를 못 한다거나, TCP 흐름제어(혼잡 회피)로 부가적으로 큐 대기를 한다거나

네트워크 통신을 전화선처럼 동기식으로 통신하면 안돼?

전화기에 쓰는 네트워크는 극단적인 신뢰성을 지닌다. 통화하는 두 명사이에 회선이 만들어져 전체 경로를 따라 고정되고 보장된 양의 대역폭이 할당된다. 큐 대기 문제도 겪지 않는다.

그럼 앺 네트워크 통신(패킷 교환)을 전화기처럼 동기식(회선 교환)으로 바꾸면 왜 안 됨?

  • 회선 네트워크는 만들어진 동안 고정된 크기를 그 누구도 사용할 수 없다.
  • 트래픽이 몰리면 고정적인 회선 만드는데 네트워크 용량 낭비하고 느려질 수 있다.
  • 그러므로 많은 트래픽 발생 가능한 네트워크에선 가변 패킷을 사용하는 패킷 교환 방식이 적합하다.

시계

서버마다 시계 값이 미묘하게 다를 수 있다. 트랜잭션 이벤트는 더 늦게 쓰기 요청을 했지만 절대 시간이 더 빨라 빨리 쓰기 요청한 트랜잭션이 누락될 수 있음을 알아야 한다.

시계 종류

1. 일 기준 시계

  • epoch 타임
  • 서버 시간을 NTP(Near Time Protocol)서버시간으로 맞추면 과거 시간으로 뛰는 현상도 발생가능 하다.

2. 단조 시계

  • System.nanotime()같은 상대적인 시계. 항상 앞으로만 흐른다.

전역 스냅숏용 동기화된 시계(트랜잭션 간 순서(인과성) 보장하는 법)

단일 DB라면 단조 증가하는 번호로 트랜잭션 ID 를 부여하면 된다. 근데 분산 DB는 그럴 수 없다.

분산 DB에서 발생하는 트랜잭션의 인과성을 어떻게 보장할 수 있을까? 즉 A 트랜잭션이 B 트랜잭션보다 빨리 발생했다 라는 사실을 알아내야한다.

▶ 타임스탬프를 트랜잭션ID 로 사용한다. 대신 가장 빠른과 늦은 타임스탬프를 포함하는 신뢰구간 A=[Aearliest, Alatest], B=[Bearliest, Blatest]을 생성한다.

A와 B 의 두 구간이 겹친다면 누가 먼저 실행됐는지를 확신할 수 있다.

프로세스 중단

리더가 하나씩임을 보장하는 방법 중 하나로

리더가 다른 노드들로부터 임차권(lease) 을 얻고, 주기적으로 갱신하는 방법이 있다. 만료되면 리더 아니라 가정하고 다른 노드에게 넘겨주는 것이다.

근데 A 구간에서 15초동안 멈춰버리면 임차권이 만료됐음에도 불구하고 알아차리지 못할것이다

근데 15초 동안이나 멈춰버릴 수 있는 이유

  • GC stop-the-world 시간
  • os 컨텍스트스위치할 때 실행중인 스레드는 코드 임의 지점에서 멈출 수 있다.
  • 디스크로 스왑가능하다면 데이터를 디스크에서 메모리로 로딩할 때 느린 IO 연산동안 스레드는 멈춘다.

그럼 우짤래? 정족수 알고리즘(다수결) 이나 펜싱 토큰으로 알아차리기

보장 알고리즘

정족수 알고리즘(quorum)

  • 노드가 응답 요청을 보냈으나 다른 노드는 받을 수 없는 경우
  • 내 메시지를 다른 노드가 받지 못하는구나를 확인했을 경우
  • stop-the-world 의 긴 시간 후에 복귀했으나 다른 노드는 죽은 줄 알고 있던 경우

→ 죽은 줄 알았지만 아닌 경우이기 때문에 그 판단을 한 노드에 의존할 수 없다.

그래서 과반수 이상의 동의를 얻어야 결정을 할 수 있다.

펜싱 토큰(fencing token)

한 클라이언트만 잠금을 획득해 쓰기 요청을 할 수 있다 가정하자

클라이언트1은 잠금 획득하고 GC 때문에 멈췄어서 잠금 만료됐었는데 돌아오고 나서 자신이 여전히 잠금 갖고있는 줄 알고 데이터에 쓰는 불상사가 일어날 수 있다.

해결: 펜싱 토큰을 활용해 이미 처리된 토큰보다 오래된 토큰이 쓰려하면 거절시킨다.

※ 비잔틴 결함(Byzantine fault)

고의로 특정 메시지를 받았다고 주장하거나 가짜 펜싱 토큰 포함한 메시지를 보내는결함. 정직하지 않은 거짓말로 인한 결함

일부 노드가 오작동하고 악의적인 공격자가 네트워크를 방해해도 시스템이 올바르게 동작하면 비잔틴 내결함성을 지닌다고 말한다.

결론: 데이터 시스템은 추상 시스템 모델이다. 현실에선 무궁무진한 예외에 결함이 있지만 낙관적으로 가정하고(ex. crash-recovery 모델에서 죽어도 데이터는 남아있다고 가정) 관리가능한 결함의 집합을 뽑아내서 문제를 해결한다. 다음장에서 문제에 대처할 수 있는 알고리즘을 살펴보겠다.

 

레퍼런스

데이터 중심 애플리케이션 8장