티스토리 뷰
08. 분산 시스템의 골칫거리
결국 엔지니어로서의 우리의 임무는 모든 게 잘못되더라도 제 역할을 해내는 시스템을 구축하는 것이다. (p274)
- 네트워크 관련 문제
- 시계 및 타이밍 문제
결함과 부분 장애
분산 시스템에서는 시스템의 어떤 부분은 잘 동작하지만 다른 부분은 예측할 수 없는 방식으로 고장나는 것도 무리가 아니다. 이를 부분 장애라고 한다. (p275)
클라우드 컴퓨팅과 슈퍼컴퓨팅
클라우드 컴퓨팅 (cf. 슈퍼컴퓨팅)
- 언제라도 사용자에게 지연 시간이 낮은 서비스를 제공해야 함 (온라인)
- 규모의 경제 덕에 낮은 비용으로 동일한 성능을 제공하지만 실패율도 높음
- IP와 이더넷을 기반으로 하며 높은 양단 대역폭을 제공하기 위해 클로스 토폴로지로 연결돼 있음
- 시스템이 커질수록 구성 요소 중 하나가 고장날 가능성도 높아짐
- 따라서 항상 뭔가 고장난 상태라고 가정하는 게 합리적
- 지리적으로 분산된 배포를 위해 인터넷 사용 (로컬 네트워크에 비해 느리고 신뢰성도 떨어짐)
분산 시스템이 동작하게 만들려면 부분 장애 가능성을 받아들이고 소프트웨어에 내결함성 메커니즘을 넣어야 한다. 신뢰성 없는 구성 요소를 사용해 신뢰성 있는 시스템을 구축해야 한다. (p276)
발생 가능성이 상당히 낮을지라도 생길 수 있는 결함을 광범위하게 고려하고 테스트 환경에서 인위적으로 이런 상황을 만들어서 어떤 일이 생기는지 보는 게 중요하다. (p277)
신뢰성 없는 네트워크
인터넷과 데이터센터 내부 네트워크 대부분은 비동기 패킷 네트워크다. 이런 종류의 네트워크에서 노드는 다른 노드로 메시지(패킷)를 보낼 수 있지만 네트워크는 메시지가 언제 도착할지 혹은 메시지가 도착하기는 할 것인지 보장하지 않는다. (p278)
현실의 네트워크 결함
네트워크 결함이 드물더라도 결함이 일어날 수 있다는 사실은 여러분의 소프트웨어가 이를 처리할 수 있어야 한다는 뜻이다. (p280)
반드시 네트워크 결함을 견뎌내도록(tolerating) 처리할 필요는 없다. 그러나 소프트웨어가 네트워크 문제에 어떻게 반응하는지 알고 시스템이 그로부터 복구할 수 있도록 보장해야 한다. (p280)
결함 감지
결함 있는 노드를 자동으로 감지할 수 있어야 함
- 로드 밸런서는 죽은 노드로 요청을 그만 보내야 함
- 단일 리더 복제를 사용하는 분산 데이터베이스에서 리더에 장애가 나면 팔로워 중 하나가 리더로 승격돼야 함
명시적인 피드백을 받을 수도 있음
- 목적지 포트에서 수신 대기하는 프로세스가 없다면 운영체제가 RST나 FIN 패킷을 응답으로 보내서 TCP 연결을 닫거나 거부
- 스크립트로 다른 노드에게 프로세스가 죽었다고 알려서 다른 노드가 타임아웃이 만료되기를 기다릴 필요 없이 빠르게 역할을 넘겨받을 수 있게 함
- 데이터센터 내 네트워크 스위치의 관리 인터페이스에 접근할 수 있으면 질의를 보내 하드웨어 수준의 링크 장애를 감지할 수 있음
- 접속하려는 IP 주소에 도달할 수 없다고 라우터가 확신하면 ICMP Destination Unreachable 패킷으로 응답할 수도 있음
아무 응답도 받지 못하는 경우, 몇 번 재시도를 해 보고 타임아웃이 만료되기를 기다렸다가 타임아웃 내에 응답을 받지 못하면 마침내 노드가 죽었다고 선얼할 수 있음
타임아웃과 기약 없는 지연
- 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어짐
- 타임아웃이 짧으면 결함을 빨리 발견하지만 노드가 일시적으로 느려졌을 뿐인데도 죽었다고 잘못 선언할 위험이 높아짐
타임아웃은 얼마나 길어야 할까?
- 모든 패킷은 어떤 시간 d 내에 전소오디거나 손실되지만 전송 시간이 결코 d보다 걸리지 않는다.
- 장애가 나지 않는 노드는 항상 요청을 r 시간 내에 처리한다.
위 조건이 보장된다면 합리적인 타임아웃은 2d + r 이지만, 우리가 사용하는 시스템은 대부분 이 중 어떤 것도 보장하지 않는다.
네트워크 혼잡과 큐 대기
다음과 같은 상황에서 큐 대기가 발생할 수 있다.
- 여러 다른 노드가 동시에 같은 목적지로 패킷을 보내려고 하는 경우 (네트워크 혼잡)
- 패킷이 목적지 장비에 도착했을 때 모든 CPU 코어가 바쁜 상태인 경우
- 가상 환경에서 실행되는 운영체제에서 다른 가상 장비가 CPU 코어를 사용하는 동안 지연이 발생하는 경우
- TCP의 흐름 제어(flow control)에 의해 노드가 네트워크 링크나 수신 노드에 과부하를 가하지 않도록 송신율이 제한되는 경우
- TCP에서 패킷 손실 및 재전송에 의한 지연이 발생하는 경우 (cf. UDP)
네트워크 지연의 변동성에 영향을 주는 요인은 다양하다.
- 여러 소비자가 자원을 공유하는 공개 클라우드 환경에서 자원을 많이 사용하는 누군가(시끄러운 이웃)가 가까이 있는 경우
지연의 변동성이 얼마나 되는지 알아내려면 긴 기간에 여러 장비에 걸쳐서 네트워크 왕복 시간의 분포를 측정해야 한다. (실험적으로 타임아웃을 선택해야 한다.)
더 좋은 방법은 고정된 타임아웃을 설정하는 대신 시스템이 지속적으로 응답 시고나과 그들의 변동성(지터)을 측정하고 관찰된 응답 시간 분포에 따라 타임아웃을 자동으로 조절하게 하는 것이다.
동기 네트워크 대 비동기 네트워크
동기 네트워크의 예 - 전화
- 회선(circuit)은 만들어져 있는 동안 다른 누구도 사용할 수 없는 고정된 양의 예약된 대역폭
- 네트워크의 다음 홉(hop)에 통화당 16비트의 공간이 이미 할당됐기 때문에 큐 대기 문제를 겪지 않음
- 왕복 시간의 최대치를 보장할 수 있음 (제한 있는 지연)
비동기 네트워크의 예 - TCP 연결
- 가용한 네트워크 대역폭을 기회주의적으로 사용
- 이더넷과 IP는 큐 대기의 영향을 받는 패킷 교환 프로토콜(cf. 회선 교환)
- 기약 없는 지연 발생 가능
- 순간적으로 몰리는 트래픽에 최적화 (네트워크 용량에 맞춰 데이터 전송률 동적 조절 가능)
따라서 우리의 시스템에서는 네트워크 혼잡, 큐 대기, 기약 없는 지연이 발생할 것이라고 가정해야 하며, 타임아웃에 올바른 값은 없으며 실험을 통해 결정해야 한다.
신뢰성 없는 시계
- 분산 시스템에서는 통신이 즉각적이지 않으므로 시간은 다루기 까다롭다.
- 게다가 네트워크에 있는 개별 장비는 자신의 시계를 갖고 있다. (보통 수정 발진기, quartz crystral oscillator)
- 시간을 도익화 하기 위해 가장 널리 쓰이는 메커니즘은 네트워크 시간 프로토콜(Network Time Protocol, NTP)로 서버 그룹에서 보고한 시간에 따라 컴퓨터 시계를 조정할 수 있게 한다.
단조 시계 대 일 기준 시계
일 기준 시계 (time-of-day clock)
- 현재 날짜와 시간 반환 (벽시계 시간)
- 리눅스의 clock_gettime(CLOCK_REALTIME)
- 자바의 System.currentTimeMillis()
- 에포크(epoch) 이래로 흐른 초 수 반환
- NTP로 동기화
단조 시계 (monotonic clock)
- 타임아웃이나 서비스 응답 시간 같은 지속 시간(시간 구간)을 재는 데 적합
- 리눅스의 clock_gettime(CLOCK_MONOTONIC)
- 자바의 System.nanoTime()
- 여러 개의 CPU 소켓이 있는 서버는 CPU마다 독립된 타이머가 있을 수도 있음
시계 동기화와 정확도
- 시계 드리프트는 장비의 온도에 따라 변한다.
- 컴퓨터 시계가 NTP 서버와 너무 많은 차이가 나면 동기화가 거부되거나 로컬 시계가 강제로 리셋될 수도 있다.
- 뜻하지 않게 노드와 NTP 서버 사이가 방화벽으로 막히면 잘못된 설정이 얼마 동안 알려지지 않을 수도 있다.
- NTP 동기화는 잘해야 네트워크 지연만큼만 좋을 수 있다.
- 어떤 NTP 서버들은 이상이 있거나 설정이 잘못돼서 몇 시간 정도 차이 나는 시간을 보고하고 있다.
- 윤초가 발생하면 1분의 길이가 59초나 61초가 되어 윤초를 고려하지 않고 설계된 시스템에서는 시간에 관한 가정이 엉망이 돼 버린다.
- 가상 장비에서 하드웨어 시계는 가상화돼서 정확한 시간 엄수가 필요한 애플리케이션에게 추가적인 어려움이 생긴다.
- 완전히 제어할 수 없는 장치에서 소프트웨어를 실행하면 아마도 그 장치의 하드웨어 시계를 전혀 믿을 수 없을 것이다.
시계 정확도가 중요해서 상당한 자원을 투입할 생각이 있다면 시계 정확도를 매우 높이는 것도 가능하다. 예) MiFID II 유럽 금융 기관 규제 초안(UTC와 100마이크로초 이내로 동기화)
동기화된 시계에 의존하기
동기화된 시곅 필요한 소프트웨어를 사용한다면 필수적으로 모든 장비 사이의 시계 차이를 조심스럽게 모니터링해야 한다. 다른 노드와 시계가 너무 차이나는 노드는 죽은 것으로 선언되고 클러스터에서 제거돼야 한다.
이벤트 순서화용 타임스탬프
시계를 의존할 수 없을 때, 여러 노드에 걸친 이벤트의 순서를 정하는 문제
최종 쓰기 승리(last wrtie wins, LWW)
- 데이터베이스 쓰기가 불가사의하게 사라질 수 있음
- 순차적인 쓰기가 빠른 시간 내에 연속으로 실행되는 것과 진짜 동시에 쓰기가 실행되는 것을 구별할 수 없음
- 두 노드가 독립적으로 동일한 타임스탬프를 가진 쓰기 작업을 만들 수도 있음
따라서 가장 "최근" 값을 유지하고 다른 것들을 버림으로써 충돌을 해소하고 싶은 유혹이 들더라도 "최근"의 정의는 로컬 일 기준 시계에 의존하며 그 시계는 틀릴 수도 있다는 것을 아는 게 중요하다.
논리적 시계(logical clock) vs. 물리적 시계(physical clock)
시계 읽기는 신뢰 구간이 있다
시계 읽기를 어떤 시점으로 생각하는 것은 타당하지 않다. 어떤 신뢰 구간에 속하는 시간의 범위로 읽는 게 나을 것이다.
대부분의 시스템은 이 불확실성을 노출하지 않지만, 스패너(Spanner)에 있는 구글 트루타임(TrueTime) API는 로컬 시계의 신뢰 구간을 명시적으로 보고한다.
전역 스냅숏용 동기화된 시계
동기화된 일 기준 시계의 타임스탬프를 트랜잭션 ID로 쓸 수 있을까?
신뢰 구간을 명시하면, 구간이 겹칠 때만 A와 B가 어떤 순서로 실행됐는지 확신할 수 없다. 이에 스패너는 읽기 쓰기 트랜잭션을 커밋하기 전에 의도적으로 신뢰 구간의 길이만큼 기다린다. (신뢰구간이 겹치지 않도록)
프로세스 중단
- 가비지 컬렉터에 의한 중단
- 가상 장비 또는 최종 사용자의 기기에서 서스펜드(모든 프로세스 실행을 멈추고 메모리 내용을 디스크에 저장)에 의한 중단
- 운영체제가 다른 스레드로 컨텍스트 스위치하거나 하이퍼바이저가 다른 가상 장비로 스위치되면서 중단
- 애플리케이션이 동기식으로 디스크에 접근할 시 스레드가 느린 디스크 I/O 연산이 완료되기를 기다리느라 중단
- 운영체제가 디스크로 스왑(페이징)할 수 있게 설정됐다면 단순한 메모리 접근만 해도 페이지를 디스크에서 메모리로 로딩하게 하는 페이지 폴트 발생 가능
- 유닉스 프로세스의 SIGSTOP에 의한 중단
따라서 분산 시스템의 노드는 어느 시점에 실행이 상당한 시간 동안 멈출 수 있다고 가정해야 한다.
응답 시간 보장
엄격한 실시간 시스템(데드라인 명시) : 항공기, 로켓, 로봇, 자동차 등
실시간 보장을 위해서는 실시간 운영체제(real-time operating system, RTOS) 필요
가비지 컬렉션의 영향을 제한하기
- GC 중단을 노드가 잠시 동안 계획적으로 중단되는 것으로 간주하고 노드가 가비지 컬렉션을 하는 동안 클라이언트로부터의 요청을 다른 노드들이 처리하게 하는 것
- 수명이 짧은 객체만 가비지 컬렉터를 사용하고 수명이 긴 객체의 전체 GC가 필요할 만큼 객체가 쌓이기 전에 주기적으로 프로세스를 재시작하는 방법
'공부 > 데이터 중심 애플리케이션 설계' 카테고리의 다른 글
04. 부호화와 발전 (0) | 2021.05.31 |
---|
- Total
- Today
- Yesterday