본문 바로가기
스터디/데이터 중심 애플리케이션 설계

[데이터 중심 애플리케이션 설계] 08. 분산 시스템의 골칫거리

by 디토20 2023. 1. 15.
반응형

 

 

 

 

[데이터 중심 애플리케이션 설계] 08. 분산 시스템의 골칫거리

 

1. 결함과 부분 장애

  • 수천 개의 노드가 있는 분산 시스템은 항상 뭔가 고장난 상태라고 가정하는게 합리적이다.
  • 분산 시스템이 동작하게 만드려면 부분 장애 가능성을 항상 받아들이고 내결함성 메커니즘을 넣어야 한다. 
  • 즉, 신뢰성 없는 구성 요소를 사용해 신뢰성 있는 시스템을 구축해야 한다.
    • 신뢰성 없는 IP(Internet Protocol)위에 신뢰성 높은 TCP(Transimission Control Protocal)를 두어 패킷 손실 시 재전송하고 순서에 맞춰 재조립되도록 보장해준다.

2. 신뢰성 없는 네트워크

  • 어떤 분산 시스템은 비공유 시스템, 네트워크가 유일한 통신 수단인 다수의 장비들이다.
  • 인터넷과 데이터센터 내부 이더넷은 비동기 패킷 네트워크다. 즉, 노드는 다른 노드로 메시지를 보낼 수 있지만 네트워크는 메시지가 언제 도착할 것인지 보장하지 않는다.
  • 요청을 보내고 응답을 기다릴 때 잘못될 수 있는 경우는 많다.
    • 요청이 손실됨(네트워크 케이블 뽑힘)
    • 요청이 큐에서 대기하다가 나중에 전송됨(네트워크나 수신자에 과부하)
    • 원격 노드에 장애(노드가 죽음)
    • 원격 노드의 일시적인 중지(GC)
    • 원격 노드가 요청을 처리했지만 응답이 네트워크에서 손실
    • 원격 노드가 요청을 처리했지만 응답이 지연
  • 전송 측은 패킷이 전송됐는지 아닌지조차 구별할 수 없다. 유일한 정보는 응답을 아직 받지 못했다는 것이다.
  • 이런 문제를 다루는 대표적인 방법은 타임아웃을 활용하여 응답이 도착하지 않았음을 가정한다.

3. 현실의 네트워크 결함

  • 누구도 네트워크 문제에서 자유로울 수 없다.
  • 상어가 해저 케이블을 물어뜯어서 손상시키기도 한다.
  • 시스템 환경에서 네트워크 결함이 드물더라도 일어날 수 있다는 사실은 소프트웨어가 이를 처리할 수 있어야 한다는 뜻이다.

3.1 결함 감지

  • 많은 시스템은 결함 있는 노드를 자동으로 감지할 수 있어야 한다.
    • 로드 밸런서는 죽은 노드로 요청을 보내면 안된다.
    • 단일 리더 복제를 사용하는 분산 데이터베이스 시스템에서 리더에 장애가 발생하면 팔로워 중 하나가 리더로 승격돼야 한다.
  • 하지만, 불행하게도 네트워크에 관한 불확실성 때문에 노드가 동작 중인지 아닌지 구별하기 어렵다.
    • TCP가 패킷이 전달됐다는 확인 응답을 했더라도 애플리케이션이 그것을 처리하기 전에 죽을 수도 있다.
    • 몇 번 재시도를 해 보고 타임아웃이 게속 발생하면 마침내 노드가 죽었다고 선언할 수 있다.

3.2 타임아웃과 기약 없는 지연

  • 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어진다.
  • 타임아웃이 짧으면 결함을 빨리 발견되지만 노드의 일시적 중단에도 죽었다고 잘못 판단할 위험이 있다.
    • 성급하게 노드가 죽었다고 선언하면 같은 동작이 여러번 수행될 수 있다.
    • 만약 과부하로 인해 노드가 죽었다고 잘못 판단하는 경우 리밸런싱 과정은 더욱 더 상태를 악화시킬 수 있다.
  • 고정된 타임아웃을 설정하는 대신 시스템이 지속적으로 응답 시간과 그들의 변동성을 측정해 유동적으로 타임아웃을 조절하게 하는 것이 좋다.
  •  

