8장 - 분산 시스템의 골칫거리

@Soo · February 02, 2022 · 19 min read

  • 분산 시스템을 다루는 것은 한 컴퓨터에서 실행되는 소프트웨어를 작성하는 일과는 다르다.

    • 뭔가 잘못될 수 있고 새롭고 흥미진진한 방법이 많다는 점이다.
  • 이번장은 분산 시스템에서 잘못될지도 모르는 것에 관한 개요이다.

결함과 부분 장애

  • 단일 컴퓨터에서 실행되는 소프트웨어는 하드웨어가 올바르게 동작하면 같은 연산은 항상 같은 결과를 낸다.
  • 좋은 소프트웨어가 설치된 각각의 컴퓨터는 보통 완전하게 동작하거나 전체 장애가 발생하지 그 중간 상태가 되지는 않는다.
  • 네트워크로 연결된 여러 컴퓨터에서 실행되는 소프트웨어를 작성할 때는 근본적으로 상황이 다르다.
  • 분산 시스템에서는 시스템의 어떤 부분은 잘 동작하지만 다른 부분은 예측할 수 없는 방식으로 고장난다. 이를 부분 장애(partial failure)라고 한다. 부분 장애는 비결정적이라서 어렵다.
  • 비결정성과 부분 장애 가능성이 분산 시스템을 다루게 어렵게 한다.

신뢰성 없는 네트워크

  • 분산 시스템을 구축하는 유일한 방법은 아니지만 몇 가지 이유로 인터넷 서비스로 구축하는 방법이 주된 방법이 됐다.
  • 인터넷과 데이터센터 내부 네트워크 대부분은 비동기 패킷 네트워크다.
  • 노드에서 다른 노드로 패킷을 보낼 수 있지만 네트워크는 패킷이 언제 도착할지 혹은 메시지가 도착하기는 할 것인지 보장하지 않는다.
  • 요청을 보내고 응답을 기다릴 때 여러가지가 잘못될 수 있다.

    • 요청이 손실됐을 수 있다.
    • 요청이 큐에서 대기하다 나중에 전송될 수 있다.
    • 원격 노드의 장애
    • 원격 노드의 일시적인 중지
    • 원격 노드 응답의 손실
    • 원격 노드 응답의 지연
  • 이런 문제를 다루는 흔한 방법은 타임아웃이다. 얼마 간의 시간이 지나면 응답 대기를 멈추고 응답이 도착하지 않는다고 가정한다.

현실의 네트워크 결함

  • 아직 신뢰성 있는 네트워크를 만드는데 성공하지 못했다.
  • 네트워크 중단의 주요 원인인 인적 오류로부터 보호해주지 못하기 때문이다.
  • 네트워크 결함이 드물더라도 일어날 수 있고 소프트웨어가 이를 처리할 수 있어야 한다.
  • 반드시 네트워크 결함을 견뎌내도록 처리할 필요는 없다.
  • 네트워크가 평상시에는 상당히 믿을 만하다면 네트워크에 문제가 있을 때 그냥 사용자에게 오류 메시지를 보여주는 것도 타당한 방법이다.

결함 감지

  • 많은 시스템은 결함 있는 노드를 자동으로 감지할 수 있어야 한다.

    • 로드 밸런서는 죽은 노드로 요청을 그만 보내야 한다.
    • 단일 리더 복제를 사용한다면 분산 데이터베이스에서 리더에 장애가 나면 팔로워 중 하나가 리더로 승격돼야 한다.
  • 하지만 네트워크에 관한 불확실성 때문에 노드가 동작 중인지 아닌지 구별하기 어렵다.
  • 뭔가 동작하지 않는다고 명시적으로 알려주는 피드백을 받을 수 있다.

타임아웃과 기약 없는 지연

  • 타임아웃만이 결함을 감지하는 확실한 수단이라면 타임아웃은 얼마나 길어야 될까?
  • 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어진다.
  • 타임아웃이 짧으면 노드가 일시적으로 느려졌을 뿐인데도 죽었다고 잘못 선언할 위험이 높아진다.

    • 노드가 죽었다고 선언되면 그 노드의 책무를 다른 노드로 전달돼야 해서 다른 노드와 네트워크에 추가적인 부하를 준다.
  • 비동기 네트워크는 패킷을 가능한 빨리 보내려고 하지만 패킷이 도착하는 데 걸리는 시간에 상한치는 없다.
  • 서버 구현은 대부분 어떤 최대 시간 내에 요청을 처리한다고 보장할 수 없다.

