8. The Trouble with Distributed Systems

앞 장에서는 분산형 시스템의 가능한 문제점을 알아보았다. 현실은 그것보다 심하다. 이 문제점들을 알아보자. 모든 것이 잘못되었을 때도 동작하는 시스템을 알아보자. 원인과 대처법을 알아보자.

Faults and Partial Failures

단일 컴퓨터에서 프로그램을 실행시키면 예상가능한 형태로 동작한다. 단일 컴퓨터에서의 소프트웨어는 결정론적이다. 이것은 의도된 컴퓨터 설계이다. 하지만 네트워크로 연결된 여러 컴퓨터에서 동작하는 소프트웨어를 쓴다면 상황은 근본적으로 달라진다. 분산형 시스템에서는 시스템의 다른 부분이 잘 동작해도 일부 부분이 예상치 못하게 고장날 수 있으며 이는 비결정론적이다. 이것이 분산형 시스템을 작업하기 어렵게 만든다.

Cloud Computing and Supercomputing

대형 컴퓨팅 시스템을 설계하는 철학이 있다. 한 극단은 고성능 연산이다. 다른 극단은 클라우드 컴퓨팅이다. 기업 데이터센터는 이 중간에 있다. 어느 극단이냐에 따라 실패를 처리하는 데 있어 매우 다른 접근법을 쓴다. 이 책에서는 인터넷 서비스를 구현하는 시스템에 집중한다.

  • 많은 인터넷 기반 애플리케이션은 온라인이다. 즉, 어느 때나 사용자를 낮은 지연시간으로 도울 수 있어야 한다.
  • 클라우드 서비스의 노드는 슈퍼컴퓨터처럼 특수화된 하드웨어로 동작하지 않는다.
  • 데이터센터 네트워크는 높은 이분 대역폭을 가져야 한다.
  • 시스템이 커질 수록 그 컴포넌트 일부가 고장날 가능성은 커진다.
  • 시스템은 고장난 노드에 대처하고 계속 동작을 하는 것이 좋다.
  • 통신은 인터넷을 통해 이루어지므로 지역 네트워크에 비해 느리고 신뢰도가 떨어진다.

분산형 시스템이 동작하려면 부분적 고장의 가능성을 수용하고 소프트웨어에서 이 실패 처리 메커니즘을 만들어야 한다. 노드가 몇 개 없더라도 이것은 중요하다. 생각할 수 있는 다양한 고장에 대처하라.

Unreliable Networks

공유 없이 네트워크를 통해 연결된 기기들에 대해 알아보자. 이는 시스템을 만드는 유일한 방법은 아니지만 널리 쓰이는 방법이다. 인터넷과 데이터센터의 내부 네트워크는 비동기 패킷 네트워크이다. 요청을 발생할 시 여러 문제가 발생할 수 있으며, 발신자는 패킷이 발송됐는지도 알 수 없다. 그래서 타임아웃을 둔다.

Network Faults in Practice

실제 네트워크는 신뢰성 있지 않다. 이는 매우 흔하다. EC2 같은 클라우드 서비스는 일시적 정지가 잦아서 악명이 높다. 흔하지 않더라도 대비는 해야 한다. 문제가 정의되거나 테스트되지 않으면 나중에 큰 문제가 된다. 네트워크 고장에 대처하는 것이 꼭 해결하는 것을 의미하진 않는다.

Detecting Faults

많은 시스템은 자동적으로 고장 노드를 감지해야 한다. 그러나 이는 쉽지 않다. 도착지의 프로세스가 응답하지 않는지를 체크하거나, 스크립트를 두거나, 하드웨어 레벨에서 감지하거나, 도달 불능 패킷을 받거나 등으로 체크할 수 있다. 원격 노드에서의 빠른 피드백이 있다면 좋겠지만 그에 의존할 수는 없다. 반응이 전혀 없을 가능성도 염두에 둬야 한다.

Timeouts and Unbounded Delays

타임아웃은 얼마나 길어야 하나? 정답은 없다. 노드 고장을 너무 빨리 단정하면 문제가 된다. 그 노드의 책임이 부당하게 재분배되기 때문이다. 어떤 기준이 있으면 그 기준에 따라 정할 수도 있다. 그러나 비동기 네트워크는 딜레이에 제한이 없다.

Network congestion and queueing

