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

예니·2023년 2월 4일
0
post-thumbnail

결함과 부분 장애

  • 좋은 소프트웨어가 설치된 각각의 컴퓨터는 보통 완전하게 동작하거나 전체 장애가 발생하지 그 중간 상태가 되지는 않는다.
  • 분산 시스템에서는 더 이상 이상화된 시스템 모델에서 동작하지 않는다.
  • 분산 시스템에서는 시스템의 어떤 부분은 잘 동작하지만 다른 부분은 예측할 수 없는 방식으로 고장나는 부분 장애가 발생할 수 있다. 부분 장애는 비결정적이라 어렵다.

클라우드 컴퓨팅과 슈퍼컴퓨팅

  • 슈퍼컴퓨터는 분산 시스템보다는 단일 노드 컴퓨터에 가깝다. 슈퍼컴퓨터는 부분 장애를 전체 장애로 확대하는 방법으로 처리한다.
  • 인터넷 서비스를 구현하는 시스템은 슈퍼컴퓨터와 매우 다르며, 분산 시스템은 슈퍼컴퓨터와 비교하여 아래와 같은 특성이 있다.
    • 항상 사용자에게 지연 시간이 낮은 서비스를 제공해야 한다. (중단 배포 X)
    • 클라우드 서비스의 노드는 상용 장비를 사용해 구축하므로 비용이 낮지만 실패율이 높다.
    • 수천 개의 노드가 있는 시스템에서는 항상 뭔가 고장난 상태라고 가정하는 것이 합리적이다.
    • 시스템이 장애 노드를 감내할 수 있고 전체적으로 계속 동작할 수 있다면 운영, 유지보수에 매우 유용한 특성이다.
    • 지리적으로 분산된 배포는 인터넷을 거치는데, 로컬 네트워크에 비해 느리고 신뢰성도 떨어진다.
  • 분산 시스템이 동작하게 만들려면 부분 장애 가능성을 받아들이고 소프트웨어에 내결함성 메커니즘을 넣어야 한다.

신뢰성 없는 네트워크

  • 분산 시스템은 비공유 시스템(네트워크로 연결된 다수의 장비)이다.
  • 인터넷과 데이터센터 내부 네트워크 대부분은 비동기 패킷 네트워크다. 요청을 보내고 응답을 기다릴 때 여러 가지가 잘못될 수 있다.
  • 다른 노드로 요청을 보내서 응답을 받지 못했다면 그 이유를 아는 것은 불가능하다.
  • 이런 문제를 다루는 흔한 방법은 타임아웃이다. 얼마 간의 시간이 지나면 응답 대기를 멈추고 응답이 도착하지 않는다고 가정한다. (실제 동작 여부는 알 수 없다.)

현실의 네트워크 결함

  • 네트워크 결함이 드물더라도 일어날 수 있다는 사실은 소프트웨어가 이를 처리할 수 있어야 한다는 뜻이다.
  • 소프트웨어가 네트워크 문제에 어떻게 반응하는지 알고 시스템이 그로부터 복구할 수 있도록 보장해야 한다.

결함 감지

  • 많은 시스템은 결함 있는 노드를 자동으로 감지할 수 있어야 한다.
  • 요청이 성공했음을 확신하고 싶다면 애플리케이션 자체로부터 긍정 응답을 받아야 한다.
  • 뭔가 잘못되면 스택의 어떤 수준에서 오류 응답을 받을지도 모르지만, 일반적으로 아무 응답도 받지 못할 것이라고 가정해야 한다. 몇 번 재시도 후에 타임아웃 내에 응답을 받지 못하면 노드가 죽었다고 선언할 수 있다.