네트워크 혼잡과 큐 대기

  • 자동차를 운전할 때 코통 체증 때문에 이동 시간이 달리지는 것 처럼 컴퓨터 네트워크에서 패킷 지연의 변동성은 큐 대기 때문인 경우가 많다.
  • 여러 장비가 같은 목적지로 네트워크 트래픽을 보내면 스위치 큐가 가득찰 수 있다.
  • TCP는 타임아웃 안에 확인 응답을 받지 않으면 패킷이 손실됐다고 간주하고 손실된 패킷은 자동으로 재전송한다.

    • 애플리케이션에서는 패킷 손실이나 재전송이 보이지 않지만 그 결과로 생기는 지연은 보인다.
  • 지연의 변동성이 얼마나 되는지 알아내는 방법은 어렵다.
  • 더 좋은 방법으로는 고정된 타임아웃을 설정하는 대신 시스템이 지속적으로 응답 시간과 그들의 변동성을 측정하고 관찰된 응답 시간 분포에 따라 타임아웃을 자동으로 조절하게 하는 것이다. 카산드라가 사용한다.

동기 네트워크 대 비동기 네트워크

  • 동기식 네트워크가 대표적인 예는 고정 회선 전화 네트워크다.

    • 전화 네트워크는 극단적인 신뢰성을 지닌다.
    • 음성 프레임이 지연되거나 통화가 유실되는 일은 매우 드물다.
    • 통화를 할 때 회선(circuit)이 만들어지고 통화를 하는 두 명 사이에 있는 전체 경로를 따라서 그 통화에 대해 고정되고 보장된 양의 대역폭이 만들어진다.
    • 데이터가 여러 라우터를 거치더라도 큐 대기 문제를 겪지 않는다.
  • 회선은 만들어져 있는 동안 다른 누구도 사용할 수 없는 고정된 양의 예약된 대역폭이지만 TCP 연결의 패킷은 가용한 네트워크 대역폭을 기회주의적으로 사용한다.
  • 인터넷 네트워크는 회선 교환 방식으로 패킷을 교환한다.

    • 이들은 순간적으로 몰리는 트래픽에 최적화됐기 때문이다.
    • 통화는 초당 비트 개수가 고정돼지만 웹 페이지 요청은 가능하면 빨리 완료되기만 하면 된다.
    • 순간적으로 몰리는 데이터 전송에 전용 회선을 쓰면 네트워크 용량을 낭비하고 전송이 불필요하게 느려진다.
    • TCP는 가용한 네트워크 용량에 맞춰 데이터 전송률을 동적으로 조절한다.

신뢰성 없는 시계

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

단조 시계 대 일 기준 시계

  • 컴퓨터는 최소 두 가지 종류의 시계를 갖고 있다.

    • 일 기준 시계(time-of-day clock)
    • 단조 시계(monotonic clock)

일 기준 시계(time-of-day clock)

  • 일 기준 시계는 현재 날짜와 시간을 반환한다.

    • 예를 들어 자바의 System.currentTimeMillis()에포크(epoch) 이래로 흐른 밀리초를 반환한다.
    • 에포크(epoch)란 UTC(협정세계시) 1970년 1월 1일 자정을 가르킨다.
  • 일 기준 시계는 보통 NTP로 동기화 된다.
  • 로컬 시계가 NTP 서버 보다 너무 앞서면 강제로 리셋되어 과거 시점으로 거꾸로 뛰는 것처럼 보일 수 있다.

단조 시계(monotonic clock)

  • 단조 시계는 타임아웃이나 서비스 응답 시간 같은 시간 구간을 재는 데 적합하다.

    • 자바의 System.nanoTime() 이 있다.
  • 단조 시계란 이름은 항상 앞으로 흐른다는 사실에서 나왔다.

    • 일 기준 시계는 시간이 거꾸로 뛸 수도 있다.

시계 동기화와 정확도

  • 단조 시계는 동기화가 필요 없지만 일 기준 시계는 NTP서버로 동기화되어야 한다.
  • 하드웨어 시계나 NTP 서버는 시계 정확도에 오차가 생길 수 있다.
  • 충돌 해소 전략 중 하나인 최종 쓰기 승리(last write wins, LWW) 는 이런 시간의 오차로 문제가 발생할 수 도 있다.

    • 따라서 가장 최근 값을 유지하고 다른 것들을 버림으로써 충돌을 해소하고 싶어도 최근의 정의는 로컬 일 기준 시계에 의존하며 그 시계는 틀릴 수 있다는 것을 아는게 중요하다.

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

  • 분산 시스템에는 공유 메모리가 없고 지연 변동이 큰 신뢰할 수 없는 네트워크를 통해 메시지를 보낼 수 있을 뿐이며 부분 장애, 신뢰성 없는 시계, 프로세스 중단에 시달릴 수 있다.
  • 원격 노드가 응답하지 않으면 그 노드가 어떤 상태에 있는지 알 방법은 없다.
  • 분산 시스템에서 우리는 동작에 관해 정한 가정을 명시하고, 이런 가정을 만족시키는 방식으로 실제 시스템을 설계 할 수 있다.
  • 이번 장의 나머지 부분에서 분산 시스템의 지식과 진실에 관한 개념을 살펴본다.