컴퓨터 네트워크의 패킷 딜레이는 대기에 의존한다. 여러 다른 노드가 같은 도착지에 패킷을 보내려고 할 때, 도착지의 CPU 유휴 코어가 없을 때, 가상화된 환경에서 운영 체제가 멈춰 있을 때, TCP가 흐름 제어를 할 때. 또한, TCP는 타임아웃 내에 반응이 없으면 유실 패킷으로 간주되어 재전송된다. 이 모든 것이 패킷 딜레이에 영향을 준다. 공적 클라우드나 복수 데이터센터에서는 자원이 많은 소비자에게 분배되어 있다. 그런 환경에서 타임아웃은 실험적으로 정할 수밖에 없다. 타임아웃을 때에 따라 조정하는 것이 좋다.

Synchronous Versus Asynchronous Networks

네트워크는 왜 패킷을 고정된 최대 딜레이를 두지 않는 것일까? 왜 예측가능성과 신뢰성을 갖지 못하는가? 일반 전화 네트워크는 동기적이다. 인터넷은 그렇지 않다.

Can we not simply make network delay predictable?

전화 네트워크의 회로는 TCP 연결과 매우 다르다. 데이터센터 네트워크와 인터넷은 회로 기반이 아니라 패킷 기반이다. 이는 간헐적으로 폭발하는 트래픽에 최적화하기 위한 선택이다. 이로서 가변적인 데이터 전송률을 둘 수 있다. 둘 다 지원하는 비동기 전환 모드 등의 시도가 있기는 했다. 그러나 이는 복수 데이터센터에는 아직 구현되지 않았다.

Unreliable Clocks

시계와 시간은 중요하며, 여러 성질을 만족시켜야 한다. 분산형 시스템에서 통신은 즉각적이지 않으므로 시간은 까다로운 일이다. 또한, 네트워크 내 각 기기는 각각의 시계를 갖고 있으며 완전히 정확하지 않다.

Monotonic Versus Time-of-Day Clocks

컴퓨터 시계에는 두 종류가 있다: 현재 시계와 단조 시계.

Time-of-day clocks

현재 시계는 우리가 일반적으로 생각하는 시계이다. 이는 NTP를 통해 동기화된다. 오래된 시스템에서의 현재 시계는 조립질이다.

Monotonic clocks

단조 시계는 시간 구간을 측정하는 데 쓰인다. 두 시점간 차를 통해 얼마 만큼 시간이 지났나를 측정한다. 복수의 CPU 소켓이 있을 때는 CPU마다 별개의 타이머가 있을 수 있으며 이들은 동기화되지 않을 수도 있다. NTP는 이를 동기화시킬 수 있다. 동기화가 필요없기 때문에 분산형 시스템에서는 이에 크게 대처하지 않아도 된다.

Clock Synchronization and Accuracy

단조 시계는 동기화가 필요 없지만 현재 시계는 그렇지 않다. 컴퓨터의 석영 시계는 그렇게 정확하지 않다. 동기화가 항상 되는 것도 아니다. 노드가 잠시 동안 방화벽에 막힐 수도 있다. NTP 동기화도 한계점이 있다. NTP 서버 자체에 문제가 있을 수도 있다. 윤초가 있는 경우에도 문제가 된다. 가상 기기에서는 하드웨어 시계도 가상화되어 있다. 완전히 제어할 수 없는 기기에서 소프트웨어를 돌리면 기기의 하드웨어 시계를 믿을 수 없다. 큰 노력을 들이면 좋은 시계 정확도를 얻을 수 있다. 이는 GPS 수신자 등을 통해 얻을 수 있다.

Relying on Synchronized Clocks

시계의 문제는 쓰기는 쉽지만 함정이 많다는 것이다. 시계에 대해서도 네트워크가 실패할 때 로컬 소프트웨어가 대처해야 한다. 부정확한 시계의 문제는 간과하기 쉽다. 동기화된 시계를 필요로 하는 소프트웨어를 짠다면 모든 기기간 시계 차이를 항상 주시해야 한다.

Timestamps for ordering events

시계에 의존했을 때 위험해지는 상황을 보자. 타임스탬프로 이벤트 순서를 매기는 것이 그렇다. 시간 순서로 타임 스탬프를 매겼을 때, 시계가 잘못될 수 있다. 이 때 마지막에 쓴 기록이 승리하는 방식을 쓸 수 있다. 그러면 데이터 일관성이 깨진다. 데이터베이스 쓰기가 사라질 수도 있다. 동시적인 쓰기와 간발의 차로 연속된 쓰기를 구별하기 힘들다. 시계가 밀리초 해상도를 가졌는데 이벤트 시각 차이는 이것보다 적을 수도 있다. 그러므로 타임스탬프를 쓸 때 시계에 의존하면 안 된다. NTP 동기화는 믿을 수 있을까? 그렇지도 않다. 그러므로 논리적 시계를 쓰는 것이 좋다.