타임아웃과 기약 없는 지연

  • 타임아웃이 얼마나 길어야 할지에 대한 정답은 없다. 너무 길면 기다리는 시간이 길어지고, 너무 짧으면 살았는데 죽었다고 선언할 수도 있다.
  • 수학적으로 딱 떨어진다면 좋으련만. 하지만 우리가 사용하는 시스템은 대부분 어떤 것도 보장하지 않는다.
  • 비동기 네트워크는 기약 없는 지연(unbounded delay)가 있고, 서버 구현은 대부분 어떤 최대 시간 내에 요청을 처리한다고 보장할 수 없다.

네트워크 혼잡과 큐 대기

  • 컴퓨터 네트워크에서 패킷 지연의 변동성은 큐 대기 때문인 경우가 많다.
    • 여러 노드가 동시에 같은 목적지로 패킷을 보내려할 때, 네트워크 링크가 붐비면 잠시 기다려야 한다. (네트워크 혼잡) 스위치 큐가 꽉 차면 패킷이 유실될 수 있다.
    • 패킷이 목적지 장비에 도착했을 때 모든 CPU 코어가 바쁘다면, 이 요청은 운영체제가 큐에 넣어 둔다.
    • 가상 환경에서 실행되는 운영체제는 다른 가상 장비가 CPU 코어를 사용하는 동안 멈출 수 있다.
    • TCP는 흐름 제어(flow control)를 수행한다. 혼잡 회피(congestion avoidance)나 배압(back pressure)이라고도 한다. 흐름 제어는 노드가 네트워크 링크나 수신 노드에 과부하를 가하지 않도록 자신의 송신율을 제한하는 것이다.
  • 이 모든 요인이 네트워크 지연의 변동성에 영향을 준다. 큐 대기 지연은 시스템이 최대 용량에 가까울 때 특히 광범위하게 일어난다.
  • 이런 환경에서는 실험적으로 타임아웃을 선택하는 수밖에 없다.

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

전화 네트워크에서 통화를 할 때는 회선이 만들어진다. 이런 종류의 네트워크는 동기식이다. 큐 대기가 없으므로 네트워크 종단 지연 시간의 최대치가 고정돼 있어, 이를 제한 있는 지연(bounded delay)이라고 한다.

그냥 네트워크 지연을 예측 가능하게 만들 수는 없을까?

  • 전화 네트워크 회선은 TCP 연결과 매우 다르다.
  • 데이터센터 네트워크와 인터넷이 패킷 교환 방식을 사용하는 이유는 순간적으로 몰리는 트래픽(bursty traffic)에 최적화됐기 때문이다.
  • 회선을 통해 파일을 전송하려면 대역폭 할당을 추정해야 한다. 순간적으로 몰리는 데이터 전송에 회선을 쓰면 네트워크 용량은 낭비되고, 전송은 느려진다. 반대로, TCP는 가용한 네트워크 용량에 맞춰 데이터 전송률을 동적으로 조절한다.
  • 네트워크에서 변동이 큰 지연은 자연 법칙이 아니라 단지 비용/이득 트레이드오프의 결과이다.

신뢰성 없는 시계

  • 분산 시스템에서는 통신이 즉각적이지 않으므로 시간은 다루기 까다롭다.
  • 네트워크에 있는 개별 장비는 자신의 시계를 갖고 있다. 하지만 시간을 어느 정도 동기화할 수 있다. 가장 널리 쓰이는 메커니즘은 네트워크 시간 프로토콜(NTP)로 서버 그룹에서 보고한 시간에 따라 컴퓨터 시계를 조정할 수 있게 한다.

단조 시계 대 일 기준 시계

일 기준 시계

  • 어떤 달력에 따라 현재 날짜와 시간을 반환한다. (벽시계 시간이라고도 함) 리눅스의 경우, 현재 시간을 조회하면 에포크(1970.01.01) 이래로 흐른 초를 반환한다.
  • 일 기준 시계는 보통 NTP로 동기화된다. 한 장비의 타임스탬프는 다른 장비의 타임스탬프와 동일한 의미를 지닌다.

