8. 분산 시스템의 골칫거리
- 지난 몇개의 장은 시스템이 잘못된 것을 어떻게 처리하냐에 대한 내용이였습니다.
- 그러나, 현실을 더 비관적으로 보면 어떤 것이든 잘못될 가능성이 있습니다.
- 분산 시스템을 다루는 것은 한 컴퓨터의 문제와 근본적으로 다르며, 여러 창의적인 방법이 있습니다.
엔지니어의 목표는 모든게 잘못되도 제 역할을 수행하는 시스템을 구축하는 것입니다. 그러기 전 아래에서는 어떤 도전에 직면해 있는지에 대해 이야기합니다.
#
결함과 부분 장애- 컴퓨터에서 프로그램을 작성할 때는 프로그램은 보통 돌아가거나 안돌아가거나로 나뉩니다.
- 가끔 컴퓨터가 재부팅하면 고쳐진다고 생각하지만, 이는 대부분 잘못 작성한 소프트웨어의 결과입니다.
- 단일 컴퓨터에서 실행되는 소프트웨어를 믿지 못할 근본적인 이유는 없습니다.
- 하드웨어가 올바르게 돌아가면 같은 연산은 항상 같은 결과를 만듭니다. (결정적)
- 좋은 소프트웨어가 설치된 각각의 컴퓨터는 보통 완전하게 동작하거나 전체 장애가 발생하지 중간 상태가 되지는 않습니다.
- 컴퓨터 설계시 의도한 부분입니다.
- 네트워크로 연결된 여러 컴퓨터에서 실행되는 소프트웨어는 근본적으로 상황이 다릅니다.
- 분산 시스템에서는 매우 광범위한 것들이 잘못될 수 있습니다.
- 분산 시스템에서는 시스템의 어떤 부분은 잘 동작하지만 다른 부분은 예측할 수 없는 방식으로 고장이 나는데 이를 부분 장애(partial failure) 라고 합니다.
- 부분 장애는 비결정적(성공했는지 아닌지 알지 못할 수 있음)이므로 어렵습니다.
#
클라우드 컴퓨팅과 슈퍼 컴퓨팅- 기업형 데이터센터는 고성능 컴퓨팅 분야와 클라우드 컴퓨팅의 중간 지점에 있습니다.
- 위의 철학에 따라 결함 처리 방법도 매우 다릅니다.
- 슈퍼컴퓨터는 분산 시스템보다는 단일 노드 컴퓨터에 가깝기 때문에, 부분 장애를 전체 장애로 확대하는 방법으로 처리합니다. (장애시, 전체가 죽도록 만듦)
- 이 책은 인터넷 서비스를 구현하는 시스템을 집중적으로 다루며 이는 보통 슈퍼컴퓨터와 매우 다릅니다.
- 인터넷 관련 어플리케이션은 언제든 사용자에게 지연 시간이 낮은 서비스를 제공하므로 온라인입니다.
- 슈퍼컴퓨터는 특화된 하드웨어를 사용해서 각 노드 사이의 신뢰성이 높으며, 공유 메모리, 원격 직접 메모리 접근 방식을 사용하나 클라우드 서비스의 노드는 상용 장비를 사용하므로 낮은 비용으로 동일한 성능을 제공하나 실패율이 높습니다.
- 거대한 데이터센터의 네트워크는 흔히 IP와 이더넷을 기반으로 하며 높은 양단 대역폭을 제공하기 위해 클로스 토폴로지로 연결되어 있습니다.
- 시스템이 커질수록 구성 요소 중 하나가 고장날 가능성도 높아집니다.
- 시스템이 장애가 난 노드를 감내할 수 있고 전체적으로는 계속 동작할 수 있다면 이는 운영과 유지보수에 매우 유용한 특성이 됩니다.
- 지리적으로 분산된 배포를 할 때 통신은 대부분 인터넷을 거치나 로컬 네트워크에 비해 느리고 신뢰성이 떨어집니다.
- 분산 시스템이 동작하게 만드려면 부분 장애 가능성을 받아들이고 소프트웨어에 내결함성 메커니즘을 넣어야합니다. 즉, 신뢰성 없는 구성 요소를 사용해 신뢰성 있는 시스템을 구축해야 합니다.
신뢰성 없는 구성 요소를 사용해 신뢰성 있는 시스템 구축하기
- 대표적인 예시로 다음과 같습니다.
- 오류 수정 코드(error-correcting code)는 무선 네트워크에서 발생하는 전파 장애 등의 이유로 가끔 일부 비트가 잘못되는 통신 채널을 통해 디지털 데이터를 정확히 전송할 수 있게 해줍니다.
- IP는 신뢰성이 없습니다. IP를 사용할 때 패킷은 누락, 지연, 중복, 순서변경 등이 발생할 수 있으므로 TCP(Transmission Control Protocol, 전송 제어 프로토콜) 를 통해 신뢰성이 높은 전송 계층을 제거합니다.
- 시스템은 기반이 되는 부분보다 높은 신뢰성을 가질 수는 있으나 신뢰성을 얼마나 더 높일 수 있는지는 항상 제한이 있습니다.
- 좀 더 신뢰성 있는 상위 수준 시스템은 완벽하지 않지만 유용합니다. 까다운 저수준 결함 중 일부를 처리해주므로 남은 결함은 보통 따져보고 처리하기가 더 쉽기 때문입니다.
#
신뢰성 없는 네트워크- 이 책에서 주로 다루는 분산 시스템은 비공유 시스템, 즉 네트워크로 연결된 다수의 장비입니다.
- 네트워크는 이 장비들이 통신하는 유일한 수단입니다. 각 장비는 자신만의 메모리와 디스크를 갖고 있으며 다른 장비의 메모리나 디스크에 접근할 수 없다고 가정합니다.
- 비공유가 시스템을 구축하는 유일한 방법은 아니지만 상대적으로 저렴하고, 상품화된 클라우드 서비스를 활용할 수 있으며, 지리적으로 분산된 여러 데이터센터에 중복 배치함으로써 높은 신뢰성을 확보할 수 있기에 인터넷 서비스를 구축하는 주된 방법이 되었습니다.
- 인터넷과 데이터센터 내부 네트워크 대부분은 비동기 패킷 네트워크(synchronous packet network) 입니다.
- 이런 종류의 네트워크에서 노드는 다른 노드로 메시지를 보낼 수 있지만 네트워크는 메시지가 언제 도착할지에 대한 보장은 없습니다.
(a) 요청 손실, (b) 원격 노드 다운, (c) 응답 손실
- 전송 측은 패킷이 전송되었는지 여부조차 알수 없으며 이러한 이유를 아는 것은 대부분 불가능합니다.
- 이러한 문제를 흔하게 다루는 방법은 타임아웃입니다.
- 얼마 간의 시간이 지나면 응답 대기를 멈추고 응답이 도착하지 않는다고 가정합니다.
#
현실의 네트워크 결함- 어떤 네트워크 든 결함이 발생할 수 있고, 결함이 일어날 수 있다는 사실은 소프트웨어가 이를 처리할 수 있어야 한다는 뜻입니다.
- 반드시 네트워크 결함을 견뎌내도록(tolerating) 처리할 필요는 없으며, 네트워크가 평상시에 상당히 믿을만 하다면 문제가 있을 때 사용자에게 오류 메시지를 보요주는 것도 타당한 방법입니다.
- 그러나 소프트웨어가 네트워크 문제에 어떻게 반응하는지 알고, 시스템이 그로부터 복구할 수 있도록 보장해야 합니다.
- 고의로 네트워크 문제를 유발하고 시스템의 반응을 테스트하는 것도 타당한 방법 중 하나입니다.
#
결함 감지- 많은 시스템은 결합 있는 노드를 자동으로 감지할 수 있어야 합니다.
- 로드 밸런서는 죽은 노드로 요청을 그만 보내야하고, 단일 리더 복제시 리더가 죽으면 팔로워 중 하나가 리더가 되어야 합니다.
- 불행하게 네트워크에 관한 불확실성 때문에 노드가 동작 중인지 아닌지 구별하기 어렵습니다. 특정한 환경에서는 뭔가 동작하지 않는다고 피드백을 받을 수 있습니다.
- 원격 노드가 다운되고 있다는 빠른 피드백은 유용하지만 여기에 의존할 수는 없습니다. (즉, 긍정 응답이 필요합니다.)
- 역으로 뭔가 잘못되면 스택의 어떤 수준에서 오류 응답을 받을지도 모르지만 일반적으로 아무 응답도 받지 못할 것이라고 가정해야합니다.
- 즉, 타임아웃이 만료되기를 기다렸다가 타임아웃 내에 응답을 받지 못하면 노드가 죽었다고 선언할 수 있습니다.
#
타임아웃과 기약 없는 지연- 타임아웃에 대한 길이에 대한 명확한 답은 없습니다.
- 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어집니다.
- 타임아웃이 짧으면 결함을 빨리 발견하지만 노드가 일시적으로 느려짐에 불구하고 죽었다고 선언할 수도 있습니다. (이는 중복 실행의 원인이 되거나 과부하를 악화시키는 원인이 될 수도 있습니다.)
- 대부분의 시스템은 이중 어떤 것도 보장하지 않습니다.
- 비동기 네트워크는 기약 없는 지연(unbounded delay) 이 있습니다.
- 서버 구현은 대부분 어떤 최대 시간 내에 요청을 처리한다고 보장할 수 없습니다.
#
네트워크 혼잡과 큐 대기컴퓨터 네트워크에서 패킷 지연의 변동성은 큐 대기 때문인 경우가 많습니다.
- 네트워크 혼잡(network congestion, 네트워크 링크가 붐비면 패킷은 슬롯을 얻을 때까지 기다림)이 발생할 수 있습니다.
- 패킷이 목적지장비에 도착했을 때 CPU 코어가 바쁜 상태라면 네트워크에서 들어온 요청은 애플리케이션 처리할 준비가 될 때까지 운영체제가 큐에 넣어 둡니다.
- 가상 환경에서 실행되는 운영체제는 다른 가상 장비가 CPU 코어를 사용하는 동안 멈추는 경우가 많습니다.
- TCP는 혼잡회피(congestion avoidance) 와 같은 흐름 제어(flow control) 를 수행합니다.
- 흐름 제어는 노드가 네트워크 링크나 수신 노드에 과부하를 가하지 않도록 자신의 송신율을 제한하는 것입니다.
- TCP는 타임아웃 안에 확인 응답을 받지 않으면 패킷이 손실됐다고 간주하고 손실된 패킷은 자동으로 재전송합니다.
- 모든 요인이 네트워크 지연의 변동성에 영향을 주며 큐 대기 지연은 시스템이 최대 용량에 가까울 때 광범위하게 일어납니다.
- 공개 클라우드와 데이터센터에서는 자원을 많이 사용하는 시스템이 주변에 있으면 네트워크 지연이 발생할 수 있습니다.
- 위의 경우에는 실험적으로 타임아웃을 선택하는 방법 밖에 없습니다.
좋은 방법은 고정된 타임아웃을 설정하는 대신 시스템이 지속적으로 응답 시간과 그들의 변동성을 측정하고 관찰한 응답 시간 분포에 따라 타임아웃을 자동으로 조절하는 것입니다. (ex. 파이 증가 장애 감지기)
TCP 대 UDP
- 화상 회의나 인터넷 전화처럼 지연 시간에 민감한 애플리케이션은 TCP 대신 UDP를 사용합니다.
- 신뢰성과 지연 변동성 사이에 트레이드오프 관계가 있습니다.
- UDP는 흐름 제어를 하지 않고 손실된 패킷을 재전송하지 않으므로 네트워크 지연이 크게 변하게 하는 원인 중 일부를 제거합니다.
- UDP는 지연된 데이터의 가치가 없는 상황에서 선택하면 좋습니다.
#
동기 네트워크 대 비동기 네트워크- 패킷 전송 지연 시간의 최대치가 고정되어 있고 패킷을 유실하지 않는 네트워크에 기댈 수 있다면 분산 시스템은 훨씬 더 단순해집니다.
- 전화 네트워크의 경우 회선(circuit) 을 통해서 고정되고 보장된 양의 대역폭이 할당됩니다.
- 이러한 종류의 네트워크를 동기식이라고 합니다
- 네트워크 종단 지연 시간의 최대치가 고정되어 있으며, 이를 제한 있는 지연(bounded delay) 라고 합니다.
#
그냥 네트워크 지연을 예측 가능하게 만들 수는 없을까?전화 네트워크 회선은 TCP 연결과 매우 다릅니다.
전화 회신 | TCP 연결 | |
---|---|---|
대역폭 | 다른 누구도 사용할 수 없는 고정된 양의 예약된 대역폭 | 대역폭을 기회적으로 쓰며, 가능하면 짧은 시간 안에 전송 희망 |
왕복 시간 | 왕복 시간의 최대치를 보장 가능 | 이더넷과 IP는 큐 대기의 영향을 받으므로 기약 없는 지연 |
목적 | 비트 개수가 고정된 서비스 | 순간적으로 몰리는 트랙픽에 최적화 |
예시 | 비트가 고정된 음성, 영상 | 웹 페이지 요청, 이메일 전송, 파일 전송 |
위의 두개를 합치려는 시도도 종종 있습니다. 서비스 품질(quality of service, QoS, 패킷에 우선순위를 매기고 스케줄링함)과 진입 제어(admission control, 전송 측에서 전송률을 제한)을 잘 쓰면 패킷 네트워크에서 회선 교환을 흉내낼 수도 있습니다.
다만 이러한 서비스는 사용할 수 없으므로, 네트워크 지연과 신뢰성에 대해 어떤 보장이 없다는 것을 알고 결정해야 합니다.
지연 시간과 자원 사용률
- 일반적으로 인터넷은 대역폭을 동적으로 공유합니다.
- 자원이 정적으로 분할된다면 어떤 환경에서는 지연 시간을 보장할 수 있으며 사용률이 줄어드는 비용이 따릅니다.
- 동적 자원 분할을 하는 멀티 테넌트 방식을 쓰면 사용률을 높여서 비용은 줄지만 지연의 변동이 큰 단점이 있습니다.
#
신뢰성 없는 시계애플리케이션은 아래의 질문에 대답하기 위해 시계에 의존합니다.
- 요청이 타임아웃 되었는지, 서비스의 응답 시간은 어떻게 되는지, 서비스가 5분 동안 평균 초당 몇개의 질의를 처리하는지, 사용자가 우리 사이트에 얼마나 보냈는지
- 이 기사가 언제 게시된지, 며칠 몇 시에 미리 알림 이메일을 보내야하는지, 캐시는 언제 만료되는지, 로그 오류 메시지의 타임스탬프는 무엇인지
여기서 위의 예시는 지속 시간을 측정하는 것이고 아래의 예시는 시점을 기술합니다.
분산 시스넴에서는 통신이 즉각적이지 않으므로 시간을 다루기가 까다롭습니다.
- 네트워크를 거쳐 다른 장비로 이동하기 때문에 시간이 걸립니다.
- 네트워크의 지연의 변동성 때문에 얼마나 나중일지 확인도 어려우며, 이는 일의 발생 순서를 어렵게 만듭니다.
네트워크에 있는 개별 장비는 자신의 시계를 가지고 있습니다.
- 일반적으로 하드웨어 장치로 구동되며, 완벽히 정확하지 않아 각 장비는 자신만의 시간 개념이 있습니다.
- 가장 널리 쓰는 메커니즘은 네트워크 시간 프로토콜(Network Time Protocol, NTP) 이며 서버 그룹에서 보고한 시간에 따라 컴퓨터 시계를 조정할 수 있게 합니다.
#
단조 시계 대 일 기준 시계현대 컴퓨터는 최소 두가지 종류의 시계(일 기준 시계와 단조 시계) 로 이루어져 있습니다. 이는 다른 목적으로 사용됩니다.
#
일 기준 시계- 직관적으로 시계에 기대하는 일 (ex. 벽시계 시간)
- 리눅스의
clock_gettime(CLOCK_REALTIME)
, 자바의System.currentTimeMillis()
- 리눅스의
- UTC 1970년 1월 1일 자정 이후에 흐른 초(또는 밀리초) 수를 반환합니다.
- 일 기준 시계는 보통 NTP로 동기화합니다.
- 이상적으로는 한 장비의 타임스탬프는 다른 장비의 스탬프와 동일한 의미를 지닌다는 의미입니다.
- 다만, 로컬 시계가 NTP 서버보다 너무 앞서면 강제로 리셋되어 과거 시점으로 거꾸로 뛰는 것처럼 보입니다.
- 이러한 뜀은 윤초를 종종 무시한다는 사실과 더불어 일 기준 시간이 경과 시간을 측정하는 데 적합하지 않게 만듭니다.
#
단조 시계- 타임아웃이나 서비스 응답 시간 같은 지속 시간(시간 구간)을 재는 데 적합합니다.
- 리눅스의
clock_gettime(CLOCK_MONOTONIC)
, 자바의System.nanoTime()
- 리눅스의
- 한 시점에서 단조 시계의 값을 확인하고 어떤 일을 한 후 나중에 다시 시계를 확인해서 이 차이로 시간이 얼마나 흘렀는지 확인할 수 있습니다.
- 시계의 절대적인 값은 의미가 없습니다. 또한 다른 컴퓨터의 단조 시계 값을 비교하는 것도 의미가 없습니다.
- NTP는 컴퓨터의 로컬 시계가 NTP 서버보다 빠르거나 느리면 단조 시계가 진행하는 진도수를 조절할 수 있습니다.
- 이를 시계를 돌린다(slewing)이라고 합니다. 기본적으로 NTP는 시계 속도를 0.05%까지 조절이 가능하지만 앞으로 가거나 뒤로 뛰게 할 수는 없습니다.
- 대부분의 시스템에서 시간 구간을 마이크로초나 그 이하 단위로 측정할 수 있습니다.
- 분산 시스템에서 경과 시간을 재는 데 단조 시간을 쓰는 것은 일반적으로 괜찮습니다.
#
시계 동기화와 정확도- 단조 시계는 동기화가 필요 없지만 일 기준 시계는 NTP 서버나 다른 외부 시간 출처에 맞춰 설정돼야 유용합니다. 그러나 이는 신뢰성이 있거나 정확하지는 않습니다.
- 컴퓨터 수정 시계는 아주 정확하지는 않습니다. 드리프트(drift) 현상이 발생할 수 있습니다. (더 빠르거나 느리게 실행)
- 컴퓨터 시계가 NTP 서버와 너무 많은 차이가 나면 동기화가 거부되거나, 로컬 시계가 강제 리셋이 가능합니다.
- 뜻하지 않게 노드와 NTP 서버 사이가 방화벽으로 막히면 잘못된 설정이 얼마 동안 알려지지도 않을 수도 있습니다.
- NTP 동기화는 패킷 지연의 변화가 큰 혼잡한 네트워크에서는 정확도에 한계가 있습니다.
- 윤초가 발생하면 이를 고려하지 않고 설계된 시스템에서는 시간에 관한 가정이 엉망이 됩니다.
- 가상 장비에서 하드웨어 시계는 가상화돼서 정확한 시간 엄수가 필요한 애플리케이션에서 추가적인 어려움이 생깁니다.
- 완전히 제어할 수 없는 장치에서 소프트웨어를 실행하면 아마도 그 장치의 하드웨어를 전혀 믿을 수 없습니다.
- 시계 정확도가 중요한 경우에는 시계 정확도를 높이는 것도 가능합니다.
- 고빈도 트레이딩 펀드의 경우, 시계를 UTC와 100마이크로초 이내로 동기화하기를 요구합니다.
- 이러한 정확도는 GPS 수신기, 정밀 시간 프로토콜(Precision Time Protocol, PTP)과 세심한 배포 및 모니터링을 사용해서 달성할 수 있습니다.
- 이는 상당한 노력과 전문 기술이 필요하며 시계 동기화가 잘못될 수 있는 수많은 방법이 있습니다.
#
동기화된 시계에 의존하기- 시계는 간단하고 사용하기 쉬워 보이지만 여러 함정이 있습니다.
- 하루가 86400초가 아닐 수 있으며, 일 기준 시계가 시간이 거꾸로 가거나, 노드의 시간이 다른 노드와 시간 차이가 많이 날 수 있습니다.
- 네트워크가 대부분의 시간에 잘 동작하더라도 네트워크에 가끔 결함이 생길 수 있다는 가정하에 설계되는 것처럼 시계도 이와 같이 설계해야합니다.
- 한가지 문제 중 하나는 시계가 잘못된다는 것을 눈치채기가 어렵습니다.
- 눈에 보이는 문제보다 조용하고 미묘한 데이터 손실이 발생할 가능성이 높습니다.
- 동기화된 시계가 필요한 소프트웨어을 사용한다면 필수적으로 모든 장비 사이의 시계 차이를 조심스럽게 모니터링해야 합니다.
- 다른 노드와 시계가 너무 차이나는 노드는 죽은 것으로 선언되고 클러스터에서 제거되어야 합니다.
- 이런 모니터링을 통해 큰 피해를 입기 전에 고장 난 시계를 알아채도록 보장할 수 있습니다.
#
이벤트 순서화용 타임스탬프- 아래처럼 특정 상황의 문제가 발생할 수 있습니다.
- 클라이언트 B는 클라이언트 A보다 인과성 측면에서 나중에 쓰지만 B가 쓸 때 사용하는 타임스탬프가 더 이릅니다.
- 위의 예시에서 x=1 이 더 나중의 결과로 시간에서 확인되므로 클라이언트 B의 증가 연산은 손실됩니다.
- 이러한 충돌 해소 전략을 최종 쓰기 승리(LWW, last write wins) 라고 불리며 리더 없는 데이터베이스에서 주로 사용됩니다.
- 아래의 문제가 발생할 수 있스빈다.
- 데이터베이스 쓰기가 불가사의하게 사라질 수 있습니다.
- LWW는 순차적인 쓰기가 빠른 시간 내에 연속으로 실행되는 것과 동시에 쓰기가 실행되는 것을 구별할 수가 없습니다.
- 두 노드가 독립적으로 동일한 타임스탬프를 가진 쓰기 작업을 만들 수도 있습니다.
- 가장 "최근" 값을 유지하고 다른 것들을 버림으로써 충돌을 해소하고 싶더라도 "최근"의 정의는 로컬 일 기준 시계에 의존하며 그 시계는 틀릴 수 있는 것을 알아야합니다.
- 잘못된 순서화가 발생하지 않을 정도로 NTP 동기화를 정확하게 하는 것은 거의 불가능합니다.
- 올바르게 순서화를 하기위해서는 시계 출처가 대상보다 더 정확해야 하므로 논리적 시계(logical clock)을 사용하는 것이 방법입니다.
- 논리적 시계는 이벤트의 상대적인 순서만 측정합니다.
- 이와 반대로 물리적 시계(physical clock)은 실제 경과 시간을 측정합니다. (ex. 일 기준, 단조 시계)
#
시계 읽기는 신뢰 구간이 있다- 아무리 로컬 네트워크에 있는 NTP 부정확한 수정 시계에서 발생하는 드리프트는 쉽게 몇 밀리초가 될 수 있으며, 공개 인터넷의 NTP 서버를 사용해도 달성 가능한 최선의 정확도는 수십 밀리초에서 네트워크 혼잡에 따라 100밀리초 이상 될 수 있습니다.
- 따라서 시계 읽기는 신뢰 구간에 속하는 시간의 범위로 읽는 것이 타당합니다.
- 불확실성 경계는 시간 출처를 기반으로 계산할 수 있습니다. (GPS 수신기 등)
- 대부분의 시스템은 이러한 불확실성을 노출하지 않습니다. (ex.
clock_gettime()
) - 스패너(Spanner)에 있는 구글 트루타입(TrueTime) API의 경우, 로컬 시계의 신뢰 구간을 명시적으로 보고합니다.
- 두개의 값 [earliest, latest] 인 (가장 이른 것, 가장 늦은 것)을 받습니다.
- 시계는 불확실성 계산을 기반으로 실제 현재 시간이 그 구간 안에 어딘가에 있는 것을 알 수 있습니다.
#
전역 스냅숏용 동기화된 시계- 지난 내용에서는 다음과 같습니다.
- 스냅숏 격리는 작고 빠른 읽기 트랜잭션과 크고 오래 실행되는 읽기 전용 트랜잭션 모두를 지원해야하는 데이터베이스에서의 중요한 기능입니다.
- 가장 흔한 스냅숏 격리 구현은 단조 증가하는 트랜잭션 ID가 필요합니다.
- 데이터베이스가 여러 데이터센터에 있는 여러 장비에 분산되어 있을 때는 코디네이션이 필요하므로 트랜잭션 ID를 생성하기 어렵습니다.
- 트랜잭션 ID는 인과성을 반영해야합니다.
- 작고 빠른 트랜잭션이 많을 수록 분산 시스템에서 트랜잭션 ID 생성은 방어할 수 없는 병목이 됩니다.
- 동기화된 일 기준의 시계의 타임스탬프를 트랜잭션 ID로 쓸 수 없는 것이 이또한 불확실성이 있습니다.
- 스패너는 이런 방법으로 데이터센터에 걸쳐서 스냅숏 격리를 구현합니다.
- 트랜잭션 타임스탬프가 인과성을 반영하는 것을 보장하기 위해 스패너는 읽기 쓰기 트랜잭션을 커밋하기 전에 의도적으로 신뢰 구간의 길이만큼 기다립니다.
- 구글은 각 데이터센터에 GPS 수신기나 원자 시계를 배치해 시계가 약 7밀리초 이내로 동기화되게 합니다.
#
프로세스 중단파티션마다 리더가 있는 데이터베이스 구조에서, 노드가 여전히 리드인지 그리고 안전하게 쓰기를 확인할 방법이 필요합니다.
- 이에 대한 첫번째 선택은 리더가 다른 노드들로부터 임차권(lease) 을 얻는 것입니다.
- 일종의 타임아웃이 있는 잠금과 비슷합니다. (특정 시점에 오직 하나의 리더만 임차권이 있습니다)
- 어떤 노드가 임차권을 획득하면 임차권이 만료될 때까지 자신이 리더일 것임을 알 수 있고, 장애가 발생한 경우 임차권 갱신이 멈추므로 다른 노드가 리더 역할을 받을 수 있습니다.
이를 코드로 표현하면 아래와 같습니다.
위 코드는 두 가지 문제를 들고 있습니다.
- 첫번째로 동기화된 시계에 의존합니다.
- 로컬 단조 시계만 사용해도, 아래 갱신 시간동안은 장애가 발생했는지에 대해 모릅니다.
lease.isValid
직전에 임차권이 만료된 경우, 임차권이 만료되었는지에 대해 전혀 모르고 작동하기에 안전하지 않은 일을 할 수 있습니다.
스레드가 멈추는 일은 다양하게 발생할 수 있습니다.
- GC(가비지 컬렉터)가 발생해서 실행중인 모든 스레드를 멈춰야할 수도 있습니다.
- 가상 환경의 경우, 가상 장비는 서스펜드(suspend, 모든 프로세스 실행을 멈추고 메모리 내용을 디스크에 저장)했다가 재개될 수 있습니다. 이러한 중단은 프로세스 실행 중 언제든지 발생할 수 있습니다.
- 노트북 같은 최종 사용자의 기기에서도 실행이 제멋대로 서스펜드됐다 재개될 수 있습니다.
- 운영체제가 다른 스레드로 컨텍스트 스위치하거나, 하이퍼바이저가 다른 가상 장비로 스위치되면 현재 실행 중인 스레드는 코드의 임의 지점에서 멈출 수 있습니다.
- 애플리케이션에서 동기식으로 디스크에 접근하면 느린 디스크 I/O 연산이 완료되기를 기다리느라 중단될 수 있습니다.
- 운영체제가 디스크로 스왑(페이징) 할 수 있게 설정되었다면 단순한 메모리 접근만 해도 페이지를 디스크에서 메모리로 로딩하게 하는 페이지 폴트가 발생할 수 있습니다.
- 유닉스 프로세스의
SIGSTOP
신호를 통해 멈출수도 있습니다.
위의 경우가 발생하면 실행 중인 스레드를 어떤 시점에서 선점(preempt) 하고 얼마간의 시간이 흐른 후 재개할 수 있습니다.
- 선점된 스레드는 이를 알지못하고, 이 문제는 단일 장비에서 다중 스레드 코드를 스레드 안전(thread-safe)하게 하는 것과 비슷합니다.
- 컨텍스트 스위치가 임의로 발생할 수도 있고 병렬성이 발생할 수 있으므로 타이밍에 대한 어떤 가정도 할 수 없습니다.
- 단일 장비에서는 다중 스레드를 처리하는 뮤텍스(mutext), 세마포어(semaphore), 원자적 카운터(atomic counter) 등등이 있으나 이런 도구는 분신 시스템에서 바로 적용할 수는 없습니다. (공유 메모리와 신뢰성이 없는 네트워크만 사용 가능하므로)
- 분산 시스템의 노드는 어느 시점에서 실행이 상당히 멈출 수 있다는 것을 가정해야하며, 심지어 함수 중간에서 멈출수도 있다고 생각해야 합니다.
#
응답 시간 보장- 이처럼 많은 프로그래밍 언어와 운영체제에서 스레드와 프로세스는 기약 없는 시간동안 중단가능합니다. 다만 노력을 통해 중단 원인을 제거할 수 있습니다.
- 실패하면 안되는 환경에서는 소프트웨어 응답 데드라인(deadline) 을 명시해서 처리하며 이는 엄격한 실시간 시스템(hard real-time) 이라고 합니다.
- 시스템에서 실시간 보장을 제공하려면 소프트웨어 스택의 모든 수준에서 지원이 필요합니다.
- 프로세스가 명시된 간격의 CPU 시간을 할당받을 수 있게 보장되도록 스케줄링해 주는 실시간 운영체제(real-time operating system, RTOS) 가 필요합니다.
- 라이브러리 함수는 최악의 실행 시간을 문서화하고, 동적 메모리 할당은 제한되거나 금지될 수 있습니다.
- 보장을 만족시킨다는 것을 확신할 수 있도록 막대한 양의 테스트와 측정을 해야합니다.
- 이러한 모든 것들은 많은 양의 부가 작업이 필요하고 사용할 수 있는 프로그래밍 언어, 라이브러리, 도구의 범위를 엄격히 제한합니다.
- 위의 이유로 실시간 시스템 개발은 매우 많은 비용이 들며 안전이 필수적인 임베디드 장치에서 가장 흔하게 사용됩니다.
- "실시간"은 "고성능"과 동일하지 않습니다.
- 대부분의 서버측 데이터 처리 시스템에게 실시간 보장은 경제적이지도, 적절하지도 않습니다.
#
가비지 컬렉션의 영향을 제한하기- 프로세스 중단의 부정적 영향은 비용이 큰 실시간 스케줄링 보장에 기대지 않고도 완화시킬 수 있습니다.
- 언어 런타임은 객체 할당률과 시간에 따라 남아 있는 여유 메모리 공간을 추적할 수 있으므로 언제 가비지 컬렉션을 할지와 관련된 어느정도의 유연성을 가지고 있습니다.
- 최근의 아이디어 중 하나는 GC 중단을 노드가 잠시 계획적으로 중단되는 것으로 간주하고 노드가 가비지 거컬렉션을 하는 동안 클라이언트로부터의 요청을 다른 노드들이 처리하게 하는 것입니다.
- 혹은 노드가 처리되지 않은 요청을 완료 후에 아무 요청을 처리하지 않는 동안 GC를 실행하기 기다릴 수도 있습니다.
- 또 다른 아이디어는 수명이 짧은 객체만 가비지 컬렉터를 사용하고, 수명이 긴 객체의 전체 GC가 필요할 만큼 객체가 쌓이기 전에 주기적으로 프로세스를 재시작하는 방법입니다.
- 가비지 컬렉터 중단을 완전히 막을 수는 없지만 애플리케이션에 미치는 영향은 유용하게 줄일 수 있습니다.
#
지식, 진실, 그리고 거짓말- 앞서 내용은 분산 시스템이 단일 컴퓨터에서 프로그램을 실행하는 것과 어떻게 다른지에 대해 이야기했습니다.
- 분산 시스템에는 공유 메모리가 없고 지연 변동이 큰 신뢰할 수 없는 네트워크를 통해 메시지를 보낼 수 있을 뿐이며 부분 장애, 신뢰성 없는 시계, 프로세스 중단에 시달릴 수 있습니다.
- 분산 시스템에서 우리는 동작(시스템 모델)에 관해 정한 가정을 명시하고, 이런 가정을 만족시키는 방식으로 실제 시스템을 설계할 수 있습니다.
- 신뢰성 없는 시스템 모델에서 잘 동작하는 소프트웨어를 만드는 것이 가능할지라도 몇가지 이슈가 있습니다.
#
진실은 다수결로 결정됩니다.네트워크는 여러의 경우에 따라 노드가 죽었다고 판단합니다.
- 비대칭적 결함이 있는 네트워크는 노드가 자신에게 보내는 메시지를 모두 받을 수 있지만, 그 노드에서 밖으로 나가는 메시지는 유실되거나 지연됩니다. 이 경우 다른 노드들이 이 노드를 죽었다고 판단됩니다.
- 한쪽 연결이 끊긴 노드는 자신이 보낸 메시지가 다른 노드로부터 확인 응답 못하는 것을 알아내 네트워크에 결함이 있다고 알았을 때, 그 네트워크는 어떤 일도 할 수 없습니다.
- 가비지 컬렉션과 같은 중단을 진행중인 노드를 다른 노드에서는 죽었다고 판단합니다.
즉, 노드가 상황에 대한 자신의 판단을 반드시 믿을 수 있는 것은 아니란 것입니다. 여러 분산 알고리즘은 정족수(quorum), 즉 노드들 사이의 투표에 의존합니다.
#
리더와 잠금- 시스템이 오직 하나의 뭔가 필요할 때가 자주 있습니다.
- 스플릿 브렌인을 피하기 위해 오직 한 노드만 데이터베이스 파티션의 리더가 되는 경우
- 특정한 자원이나 객체에 동시에 쓰거나 오염시키는 것을 방지하기 위해 오직 하나의 트랜잭션이나 클라이언트만 어떤 자원이나 객체의 잠금을 획득할 수 있습니다.
- 사용자명으로 사용자를 유일하게 식별할 수 있으므로 오직 한 명의 사용자만 특정한 사용자명으로 등록할 수 있습니다.
- 분산 시스템에서 어떤 노드가 스스로를 "선택된자" 라고 판단해도 노드의 정족수도 반드시 동의한다는 뜻이 아닙니다.
- 노드의 과반수가 어떤 노드가 죽었다고 선언했어도 그 노드가 선택된 자인 것처럼 계속 행동한다면 어떤 시스템에서는 문제가 발생할 수 있습니다.
- 위는 잠금을 잘못 구현한 예시입니다.
#
펜싱 토큰- 잘못된 노드의 "선택된 자"를 해결하기 위한 단순한 기법은 펜싱(fencing)이 있습니다.
- 잠금 서버가 잠금이나 임차권을 승인할 때마다 펜싱 토큰(fencing token)을 반환한다고 가정합니다.
- 클라이언트가 쓰기 요청을 저장소 서비스로 보낼 때마다 자신의 현재 펜싱 토큰을 포함하도록 요구합니다.
- 잠금 서비스로 주키퍼를 사용하면 트랜잭션 ID나 노드 버전을 펜싱 토큰으로 사용할 수 있습니다.
- 서버 측에서 토큰을 확인하는 것은 대부분 좋습니다.
Q. 이거를 운영단에서는 데이터베이스 제공 업체에서 고민하는 문제인지, DBA의 고민 문제인지 궁금
#
비잔틴 결함- 펜싱 토큰은 부주의에 의한 오류에 빠진 노드를 감지하고 차단할 수 있습니다.
- 그러나 노드가 고의로 시스템의 보장을 무너뜨리려한다면 가짜 펜싱 토큰을 포함한 메시지를 보내면 됩니다.
- 책에서는 노드들이 신뢰성은 없지만 정직하다고 가정합니다.
- 노드들은 느리거나 응답을 하지 않거나, 지연할 수 있지만 응답한다면 노드는 진실을 말한다고 가정합니다.
- 분산 시스템 문제는 노드가 "거짓말"을 할지도 모른다는 위험이 있다면 훨씬 더 어려워집니다.
- 어떤 노드가 실제로는 받지 않은 특정 메시지를 받았다고 주장할 수 있으며 이런 동작을 비잔틴 결함(Byzantine fault) 라고 합니다.
- 신뢰할 수 없는 환경에서 합의에 도달하는 문제를 비잔틴 장군 문제(Byzantine Generals Problem) 라고 합니다.
- 일부 노드가 오작동하고 프로토콜을 준수하지 않거나 악의적 공격자가 네트워크를 방해하더라도 시스템이 계속 올바르게 동작한다면 이 시스템은 비잔틴 내결합성을 지닙니다.(Byzantine fault-tolerant).
- 항공우주 산업 환경에서 저장된 데이터가 방사선에 오염되서 전혀 에측할 수 없는 방식으로 반응
- 여러 조직이 참여하는 시스템에서는 어떤 참여자들은 속이거나 사취할지도 모르므로 믿는 것 자체가 안전하지 않습니다.
- 책에서 살펴보는 시스템의 종류는 보통 비잔틴 결함이 없다고 가정할 수 있습니다.
- 데이터센터에서 조직이 모든 제어하고, 방사선 수준의 메모리 오염은 큰문제가 되지 않습니다.
- 시스템이 비잔틴 내결함성을 지니도록 만드는 프로토콜은 매우 복잡하고 내결함성을 지닌 임베디드 시스템은 하드웨어 수준의 지원에 의존합니다.
- 대부분의 서버 측 데이터 시스템에서 비잔틴 내결함성 솔루션을 배치하는 것은 비용이 커서 실용적이지 않습니다.
- 웹 애플리케이션은 최종 사용자가 제어하는 웹브라우저 같은 클라이언트의 행동이 임의적이고 악의적이라고 예상해야 합니다.
- 웹 애플리케이션은 input validation(입력 확인), sanitization(위험할 수 있는 입력 값을 유효한 값으로 변경하는 처리), output escaping(출력 이스케이핑)이 매우 중요합니다.
- ex) SQL Injection, cross site scripting
- 일반적으로 비잔틴 내결함성 프로토콜은 여기에 쓰지는 않고 클라이언트의 행동이 허용된 것인지 아닌지 결정하는 권한을 서버에게 줄 뿐입니다.
- 비잔틴 내결함성은 이런 중앙 권한이 없는 피어투피터 네트워크에 더 적절합니다.
- 웹 애플리케이션은 input validation(입력 확인), sanitization(위험할 수 있는 입력 값을 유효한 값으로 변경하는 처리), output escaping(출력 이스케이핑)이 매우 중요합니다.
- 소프트웨어의 버그를 비잔틴 결함으로 간주할 수도 있지만 동일한 소프트웨어를 모든 노드에 배포하면 비잔틴 내결함성 알고리즘도 도움이 되지 않습니다.
- 대두분의 비잔틴 내결함성 알고리즘은 노드의 2/3 이상의 암도적 다수가 올바르게 동작하기를 요구합니다.
- 버그를 막는데 이 방법을 쓰려면 동일한 소프트웨어를 독립적으로 구현한 것이 네 개가 있어야 합니다.
- 프로토콜이 취약점, 보안 침해, 악성 공격에서 우리를 보호할 수 있다면 유용할 것이나 현실적이지 않습니다.
- 대부분의 시스템에서 노드 하나를 침해할 수 있다면 모든 노드를 침해할 수 있습니다. (동일한 소프트웨어를 사용함으로)
- 전통적인 메커니즘(인증, 접근 제어, 암호화, 방화벽 등)이 여전히 공격자로부터 보호하는 수요 수단으로 사용됩니다.
비잔틴 장군 문제
- 비잔틴 장군 문제는 두 장군 문제(Two Generals Problems) 를 일반화한 것입니다.
- 비잔틴 버전 문제에서는 n명의 장군이 동의해야 하며 노력은 그들 가운데 배신자들이 있다는 사실에 방해를 받습니다.
- 장군들은 대부분 충성스럽기에 진실된 메시지를 보내지만, 배신자들은 가짜나 허위 메시지를 보내 다른 장군들을 속이거나 혼란스럽게 합니다.
- 비잔티움은 심하게 복잡하고 관료주의적이며 정직하지 못하다는 뜻에서 정치에서 사용됩니다.
#
약한 형태의 거짓말- 노드들이 일반적으로 정직하다고 가정하지만 약한 형태의 거짓말(하드웨어 문제, 소프트웨어 버그, 잘못된 설정 때문에 유효하지 않은 메시지)로부터 보호해주는 메커니즘을 소프트웨어에 추가하는 게 가치가 있을 수도 있습니다.
- 네트워크 패킷은 때때로 오염되며, 오염된 패킷은 TCP와 UDP에 내장된 체크섬으로 검출되지만 때로는 검출을 피하는 경우도 있습니다. 이러한 오염으로부터 보호하려면 보통 애플리케이션 수준 프로토콜에서 체크섬을 쓰는 것처럼 단순한 수단을 쓰면 충분합니다.
- 공개적으로 접근 가능한 애플리케이션은 사용자 입력을 신중하게 살균해야 합니다. (값이 합당한 범위인지, 메모리를 대량으로 할당하지 않도록 문자열 크기 제한 등등)
- NTP 클라이언트는 여러 서버 주소를 설정할 수 있습니다. (대다수의 서버들이 정상적이고 잘못 설정된 NTP 서버를 이상치로 검출해서 동기화 대상에서 제거할 수 있습니다.)
#
시스템 모델과 현실분산 시스템 문제를 해결하기 위해 많은 알고리즘이 설계되고 있습니다. 또한 이러한 알고리즘은 하드웨어나 소프트웨어 설정의 세부 사항에 의존하지 않는 방식으로 작성합니다. 시스템에서 발생할 것으로 예상되는 결함의 종류를 어떻게든 정형화해야합니다.
시스템 모델(system model) 을 정의해서 정형화하는데, 시스템 모델은 알고리즘이 가정하는 것을 기술한 추상화입니다.
타이밍 가정에 대해서는 세 가지 시스템 모델이 흔히 사용됩니다.
- 동기식 모델
- 네트워크 지연, 프로세스 중단, 시계 오차에 모두 제한이 있다고 가정합니다.
- 대부분 현실적인 모델이 아닙니다.
- 부분 동기식 모델
- 시스템이 대부분의 시간에는 동기식 시스템처럼 동작하지만 때때로 네트워크 지연, 프로세스 중단, 시계 드리프트의 한계치를 초과한다는 뜻입니다.
- 대부분 현실적인 모델입니다.
- 어떤 타이밍 가정이 깨질수도 있음을 고려합니다.
- 비동기식 모델
- 타이밍에 대한 어떤 가정도 할 수 없습니다.
- 시계가 없을 수도 있고 어떤 알고리즘은 비동기식 모델용으로 설계할 수 있지만 매우 제한적입니다.
세 가지 노드용 시스템 모델은 다음과 같습니다.
- 죽으면 중단하는(crash-stop) 결함
- 죽으면 중단하는 모델에서 알고리즘은 노드 장애가 나면 죽는 경우만 있습니다.
- 노드가 어느 순간에 갑자기 응답을 멈추면 이후로 노드는 영원히 사용할 수 없고 결코 되돌아오지 않습니다.
- 죽으면 복구하는(crash-recovery) 결함
- 노드가 어느 순간에 죽을 수 잇지만 알려지지 않은 시간이 흐른 후에 다시 응답하기 시작할 것이라고 가정합니다.
- 죽으면 복구하는 모델에서는 노드는 메모리에 있는 상태는 손실되지만 죽어도 데이터가 남아 있는 안정된 저장소가 있다고 가정합니다.
- 비잔틴(임의적인) 결함
- 다른 노드를 속이거나 기만하는 것 들이 모든 일이 가능합니다.
현실 시스템을 모델링하는데 죽으면 복구하는 결함을 지닌 부분 동기식 모델이 일반적으로 유용한 모델입니다. 분산 알고리즘은 아래처럼 대응합니다.
#
알고리즘의 정확성알고리즘이 정확하다(correct) 는 것을 정의하기 위해서는 알고리즘의 속성(property) 을 기술할 수 있습니다.
예를 들어 잠금에 펜싱 토큰을 생성시에는 다음 속성을 지니어야 합니다.
- 유일성, 펜싱 토큰 요청이 같은 값을 반환하지 않는다.
- 단조 일련번호, 앞의 완료된 요청의 토큰은 그 후 토큰보다 앞에 있습니다.
- 가용성, 펜싱 토큰은 요청하고 죽지 않은 노드는 결국 응답을 받습니다.
알고리즘은 시스템 모델에서 발생하리라고 가정한 모든 상황에서 속성들을 항상 만족시키면 해당 시스템 모델에서 정확합니다. 모든 노드가 죽거나 모든 네트워크 지연이 무한히 길어지면 어떤 알고리즘이라도 아무것도 할 수 없습니다.
#
안전성과 활동성- 상황을 분명히 하기 위해 안전성(safety) 과 활동성(liveness) 을 구별할 필요가 있습니다.
- 위의 유일성과 단조 일련번호는 안전성 속성이지만 가용성은 활동성 속성입니다.
- 두 속성의 구별에 핵심은 결국에는(eventually) 단어를 포함하는 것입니다
- 안전성과 활동성의 실제 정의는 정확하고 수학적입니다.
- 안전성 속성이 위반되면 그 속성이 깨진 특정 시점을 가리킬 수 있습니다. 안전성 속성이 위반된 후에는 그 위반을 취소할 수 없습니다.
- 활동성 속성은 반대로 동작합니다. 어떤 시점을 정하지 못할 수 있지만 항상 미래에 그 속성을 만족시킬 수 있다는 희망이 있습니다.
- 안전성과 활동성 속성을 구별하면 어려운 시스템 모델을 다루는 데 도움이 됩니다.
- 분산 알고리즘은 시스템 모델의 모든 상황에서 안전성 속성이 항상 만족되기를 요구하는 게 일반적입니다.
- 활동성 속성에 대해서는 경고를 하는 게 허용합니다.
#
시스템 모델을 현실 세계에 대응시키기- 안전성 및 활동성 속성과 시스템 모델은 분산 시스템의 정확성을 따져보는데 매우 유용합니다.
- 정족수 알고리즘은 노드가 저장했다고 선언한 데이터를 기억하고 있다는 것에 의존합니다.
- 알고리즘은 여러 가정을 할 수 있지만, 실제 구현에서는 여러 가정이 발생할 수 있기 때문에 이를 막는 코드를 포함시켜야 할 수 있습니다.
- 추상 시스템 모델은 현실 시스템의 복잡함에서 추론할 수 있는 관리 가능한 결함의 집합을 뽑아서, 문제를 이해하고 체계적으로 해결하려고 노력할 수 있게 하는데 엄청난 도움이 됩니다.
- 알고리즘이 올바르다고 증명됐더라도 반드시 현실 시스템에서의 구현도 언제나 올바르게 동작한다는 뜻은 아닙니다.
즉, 이론적 분석과 경험적 실험은 중요합니다.
#
정리이 장에서는 분산 시스템에서 나타날 수 있는 광범위한 문제를 설명했습니다.
- 네트워크로 패킷을 보내려고 할 때는 언제나 패킷이 손실되거나 임의대로 지연될 수 있습니다. 마찬가지로 응답도 손실되거나 지연될 수 있으므로 응답을 받지 못하면 메시지가 전달됐는지 아닌지를 알 수 없습니다.
- 노드의 시계는 다른 노드와 심하게 맞지 않을 수 있고(최선을 다해 NTP를 설정하더라도) 시간이 갑자기 앞뒤로 뛸 수도 있습니다. 그리고 시계의 오차 구간을 측정할 좋은 수단이 없을 가능성이 크므로 시계에 의존하는 것은 위험합니다.
- 프로세스는 실행 도중 어느 시점에서 상당한 시간동안 멈출 수 있고 다른 노드에 의해 죽었다고 선언될 수 잇으며 되살아났을 때 멈췄다는 사실을 알지 못할 수도 있습니다.
부분 실패(partial failure) 가 생길 수 있다는 사실은 분산 시스템의 뚜렷한 특성입니다. 소프트웨어가 다른 노드와 연관된 뭔가를 하려고 할 때는 전혀 응답하지 않을 가능성이 있습니다. 분산 시스템에서 구성 요소의 일부가 고장 나더라도 전체로서의 시스템은 계속 동작할 수 있도록 부분 실패에 대한 내성을 소프트웨어에 내장하려고 노력합니다.
결함을 견뎌내려면 그것을 감지하는 게 첫걸음이지만 그것조차 어렵습니다. 대부분의 시스템은 노드에 장애가 발생했는지 알 수 있는 정확한 메커니즘이 없어서 대부분의 분산 알고리즘은 원격 노드를 아직 쓸 수 있는지 결정하기 위해 타임아웃을 사용합니다. 타임아웃은 네트워크 장애와 노드 장애를 구별할 수 없고 변동이 큰 네트워크 지연 때문에 때때로 노드가 죽은 것으로 잘못 의심받을 수 있습니다.
결함이 발견됐을 때 시스템이 이를 견딜 수 있게 만들기도 쉽지 않습니다. 장비들 사이에는 전역 변수도, 공유 메모리도, 공통된 지식이나 어떤 종류의 공유 상태도 없습니다.
엄격한 실시간 응답 보장과 네트워크 지연에 제한을 두는 것은 가능하지만 그렇게 하는 빙용이 매우 크며 하드웨어 자원 사용률이 낮아집니다. 대다수의 안전이 필수적이지 않은 시스템은 비싸고 신뢰성이 있는 것보다 저렴하고 신뢰성이 없는 것을 택합니다.
분산 시스템은 모든 결함과 유지보수를 노드 수준에서 처리할 수 있으므로 서비스 수준에서는 중단 없이 영원히 실행될 수 있습니다. (이론적) 이번 장은 모든 문제에 대한 것이고 다음 장에서는 해결책에 대해 이야기합니다.