4. 동기 네트워크 vs 비동기 네트워크

동기식 네트워크

  • 동기식 네트워크의 대표적인 예시는 전화 네트워크이다. 전화 네트워크는 극단적인 신뢰성을 가진다.
  • 전화 네트워크에서 통화할 때는 회선(circuit)이 만들어지며 통화가 끝날 때까지 유지된다.
  • 동기식 네트워크는 이미 특정 공간만큼의 회선이 할당되어 있기 때문에 데이터가 여러 라우터를 거치더라도 큐 대기 문제를 겪지 않는다.
  • 큐 대기가 없으므로 네트워크 종단 지연 시간의 최대치가 고정돼 있다.
  • 동기식 네트워크와 같이 회선을 할당하는 방식은 통화와 같은 초당 전송하는 비트 수가 고정되어 있는 경우 회선이 적절하지만 웹 페이지 요청과 같이 순간적으로 몰릴 수 있는 데이터 전송에 효율적이지 못하다.
  • 회선이 점유한 대역폭만큼을 계속 보유하기 때문에 실제 대역폭 만큼 데이터를 전송하지 않더라도 대역폭은 계속 할당되는 대신 지연의 변동은 적다.

비동기식 네트워크

  • 비동기 네트워크의 대표적인 예시는 인터넷이다. 인터넷은 대역폭을 동적으로 공유한다.
  • 전송 측은 가능하면 빨리 패킷을 보내기 위해 서로 밀치며 네트워크 스위치가 빈번하게 어떤 패킷을 보낼지(대역폭을 할당할지) 결정한다. 이 방법은 큐 대기가 생길 수 있지만 선로를 효율적으로 이용할 수 있다.
  • 이 방식은 자원을 최대한 효율적으로 사용한다. 하지만 지연이라는 큰 변동이 생기게 된다.

5. 신뢰성 없는 시계

  • 네트워크에 있는 개별 장비는 자신의 시계를 갖고 있다. 
  • 이 장치는 완벽히 정확하지 않아서 각 장비는 자신만의 시간 개념이 있으며 이는 다른 장비보다 약간 빠를 수도 느릴 수도 있다.

5.1 일 기준 시계 대 단조 시계

  • 현대 컴퓨터는 최소 두 가지 종류의 시계를 갖고 있다. 일 기준 시계(time-of-day clock) 단조 시계(monotinic clock)다.

5.1.1 일 기준 시계

  • 일 기준 시계는 벽시계 시간이라고도 하며 현재 날짜와 시간을 반환한다.
    • Java의 System.currentTimeMillis()는 epoch이래로 흐른 밀리초를 반환한다.
  • 일 기준 시계는 보통 NTP로 동기화된다.
  • 윤초는 세지 않으며, 로컬 시게가 NTP 서버보다 앞설 경우 강제로 과거 시점으로 리셋 되어 종종 시간이 과거로 가는 것 같아 보인다.
  • NTP로 동기화를 하더라도 네트워크 지연이 있기 때문에 모든 분산시스템에서 완벽히 동일한 일 기준 시계를 가지는건 불가능하다.

5.1.2 단조 시계

  • 단조 시계는 항상 앞으로만 흐르는 시계로 컴퓨터 별로 고유한 값을 가진다.
    • Java의 System.nanoTime()가 대표적인 예다.
    • 컴퓨터 별로 고유하기 때문에 다른 컴퓨터의 단조 시계와 비교하는건 의미가 없다.
  • 단조 시계는 타임아웃이나 서비스 응답 시간 같은 지속 시간과 같이 두 시점 사이에 흐른 시간이 얼마인지 재는데 적합하다.
  • 로컬 시계가 NTP보다 빠르거나 느릴 때 단조 시계가 진행하는 진도수를 조정할 순 있지만 단조 시계가 앞이나 뒤로 뛰게 할 수는 없다.
  • 단조 시계의 해상도는 보통 상당히 좋기 때문에 분산 시스템에서 경과 시간을 재는 데 단조 시계를 쓰는 것이 일반적으로 좋다.