단조 시계

  • 단조 시계는 타임아웃이나 서비스 응답 시간 같은 지속 시간을 재는 데 적합하다.
  • NTP는 컴퓨터의 로컬 시계가 NTP 서버보다 빠르거나 느리다는 것을 발견하면 단조 시계가 진행하는 진도수를 조정할 수도 있다. (시간을 앞,뒤로 돌리는 것은 못한다)

시계 동기화와 정확도

  • 단조 시계는 동기화가 필요 없지만, 일 기준 시계는 NTP 서버나 다른 외부 시간 출처에 맞춰 설정돼야 유용하다.
  • 많은 이유에 의해 하드웨어 시계와 NTP의 시간이 맞지 않게 된다.
  • 많은 자원을 투입하면 시계 정확도를 높일 수는 있다.

동기화된 시계에 의존하기

동기화된 시계가 필요한 소프트웨어를 사용한다면 필수적으로 모든 장비 사이의 시계 차이를 조심스럽게 모니터링해야 한다. 다른 노드와 시계가 너무 차이나는 노드는 죽은 것으로 선언되고 클러스터에서 제거돼야 한다.

이벤트 순서화용 타임스탬프

  • 다중 리더 복제나 리더 없는 데이터베이스에서 여러 노드가 하나의 데이터에 대해 쓰기를 실행했을 때, 더 최근 값으로 수정되는 방식이 있다. 이를 최종 쓰기 승리(LWW)라고 한다.
  • 가장 최근 값을 유지하고 다른 것들을 버림으로써 충돌을 해소하고 싶은 유혹이 들더라도, 최근의 정의는 로컬 일 기준 시계에 의존하며 그 시계는 틀릴 수도 있다는 것을 아는 게 중요하다. (이것이 LWW의 근본적인 문제이다.)
  • 논리적 시계는 진동하는 수정 대신 증가하는 카운터를 기반으로 하며 이벤트 순서화의 안전한 대안이다. 논리적 시계는 일 기준 시간이나 경과한 초 수를 측정하지 않고 이벤트의 상대적인 순서만 측정한다.
  • 일 기준 시계와 단조 시계는 실제 경과 시간을 측정하며 물리적 시계라고 한다.

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

  • 시계 읽기를 어떤 시점으로 생각하는 것은 타당하지 않고, 어떤 신뢰 구간에 속하는 시간의 범위로 읽는 게 나을 것이다.
  • 구글 스패너에 있는 트루타임 API는 예외다. 이 API는 로컬 시계의 신뢰 구간을 명시적으로 보고한다. 가능한 타임스탬프 범위 중 가장 이른 것과 가장 늦은 것을 가리키는 두 개의 값을 받는다.

전역 스냅숏용 동기화된 시계

  • 가장 흔한 스냅숏 격리 구현은 단조 증가하는 트랜잭션 ID가 필요하다.
  • 데이터베이스가 여러 데이터센터에 있는 여러 장비에 분산돼 있을 때는 코디네이션이 필요하므로 전역 단조 증가 트랜잭션 ID를 생성하기 어렵다.
  • 구글 스패너 외에는 이를 해결할 수 있도록 구현한 사례가 없다.

프로세스 중단

분산 시스템의 노드는 어느 시점에 실행이 상당한 시간 동안 멈출 수 있다고 가정해야 한다. 멈춰 있는 동안 외부 세계는 계속 움직인다. 결국 멈춘 노드는 다시 실행되겠지만 시계를 다시 확인할 때까지는 본인이 중단되었다는 것을 알아채지 못한다.

응답 시간 보장

  • 소프트웨어가 응답해야 하는 데드라인이 명시되고, 데드라인을 만족시키지 못하면 전체 시스템의 장애를 유발할 수 있는 시스템을 엄격한 실시간 시스템이라고 한다.
  • 대부분의 서버측 데이터 처리 시스템에게 실시간 보장은 전혀 경제적이지도, 적절하지도 않다.