Clock readings have a confidence interval

기계의 현재 시계를 마이크로초나 나노초 단위로 읽어야 할 수 있다. 하지만 시계를 읽는 것은 그 자체로 시간이 걸린다. 이는 체크할 수는 있다. 하지만 대다수의 시스템은 이 불확실성을 노출하지 않는다. 예외는 Google의 TrueTime 정도이다.

Synchronized clocks for global snapshots

앞에서는 스냅샷 분리에 대해 다뤄보았다. 이는 대개 단조증가하는 트랜잭션 ID로 구현된다. 하지만 여러 기기에 분산된 네트워크에서는 쓰기 힘들다. 현재 시계를 쓸 수 있을까? 시계 정확도의 문제다. 그래서 쓰기 시에는 시계의 신뢰구간만큼 기다려야 한다. 이는 현재 활발히 연구 중인 분야이다.

Process Pauses

분산형 시스템에서 또 다른 시계 사용의 위험한 예를 알아보자. 리더 노드가 리더임을 다른 노드에 알게 하려면 어떻게 할까? 타임아웃 있는 락을 걸 수 있다. 하지만 이 때 동기화된 시계에 의존하면 안 된다. 타임아웃이 적절하지 않을 수도 있다. 프로그램의 실행이 타임아웃보다 긴 시간 동안 예상치 못하게 중단된 경우가 있을 수 있다. 쓰레기 수집이 도는 경우, 가상 기기가 지연되는 경우, 랩탑 등을 덮는 경우, 컨텍스트 스위치가 발생하는 경우, 스레드가 디스크 입출력 연산을 기다리는 경우, 페이지 폴트가 있는 경우, 프로세스가 OS에 의해 중지되는 경우. 이 모든 것들은 동작 중인 스레드를 선점할 수 있다. 단일 기기에서 멀티스레드 코드를 쓸 때는 스레드 안전성을 보장할 여러 도구가 있지만 분산형 시스템에서는 그렇지 않다. 분산형 시스템에서 노드는 그 실행이 예측할 수 없는 시간 동안 중지될 수 있음을 가정해야 한다.

Response time guarantees

많은 프로그래밍 언어와 운영 체제에서 스레드/프로세스가 무기한 종료될 수 있는 원인은 제거할 수 있다. 어떤 환경에서는 강-실시간 시스템 요구 조건이 있을 수 있다. 차의 온보드 센서, 실시간 운영체제. 그러나 이런 것들은 프로그래밍 언어, 라이브러리, 도구의 범위를 크게 줄인다. 서버 쪽 데이터 프로세싱에서는 이는 적절하지 않다.

Limiting the impact of garbage collection

프로세스 중단의 부정적 효과는 강-실시간 시스템 요건을 완화해서 대응할 수 있다. 쓰레기 수집은 노드의 일시 고장으로 다룰 수 있다. 또는 쓰레기 수집을 짧은 시간 생존한 오브젝트에 돌리고 주기적으로 프로세스를 재시작할 수 있다. 이들은 쓰레기 수집 중단을 완전히 막을 순 업지만 많이 줄일 수 있다.

Knowledge, Truth, and Lies

지금까진 분산형 시스템이 단일 컴퓨터의 프로그램과 다른 점을 알아보았다. 이들은 상식과 많이 달라보일 수 있다. 그러면 시스템에 대해 무엇을 알아야 하나? 동작에 대해서 어떤 가정을 해야 하나? 쉽지는 않은 문제다.

The Truth Is Defined by the Majority

노드가 살아 있어도 노드의 메시지를 대다수의 노드가 들을 수 없으면 그 노드는 죽은 것이다. 노드의 메시지가 퍼지지 않아도 마찬가지다. 아니면 쓰레기 수집이 긴 시간동안 지속될 수 있을 때. 즉 노드는 그 자신의 상황 판단을 믿을 수 없다. 네트워크 내 노드의 다수가 그것을 결정한다. 대개 이 기준은 과반으로 결정된다.

The leader and the lock

시스템에서는 대개 단 하나의 리더/락 획득/동일한 사용자명 등이 허용된다. 이를 분산형 시스템에 적용하기는 까다롭다. 노드의 고장에 대응할 수 있어야 하기 때문이다. 락을 잘못 걸면 데이터가 오염된다. 이는 이전에 다룬 프로세스 중단의 예와 비슷하다.