5.2 시계 동기화와 정확도

  • 하드웨어 시계와 NTP의 시계는 정확하지 않다. 다양한 사례로 시계의 정확도가 어긋날 수 있다.
    • 장비의 온도에 따라 하드웨어 시계에 영향을 줄 수 있다.
    • NTP 서버와의 지연으로 인해 오차가 발생할 수 있다.
  • 카산드라는 충돌 해소 전략으로 최종 쓰기 승리(LWW)를 사용하는데 시계는 정확하지 않기 때문에 이로 인해 문제가 발생할 수도 있다.
    • 가장 최근 값을 유지한다 하더라도 결국 최근의 정의는 로컬 일 기준 시계에 의존하기 때문에 완벽히 정확할 수 없다는 것을 아는게 중요하다.

5.3 시계 읽기는 신뢰 구간이 있다.

  • 분산 시스템에서 각 시스템별로 시간차를 보장할 수 있는 신뢰 구간이 있다면 이를 통해 순서를 보장할 수 있다.
    • 신뢰 구간이 5ms이고 A작업이 1ms에 시작됐고 B작업이 7ms에 시작됐다고하면 A작업은 B작업보다 빠른 시점에 수행되었음을 확신할 수 있다.
    • 이런식으로 순서를 보장하기 위해선 신뢰 구간까지 기다려야 하기 때문에 신뢰 구간을 최대한 짧게 유지하는 것이 중요하다.

6. 지식, 진실, 그리고 거짓말

6.1 진실은 다수결로 결정된다

  • 분산 시스템은 한 노드에만 의존할 수 없다. 
  • 노드는 언제든 장애가 나서 잠재적으로 시스템이 멈추고 복구할 수 없게 될 수 있다. 각 노드는 자신의 판단을 믿을 수 없다.
  • 여러 분산 알고리즘은 정족수를 활용한다.
    • 정족수는 노드가 죽었다고 선언하는 것에 관한 결정에서도 사용된다.
    • 정족수를 이룬 노드들이 다른 노드를 죽었다고 선언하면 그 노드는 여전히 살아있을지 몰라도 죽었다고 간주되어야 한다.

6.2 리더와 잠금

  • 시스템이 오직 하나의 뭔가가 필요할 때가 자주 있다.
    • 스플릿 브레인을 피하기 위해 오직 한 노드만 데이터베이스의 파티션 리더가 될 수 있다.
    • 오직 하나의 트랜잭션이나 클라이언트만 특정한 자원이나 객체에 잠금을 획득 할 수 있다.
    • 유일한 식별 키는 오직 한 명의 사용자만 가질 수 있다.
  • 리더와 잠금을 분산 시스템에서 구현하려면 주의해야 한다.
    • 어떤 노드가 이전에 리더였더라도 네트워크가 잠시 중단되었을 뿐 실제로 그 노드가 살아있음에도 불구하고 다른 노드가 그 노드를 죽었다고 선언해 새로운 리더를 선출했을 수도 있다.

  • 위 예시는 잠금을 잘못 구현해서 생긴 데이터 오염 버그를 보여준다.
  • 클라이언트 1이 임차권을 획득하고 stop-the-world로 중단이 되었을 때 임차권이 만료되어 클라이언트 2가 쓰기를 수행했지만 클라이언트 1은 여전히 임차권을 보유했다고 잘못 판단하여 쓰기 충돌이 발생한다.