가비지 컬렉션의 영향을 제한하기

  • GC 중단을 노드가 잠시 동안 계획적으로 중단되는 것으로 간주하고 노드가 가비지 컬렉션을 하는 동안 클라이언트로부터의 요청을 다른 노드들이 처리하게 하는 방법이 있다.
  • 위 아이디어의 변형으로, 수명이 짧은 객체만 가비지 컬렉터를 사용하고 수명이 긴 객체의 전체 GC가 필요할 만큼 객체가 쌓이기 전에 주기적으로 프로세스를 재시작하는 방법이 있다.

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

  • 분산 시스템에서 네트워크에 있는 노드는 어떤 것도 확실히 알지 못한다. 그러면 대체 무엇을 믿을 수 있다고 확신할 수 있을까? 어떻게 확신할 수 있을까?
  • 분산 시스템에서 우리는 동작(시스템 모델)에 관해 정한 가정을 명시하고, 이런 가정을 만족시키는 방식으로 실제 시스템을 설계할 수 있다. 어떤 시스템 모델 내에서 알고리즘이 올바르게 동작하는지 증명할 수 있다. 기반 시스템 모델이 매우 적은 보장만 제공하더라도 신뢰성 있는 동작을 달성할 수 있다는 의미다.

진실은 다수결로 결정된다

분산 시스템은 한 노드에만 의존할 수 없다. 노드에 언제든 장애가 나서 잠재적으로 시스템이 멈추고 복구할 수 없게 될 수도 있기 때문이다. 대신 여러 분산 알고리즘은 정족수(quorum), 즉 노드들 사이의 투표에 의존한다. 특정한 노드 하나에 대한 의존을 줄이기 위해 결정을 하려면 여러 노드로부터 어떤 최소 개수의 투표를 받아야 한다.

리더와 잠금

  • 어떤 노드가 스스로를 선택되 자(파티션 리더, 잠금 획득자 등)라고 믿을지라도 노드의 정족수도 반드시 동의한다는 뜻은 아니다. 다른 노드들이 그 노드가 죽었다고 선언하면 그 노드는 강등되고 다른 리더가 이미 선출됐을지도 모른다.
  • 죽었다고 선언했음에도 그 노드가 선택된 자인 것처럼 계속 행동한다면 신중하게 설계되지 않은 시스템에서 문제가 생길 수 있다.

펜싱 토큰

  • 위의 문제를 막기 위한 단순한 기법으로 펜싱(fencing)이 있다.
  • 펜싱 토큰 기법 잠금 서버가 잠금이나 임차권을 승인할 때마다 펜싱 토큰도 반환한다. 펜싱 토큰은 잠금이 승인될 때마다 증가하는 숫자다. 클라이언트 쓰기 요청마다 자신의 현재 펜싱 토큰을 포함한다. 저장소 서버는 더 큰 토큰 번호를 가진 쓰기를 처리했다면 더 작은 토큰 번호를 가진 쓰기 요청은 거부한다.

비잔틴 결함

  • 어떤 노드가 실제로는 받지 않은 특정 메시지를 받았다고 거짓말할수도 있다. 이를 비잔틴 결함이라고 한다. 펜싱 토큰 기법에서는 노드가 고의로 시스템의 보장을 무너뜨리려 한다면 가짜 펜싱 토큰을 포함한 메시지를 보내기만 하면 된다.
  • 비잔틴 내결함성 솔루션을 배치하는 것은 비용이 커서 실용적이지 않다.
  • 웹 어플리케이션은 최종 사용자가 제어하는 웹브라우저 같은 클라이언트의 행동이 임의적이고 악의적이라고 예상해야 한다. 이는 input validation, sanitization, output escaping이 매우 중요한 이유다. 보통 비잔틴 내결함성 프로토콜을 여기에 쓰지는 않고, 클라이언트의 행동이 허용된 것인지 아닌지를 결정하는 권한을 서버에게 줄 뿐이다.