진실은 다수결로 결정된다

  • 분산 시스템은 한 노드에만 의존할 수는 없다. 노드에 언제든 장애가 나서 잠재적으로 시스템이 멈추고 복구할 수 없게 될 수 있기 때문이다.
  • 여러 분산 알고리즘은 정족수, 즉 노드들 사이의 투표에 의존한다.

    • 특정한 노드 하나에 대한 의존을 줄이기 위해 결정을 하려면 여러 노드로부터 어떤 최소 개수의 투표를 받아야 한다.

리더와 잠금

  • 분산 시스템에서 어떤 노드를 선택하는 행위(파티션의 리더, 잠금을 획득한 자, 사용자명을 차지는데 성공한 사용자의 요청)는 주의해야한다.
  • 어떤 노드가 이전에 리더였더라도 시간이 흐른 사이에 다른 노드들이 그 노드가 죽었다고 선언하면 그 노드는 강등되고 다른 리더가 이미 선출됐을지도 모르기 때문이다.
  • 분산 잠금을 잘못 구현 : 클라이언트 1은 임차권이 마료됐는데도 여전히 유효하다고 생각해서 저장소에 있는 파일을 오염시킨다.

펜싱 토큰

  • 위의 분산 잠금 문제를 해결하기 위해서는 자신이 선택된 자라고 잘못 믿고 있는 노드가 나머지 시스템을 방해할 수 없도록 보장해야 한다.
  • 이 목적을 달성하는 기법으로 펜싱(fencing)이 있다.
  • 잠금 서버가 잠금이나 임차권을 승인할 때마다 펜싱 토큰도 반환하도록 한다.

    • 펜싱 토큰은 잠금이 될 때마다 증가하는 숫자다.
    • 클라이언트가 쓰기 요청을 할 때 이 펜싱 토큰을 함께 보내 현재 알고 있는 펜싱 토큰값과 비교하여 선택된 자를 식별할 수 있다.
  • 이 메커니즘은 오래된 토큰을 사용해서 쓰는 것을 거부함으로써 토큰을 확인 역활을 한다.

비잔틴 결함

  • 노드가 거짓말(임의의 결함이 있거나 오염된 응답을 보냄)을 할지도 모른다는 위험이 있다면 훨씬 더 어려워진다.

    • 펜신 토큰에서 클라이언트는 고의로 가짜 펜신 토큰을 보내면 시스템의 보장을 무너뜨릴 수 있다.
  • 어떤 노드가 실제로는 받지 않은 메시지를 받았다고 주장할 수 있는데 이런 동작을 비잔틴 결함(Byzantine fault)라고 한다.
  • 이렇게 신뢰할 수 없는 환경에서 합의에 도달하는 문제를 비잔틴 장군 문제라고 한다.
  • 일부 노드가 오동작하고 프로토콜을 준수하지 않거나 악의적인 공격자가 네트워크를 방해하더라도 시스템에 계속 올바르게 동작한다면 비잔틴 내결함성을 지닌다라고 한다.
  • 웹 애플리케이션은 웹브라우저 같은 클라이언트 행동이 임의적이고 악의적이라고 예상해야 한다. 이는 입력 확인, 변환, 출력 이스케이핑이 중요한 이유다.

    • 예를 들어 SQL 인젝션이나 XSS 같은 해킹을 막아야 한다.
  • 대부분의 비잔틴 내결함성 알고리즘은 노드의 2/3 이상의 다수가 올바르게 동작하기를 요구한다.

정리

  • 분산 시스템에서 나타날 수 있는 문제는 광범위하다.

    • 네트워크 결함
    • 노드 간 시계의 오차
    • 원격노드의 상태를 알 수 없는 문제
  • 대부분의 분산 알고리즘은 원격 노드를 아직 쓸 수 있는지 결정하기 위해 타임아웃을 사용한다.
  • 분산 시스템에선 한 노드에만 의존할 수 없으므로 정족수를 이용하는 프로토콜이 필요하다.
@Soo
RDBMS, NoSQL, 분산 처리에 관심이 많은 백엔드 엔지니어입니다.