6.2.1 펜싱 토큰

  • 위와 같은 문제를 해결하기 위한 단순한 기법으로 펜싱(fencing)기법이 있다.

  • 잠금 서버가 잠금이나 임차권을 승인할 때 마다 값이 하나씩 증가하는 펜싱 토큰도 반환하다.
  • 클라이언트가 쓰기 요청을 보낼 때 자신의 현재 펜싱 토큰을 포함하도록 하여 서버는 이 토큰 값을 비교하여 쓰기를 수행하도록 판별할 수 있다.

6.2.2 비잔틴 결함

  • 펜싱 토큰은 부주의에 의한 오류에 빠진 노드를 감지하고 차단할 수 있다.
  • 그러나 노드가 고의로 시스템 보장을 무너뜨리려 한다면 가짜 펜싱 토큰을 보내기만 하면 된다.
  • 보통 노드들이 신뢰성은 없을 수 있지만 정직하다고 가정한다. 노드가 거짓말을 할지도 모른다는 위험이 있다면 훨씬 더 어려워 진다.
  • 노드가 실제로는 받지 않은 메세지를 받았다고 주장하는 것을 비잔틴 결함이라고 하며 이렇게 신뢰할 수 없는 환경에서 합의에 도달하는 문제를 비잔틴 장군 문제라고 한다.
    • 일부 노드가 오작동하고 악의적인 공격자가 네트워크를 방해하더라도 시스템이 계속 올바르게 동작한다면 비잔틴 내결함성을 지닌다고 한다.
    • 이런 관심사는 항공기와 같은 시스템에서 필요로 한다.
  • 대부분의 서버 측 데이터 시스템에서는 조직이 모든 노드를 제어하고 관리하기 때문에 매우 복잡하고 비싼 비잔틴 내결함성 솔루션을 배치하는 것은 실용적이지 못하다.
  • 비잔틴 내결함성은 중앙 권한 없는 Peer-to-peer 네트워크에 더 적합하다.

6.3 약한 형태의 거짓말

  • 노드는 일반적으로 정직하다고 가정하지만, 약한 형태의 "거짓말"로부터 보호해주는 메커니즘을 소프트웨어에 추가하는 것이 좋다.
    • 네트워크 패킷은 때때로 여러 버그로 오염된다.
    • 공개적으로 접근 가능한 애플리케이션은 사용자의 입력을 신중하게 살균해야 한다.
    • NTP 클라이언트는 여러 서버 주소를 설정할 수 있으므로, 동기화 시 잘못 설정된 서버를 검출해서 제거할 수 있다.

7. 시스템 모델과 현실

  • 분산 시스템 문제를 해결하기 위한 알고리즘은 하드웨어와 소프트웨어 세부 사항에 심하게 의존하면 안된다.
  • 시스템에서 발생할 것으로 예상되는 결함의 종류는 시스템 모델로 정형화된다.
  • 시스템 모델은 알고리즘이 가정하는 것을 기술한 추상화다.

7.1 타이밍 가정 시스템 모델

  • 동기식 모델 : 네트워크 지연, 프로세스 중단, 시계 오차에 모두 제한에 있다고 가정한다. 현실적이지 않다.
  • 부분 동기식 모델 : 대부분 동기식처럼 동작하지만, 때때로 기약없는 지연과 중단이 있다. 현실적인 모델이다.
  • 비동기식 모델 : 타이밍에 대한 어떤 가정도 할 수 없다. 심지어 시계가 없을 수도 있다.

7.2 노드용 시스템 모델

  • 죽으면 중단하는 결함 : 노드가 갑자기 응답하기를 멈추면 이후로 그 노드는 영원히 사용할 수 없다.
  • 죽으면 복구하는 결함 : 노드가 죽을 수 있지만 시간이 흐른 후 다시 응답할 것이라고 가정한다.
  • 비잔틴 결함 : 다른 노드를 속이거나 기만하는 것을 포함에 전적으로 무슨일이든 할 수 있다.

 

 

 

 

 

728x90
반응형

댓글