시스템 모델과 현실

  • 알고리즘은 그들이 실행되는 하드웨어와 소프트웨어 설정의 세부 사항에 너무 심하게 의존하지 않는 방식으로 작성해야 한다. 그러려면 시스템에서 발생할 것으로 예상되는 결함의 종류를 어떻게든 정형화해야 한다. 시스템 모델을 정의해서 정형화하는데, 시스템 모델은 알고리즘이 가정하는 것을 기술한 추상화다.
  • 타이밍 가정에 대해 흔히 사용되는 시스템 모델
    • 동기식 모델
    • 부분 동기식 모델
    • 비동기식 모델
  • 노드 장애에 대해 흔히 사용되는 시스템 모델
    • 죽으면 중단하는(crash-stop) 결함
    • 죽으면 복구하는(crash-recovery) 결함
    • 비잔틴(임의적인) 결함

알고리즘의 정확성

알고리즘은 시스템 모델에서 발생하리라고 가정한 모든 상황에서 그 속성들을 항상 만족시키면 해당 시스템 모델에서 정확하다.

안전성과 활동성

  • 안전성 안전성 속성이 위반되면 그 속성이 깨진 특정 시점을 가리킬 수 있다. 안전성 속성이 위반된 후에는 그 위반을 취소할 수 없다. 이미 손상된 상태다.
  • 활동성 활동성 속성은 반대로 동작한다. 어떤 시점을 정하지 못할 수 있지만 항상 미래에 그 속성을 만족시킬 수 있다는 희망이 있다. (eventually라는 단어를 포함하는 것)
  • 비공식적으로, 안전성은 나쁜 일은 일어나지 않는다라고, 활동성은 좋은 일은 결국 일어난다라고 정의된다.

시스템 모델을 현실 세계에 대응시키기

추상 시스템 모델은 현실 시스템의 복잡함에서 우리가 추론할 수 있는 관리 가능한 결함의 집합을 뽑아내서, 문제를 이해하고 체계적으로 해결할 수 있게 하는 데 엄청난 도움이 된다. 어떤 시스템 모델에서 그것들의 속성이 항상 성립한다고 보여줌으로써 알고리즘이 올바르다고 증명할 수 있다.

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

  • 분산 시스템에서 네트워크에 있는 노드는 어떤 것도 확실히 알지 못한다. 그러면 대체 무엇을 믿을 수 있다고 확신할 수 있을까? 어떻게 확신할 수 있을까?
  • 분산 시스템에서 우리는 동작(시스템 모델)에 관해 정한 가정을 명시하고, 이런 가정을 만족시키는 방식으로 실제 시스템을 설계할 수 있다. 어떤 시스템 모델 내에서 알고리즘이 올바르게 동작하는지 증명할 수 있다. 기반 시스템 모델이 매우 적은 보장만 제공하더라도 신뢰성 있는 동작을 달성할 수 있다는 의미다.

진실은 다수결로 결정된다

분산 시스템은 한 노드에만 의존할 수 없다. 노드에 언제든 장애가 나서 잠재적으로 시스템이 멈추고 복구할 수 없게 될 수도 있기 때문이다. 대신 여러 분산 알고리즘은 정족수(quorum), 즉 노드들 사이의 투표에 의존한다. 특정한 노드 하나에 대한 의존을 줄이기 위해 결정을 하려면 여러 노드로부터 어떤 최소 개수의 투표를 받아야 한다.

리더와 잠금

  • 어떤 노드가 스스로를 선택되 자(파티션 리더, 잠금 획득자 등)라고 믿을지라도 노드의 정족수도 반드시 동의한다는 뜻은 아니다. 다른 노드들이 그 노드가 죽었다고 선언하면 그 노드는 강등되고 다른 리더가 이미 선출됐을지도 모른다.
  • 죽었다고 선언했음에도 그 노드가 선택된 자인 것처럼 계속 행동한다면 신중하게 설계되지 않은 시스템에서 문제가 생길 수 있다.