Fencing tokens

락을 이용해 어떤 자원에 대한 접근을 보호할 때 펜싱을 칠 수 있다. 락 서버가 락을 얻을 때마다 펜싱 토큰을 반환할 수 있다. 서버는 이에 기반해 락의 획득을 거절할 수 있다. 이는 단조증가하기 때문에 조건을 만족한다. 이 메커니즘은 자원 자체가 토큰을 체크하는 것을 필요로 한다. 토큰은 서버 쪽에서 검증하는 것이 좋다.

Byzantine faults

펜싱 토큰은 의도치 않게 오작동하는 노드는 막을 수 있지만 의도적으로 오작동하는 노드는 막기 힘들다. 노드는 강건하지 않지만 정확하다고 가정했다. 분산형 시스템에서는 그렇지 않을 수도 있다. 공격자가 의도적으로 동작을 속일 경우나 노드의 정확성이 잘못될 경우에 대처하는 것을 비잔틴 고장 허용이라 한다. 항공우주 산업에서 CPU 레지스터가 망가질 때, 악의적 공격자가 존재할 때. 이 책에서는 대개 그런 상황을 가정하지 않는다. 하지만 웹 애플리케이션에서는 그런 상황을 가정해야 한다. 소프트웨어의 버그도 그런 가정으로 여겨질 수 있다. 프로토콜이 이에 대응할 수 있다면 좋겠지만 그것을 기대하긴 힘들다.

Weak forms of lying

소프트웨어에 노드의 약한 거짓말에 대처 가능하게 만드는 것이 좋다. 네트워크 패킷의 손상, 사용자의 잘못된 입력에 대한 대처, NTP 시스템에서 동기화.

System Model and Reality

분산형 시스템 문제를 풀기 위해 많은 알고리즘이 설계되었다. 이 때 시스템 모델 설계가 중요하다. 동기 모델, 부분 비동기 모델, 비동기 모델이 쓰인다. 노드에 대해서도 노드 모델 설계가 필요하다. 크래시-중단 폴트, 크래시-복구 폴트, 비잔틴 폴트 등의 모델이 존재한다.

Correctness of an algorithm

알고리즘의 정확성은 어떻게 판단하나? 다음의 조건을 만족해야 함을 요구할 수 있다. 유일성, 단조성, 가용성.

Safety and liveness

안전성과 라이브니스를 구별하는 것은 중요하다. 어떻게 구별하나? 안전성은 나쁜 일이 일어나지 않는 것이고 라이브니스는 언젠가 좋은 일이 일어나는 것이다. 안전성이 깨지면 언제 고장이 났는지 알 수 있다. 라이브니스는 시점을 특정할 수는 없다. 이를 구별하는 것은 어려운 시스템 모델에 대처하는 데 도움이 된다. 하지만 라이브니스 성질에 대해서는 여러 함정이 존재한다.

Mapping system models to the real world

안전성과 라이브니스 특성과 시스템 모델은 분산형 알고리즘의 정확성을 판단하는 데 도움이 된다. 하지만 현실적인 여러 문제도 있다. 쿼럼 알고리즘은 노드가 저장한 데이터를 기억한다는 것에 의존한다. 알고리즘은 그 자체가 가정하는 것들이 있다. 이는 이론이 무의미하다는 것이 아니다. 구현이 중요하다는 것이다.

Summary

이 장에서는 분산형 시스템에서 발생할 수 있는 넓은 범위의 문제를 알아보았다. 예를 들면:

  • 네트워크를 통해 패킷을 전송하려 할 때 이것이 유실되거나 임의로 지연될 수 있다. 마찬가지로, 그 응답도 유실되거나 지연될 수 있다. 그러므로 응답을 받지 못하면 메시지가 전달되었는지를 알 수 없다.
  • 노드의 시계는 다른 노드의 것들과 맞지 않을 수 있다. (NTP을 설정했더라도) 이는 갑자기 전진하거나 후진할 수 있다. 그러므로 이에 의존하는 것은 위험하다. 시계의 신뢰구간을 대개 접근할 수 없기 때문에.
  • 프로세스는 실행 중 어느 시간에도 상당한 동안 중단될 수 있다. (프로세스 중단 쓰레기 수집 등의 이유로) 이는 다른 노드에 의해 죽었다고 선언될 수 있고 그것이 중단된 지도 모른 채 다시 되살아날 수 있다.