펜싱 토큰

  • 위의 문제를 막기 위한 단순한 기법으로 펜싱(fencing)이 있다.
  • 펜싱 토큰 기법 잠금 서버가 잠금이나 임차권을 승인할 때마다 펜싱 토큰도 반환한다. 펜싱 토큰은 잠금이 승인될 때마다 증가하는 숫자다. 클라이언트 쓰기 요청마다 자신의 현재 펜싱 토큰을 포함한다. 저장소 서버는 더 큰 토큰 번호를 가진 쓰기를 처리했다면 더 작은 토큰 번호를 가진 쓰기 요청은 거부한다.

비잔틴 결함

  • 어떤 노드가 실제로는 받지 않은 특정 메시지를 받았다고 거짓말할수도 있다. 이를 비잔틴 결함이라고 한다. 펜싱 토큰 기법에서는 노드가 고의로 시스템의 보장을 무너뜨리려 한다면 가짜 펜싱 토큰을 포함한 메시지를 보내기만 하면 된다.
  • 비잔틴 내결함성 솔루션을 배치하는 것은 비용이 커서 실용적이지 않다.
  • 웹 어플리케이션은 최종 사용자가 제어하는 웹브라우저 같은 클라이언트의 행동이 임의적이고 악의적이라고 예상해야 한다. 이는 input validation, sanitization, output escaping이 매우 중요한 이유다. 보통 비잔틴 내결함성 프로토콜을 여기에 쓰지는 않고, 클라이언트의 행동이 허용된 것인지 아닌지를 결정하는 권한을 서버에게 줄 뿐이다.

시스템 모델과 현실

  • 알고리즘은 그들이 실행되는 하드웨어와 소프트웨어 설정의 세부 사항에 너무 심하게 의존하지 않는 방식으로 작성해야 한다. 그러려면 시스템에서 발생할 것으로 예상되는 결함의 종류를 어떻게든 정형화해야 한다. 시스템 모델을 정의해서 정형화하는데, 시스템 모델은 알고리즘이 가정하는 것을 기술한 추상화다.
  • 타이밍 가정에 대해 흔히 사용되는 시스템 모델
    • 동기식 모델
    • 부분 동기식 모델
    • 비동기식 모델
  • 노드 장애에 대해 흔히 사용되는 시스템 모델
    • 죽으면 중단하는(crash-stop) 결함
    • 죽으면 복구하는(crash-recovery) 결함
    • 비잔틴(임의적인) 결함

알고리즘의 정확성

알고리즘은 시스템 모델에서 발생하리라고 가정한 모든 상황에서 그 속성들을 항상 만족시키면 해당 시스템 모델에서 정확하다.

안전성과 활동성

  • 안전성 안전성 속성이 위반되면 그 속성이 깨진 특정 시점을 가리킬 수 있다. 안전성 속성이 위반된 후에는 그 위반을 취소할 수 없다. 이미 손상된 상태다.
  • 활동성 활동성 속성은 반대로 동작한다. 어떤 시점을 정하지 못할 수 있지만 항상 미래에 그 속성을 만족시킬 수 있다는 희망이 있다. (eventually라는 단어를 포함하는 것)
  • 비공식적으로, 안전성은 나쁜 일은 일어나지 않는다라고, 활동성은 좋은 일은 결국 일어난다라고 정의된다.

시스템 모델을 현실 세계에 대응시키기

추상 시스템 모델은 현실 시스템의 복잡함에서 우리가 추론할 수 있는 관리 가능한 결함의 집합을 뽑아내서, 문제를 이해하고 체계적으로 해결할 수 있게 하는 데 엄청난 도움이 된다. 어떤 시스템 모델에서 그것들의 속성이 항상 성립한다고 보여줌으로써 알고리즘이 올바르다고 증명할 수 있다.

0개의 댓글