이런 부분적 실패가 발생할 수 있다는 특성은 분산형 시스템의 성질을 정의한다. 소프트웨어가 다른 노드를 포함하는 일을 하면 그것은 종종 실패하거나, 무작위로 느려지거나, 아예 응답하지 않을 수도 있다 (그래서 타임아웃이 발생하거나). 분산형 시스템에서는 부분적 고장에 대한 대처를 소프트웨어에서 수행해야 한다. 그래서 시스템의 특정 부분이 고장나더라도 전체적으로는 잘 동작하도록 해야 한다.

실패에 대처하기 위해서는 첫 번째로 이를 감지해야 하지만 이조차도 어렵다. 많은 시스템은 노드가 고장났는지를 감지하는 정확한 메커니즘이 없으므로 대다수의 분산형 알고리즘은 타임아웃에 의존해 원격 노드가 가용가능한지를 판정해야 한다. 그러나 타임아웃은 네트워크와 노드의 고장을 구별할 수 없으며, 가변적인 네트워크 딜레이는 노드가 고장났음을 부당하게 의심하도록 만든다. 게다가, 가끔 노드는 성능 저하된 상태에 빠지기도 한다. 예를 들어, Gigabit 네트워크 인터페이스는 드라이버 버그로 인해 갑자기 1kb/s 처리량으로 떨어지기도 한다. 이렇게 잘 동작하지 못하지만 죽지 않은 노드는 완전히 고장난 노드보다 더 다루기 힘들다.

실패가 감지되었다면 시스템이 거기에 대처하게 하는 것도 쉽지 않다. 전역 변수가 없고, 공유 메모리가 없고, 기기간 공통 지식이나 공유 상태가 없기 때문이다. 노드들은 시간도 서로 동의할 수 없고, 다른 심오한 것들은 더 그렇다. 한 노드에서 다른 노드로 정보가 흐를 수 있는 방법은 신뢰 불가능한 네트워크를 통한 것들뿐이다. 중요한 결정들은 단일 노드에 의해 안전하게 결정될 수 없다. 그러므로 프로토콜은 다른 노드들을 모아 과반 이상이 동의하게 해야 한다.

같은 동작이 항상 결정론적으로 같은 결과를 내는 이상적 수학적 완전성이 있는 단일 컴퓨터에서 동작하는 소프트웨어를 썼다면 분산형 시스템의 지저분한 물리적 현실이 충격이 될 수 있다. 역으로, 분산형 시스템 엔지니어는 문제가 단일 컴퓨터에서 풀리면 단순한 것으로 간주하기도 한다 – 요즘 단일 컴퓨터는 많은 것을 할 수 있음에도 불구하고. 판도라의 상자를 열지 않고 단일 기기에서 동작을 계속할 수 있다면 일반적으로 그것이 낫다.

하지만, 앞에서 다뤘듯이, 확장 가능성은 분산형 시스템을 사용할 유일한 이유가 아니다. 실패에 대한 대처와 낮은 지연 시간 (데이터를 사용자와 지정학적으로 가까이 두는 것)은 똑같이 중요한 목표이며 이는 단일 노드를 사용해서는 얻을 수 없는 목표이다.

이 장에서는 네트워크, 시계, 프로세스의 비신뢰가능성의 어쩔 수 없는 측면들을 다뤄보았다. 사실 그렇지는 않다. 강 실시간 반응 보장 조건을 주고 제한된 딜레이를 네트워크에 걸 수도 있다. 하지만 이것은 매우 비용이 많이 들고 하드웨어 사용의 활용량을 줄인다. 대다수의 안전성에 치명적이지 않은 시스템은 비싸고 신뢰성 있는 시스템 대신 싸고 신뢰성 덜한 시스템을 택한다.

신뢰성 있는 컴포넌트를 가정하므로 컴포넌트가 실패하면 전체가 중단되고 재시작되어야 하는 슈퍼컴퓨터에 대해서도 살짝 알아보았다. 반면에, 분산형 시스템은 서비스 수준에서는 중단되지 않고 영원히 지속될 수 있어야 한다. 왜냐하면 모든 고장과 유지보수가 노드 수준에서, 최소한 이론적으로는 다뤄질 수 있기 때문이다. (실제로는 나쁜 설정 사항 변경이 모든 노드에 퍼지면 분산형 시스템도 이런 상황에 실패할 수 있다.)

이 장에서는 이 문제들을 다뤄보았고 암울한 전망을 주었다. 다음 장에서는 해법에 대해 다뤄보고 분산형 시스템의 문제에 대처할 수 있는 알고리즘들을 알아볼 것이다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중