data-tech-newbie / designing-data-intensive-applications

0 stars 0 forks source link

2022년 08월 17일 스터디 #8

Open Enoch-Kim opened 2 years ago

Enoch-Kim commented 2 years ago

Issue Brief

범위

분산 시스템의 골칫거리

이제부터 잘못될 가능성이 있다 -> 잘못된다로 인식하자(충분히 합리적). 우리는 시스템이 잘못되더라도 사용자의 기대하는 보장을 만족하는 시스템을 구축할 것이다.

이번장은 분산 시스템에 어떤 문제가 있고 어느 정도로 회피가 가능한지 알아보며, 분산 시스템의 상태에 대해 생각하는 방법과 무슨일이 일어났는지 추론하는 방법을 알아본다.

결함과 부분 장애

단일 컴퓨터의 소프트웨어는 결정적(동작하거나 동작하지 않거나)이다.(종종 버그가 재부팅되면서 해결되는 경우가 있지만 그건 동작하지 않는 것이다.) 소프트웨어가 결정적인 이유는 원하지 않는 결과를 내느니 동작하지 않길 원하기 때문이다.(사이드 이펙트는 최악) 단일 컴퓨터에서 우리는 이상적으로 동작하길 바라고, 실제로 유사하게 동작한다.

그러나 분산 시스템에서는 비결정적이다. 어떤 노드가 동작할때도 있고 동작하지 않을때도(부분 장애) 있어서 전체 시스템은 애매한 상태에 놓이게 된다.

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

대규모 컴퓨팅 시스템 구축 철학은 몇가지가 있다

슈퍼컴퓨터의 경우 체크포인트 저장 후 단일 노드의 장애 발생 시 클러스터를 중지시켜 노드 복구 후 체크포인트부터 재실행한다.(단일 컴퓨터와 유사하게 동작) 그러나 우리가 원하는 분산 시스템은 슈퍼컴퓨터와는 다른 특징을 가진다.

결국 우리는 신뢰할 수 없는(언제든 장애가 발생하는) 시스템을 신뢰성 있게 구축하는 것을 해야한다.

신뢰성 없는 네트워크

우리가 다루는 분산 시스템은 HPC와 달리 비공유 네트워크를 사용한다. 즉 다른 장비의 메모리나 디스크에 접근이 불가능하다. 이런 비공유 네트워크를 사용하는 이유는 다음과 같다.

이런 시스템에서는 보통 비동기 패킷 네트워크(asynchronous packet network)를 사용한다. 노드는 다른 노드로 패킷을 보낼 수 있으나, 이는 패킷의 전송 시간과 결과를 보장하지 않는다. 즉 다음과 같은 문제들이 발생할 수 있다.

1

응답을 받지 못했을 경우 클라이언트 측은 알 수 없다. 타임아웃을 이용해 판단은 할 수 있지만 어떤 문제가 발생한 것인지 모른다.

현실의 네트워크 결함

실제로 네트워크 장비를 중복 설정하고 하더라도 스위치 설정 오류 등의 인적 장애를 해결하지 못하기 때문에 신뢰성 있는 네트워크를 만드는 것은 불가능하다. 결국 우리는 일어날 수 있는 네트워크 결함을 대비해야한다는 것이다. (fault tolerance를 가져야함) 단지 에러를 보내는 것을 넘어 시스템이 복구될 수 있도록 해야한다.

결함감지

원격 노드가 요청을 처리하다 죽었다면 원격 노드에서 데이터가 얼마나 처리됐는지 알 수 있는 방법이 없다. 이런경우 다음과 같은 방법으로 결함을 감지할 수 있다.

애플리케이션에게 2xx,3xx 응답을 받지 않으면 우린 TCP Ack 만으로 안심할 수 없다 결국 뭔가 잘못되면 응답을 받지 못할 것을 가정하고 타임아웃을 두어 처리해야한다.

타임아웃과 기약없는 지연

타임아웃은 길어도 문제 안길어도 문제.. 보통 어떤 시간 d 내에 전송되거나 손실되지만 결코 d보다 오래걸리지 않고 장애가 나지 않는 노드가 r만큼의 처리 시간을 보장한다고 할때 2d+r의 타임아웃을 두나 실제로 분산시스템은 기약없는 지연(unbounded delay)이 있어 서버 구현이 대부분 어떤 시간 내에 처리한다고 보장할 수 없다.

네트워크 혼잡과 큐 대기

2

추가로 TCP는 재전송도 있음.. 리소스가 풍부하면 상관없으나 실제 클라우드는 엄청 많은 서비스가 동작하고 있고, 맵리듀스 같은 일괄 처리 작업부하 등으로 네트워크 링크를 포화시키기 쉽다.

결국 우리는 실험적으로 타임아웃을 조정할 수 밖에 없다. 더 유동적으로 변동성(jitter)를 측정하고 관찰된 응답시간 분포에 따라 타임아웃을 자동으로 조절하게 하는 방법도 있다. 파이 증가 장애 감지기를 쓰는 아카와 카산드라가 그 예이다.

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

전화처럼 동기 네트워크를 사용하면 되지 않을까? 라는 생각을 한다면 오산이다. 전화의 경우 다음과 같은 특징을 갖는다.

TCP의 경우 가용한 네트워크 대역폭을 기회주의적으로 사용한다. 이는 요청의 크기가 제각각인 다수의 통신을 가능하게 하기 위함이다(순간적으로 몰리는 트래픽 bursty traffic에 최적화돼있다). 리소스 사용률을 높이기 위함도 있따. 만약 특정 대역을 할당해서 사용한다면 얼만큼을 사용할지도 문제가 된다. 너무 적은 대역폭을 할당하면 리소스가 감당하지 못할 것이고, 너무 큰 리소스를 할당하면 리소스가 부족해 네트워크 리소스를 할당받는 시간만으로 기존의 대기시간을 넘어버릴 것이다.(애초에 IP와 이더넷은 패킷교환 프로토콜이라 네트워크에 기약없는 지연이 있다. 프로토콜부터 바꿔야함)

ATM, InfiniBand 등의 하이브리드 시도도 있었다. 서비스 품질(Quality of Service, 패킷에 우선순위를 매기고 스케줄링함), 진입제어(admission control, 전송측에서 전송률 제한)를 잘쓰면 패킷 네트워크에서 회선 교환을 흉내 내거나 통계적으로 제한 있는 지연을 제공하는 것이 가능하다. 문제는 이런 통신은 인터넷과 이더넷에서 사용할 수 없다..ㅠ 결국 실험적으로..

신뢰성 없는 시계

애플리케이션은 다양한 방식으로 시계에 의존한다. 대표적으로 지속 시간과 시점을 측정하는데, 분산 시스템에서는 통신이 즉각적이지 않기 때문에 시간을 다루기 까다롭다.

단조 시계 대 일 기준 시계

현재 컴퓨터는 최소 두가지 이상의 시계를 가진다(각각 사용 목적이 다름)

시계 동기화와 정확도

일 기준 시계는 NTP서버와 동기화 돼야하는데 사실 다음과 같은 이유로 신뢰할만하지 않다

돈이 많으면 어느정도 해결할 수 있는 방법(좋은 장비와 프로토콜)이 있으나 완전히 해결하진 못한다.

동기화된 시계에 의존하기

우리는 이런 시간 결함을 고려하여 시스템을 설계해야한다. 보통 수정시계가 잘못되거나 NTP클라이언트 설정이 잘못된 경우 빠르게 눈치채지 못할 가능성이 높다. 따라서 동기화된 시계가 필요한 소프트웨어를 사용한다면 필수적으로 모든 장비 사이의 시계차이를 조심스럽게 모니터링 해야한다.

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

카산드라에서 다음과 같은 상황이 발생할 수 있다.

3

여기서 Node2는 set x=1을 나중으로 인식하여 x+=1 연산을 버리고 만다. 이 충돌 해소 전략을 최종 쓰기 승리(last write wins, LWW)라고 불리며 다중 리더 복제와 카산드라 리악과 같은 리더 없는 데이터베이스에서 널리 사용된다. 타임스탬프를 클라이언트에서 구현할 수 있지만 근본적인 문제를 해결하진 못한다.

우린 결국 일 기준 시계가 틀릴 수 있다는 것을 인지해야한다. 올바른 순서화를 위해서는 시계 출처가 측정하려고 하는 대상(즉 네트워크 지연)보다 훨씬 더 정확해야한다. 이른바 논리적 시계(logical clock)는 진동하는 수정 대신 증가하는 카운터를 기반으로 하며 이벤트 순서화의 안전한 대안이 된다. 논리적 시계는 일 기준 시간이나 경과한 초 수를 측정하지 않고 이벤트의 상대적인 순서만 측정한다.

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

아무리 마이크로초에서 나노초로 읽을 수 있는 시계가 있다해도 그 시계는 쉽게 드리프트되기 때문에 신뢰할 수 없다. 다만 우린 특정 신뢰하는 구간으로 시간을 읽을 수 있을 뿐이다. 시계에서 제공하는 오차 범위를 기반으로 계산하는 것이다(자세한 계산법은 생략)

그러나 실제 시스템은 이 불확실성을 노출하지 않는다. cloack_gettime()을 호출하더라도 오차가 몇인지는 알려주진 않으므로 신뢰구간이 5밀리초인지 5년인지 알 수 없다.

하지만 구글의 트루타임(TrueTime) 같은 API는 시계의 신뢰구간을 명시적으로 보고한다. API 요청 시 [earliest, latest]를 받는다.

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

스냅숏 격리에서 전역 단조 증가 트랜잭션ID가 필요하다. 그러나 분산 시스템에서 모든 파티션에 걸쳐 트랜잭션 ID를 생성하는 것이 쉽지 않다. 동기화된 일 기준 시계 타임스탬프를 기준으로 트랜잭션ID를 생성하는 것은 위험하다.(이미 많이 설명했다)

스패너는 트루타임 API의 신뢰구간을 비교함으로써 스냅숏 격리를 구현한다. 두 구간이 겹치지 않는다면 순서를 보장하는 것이다. A = [A earliest, A latest] and B = [B earliest, B latest]) A earliest < A latest < B earliest < B latest 문제는 두 구간이 겹칠 시 순서를 보장할 수 없다는 것이다. 스패너는 읽기 쓰기 트랜잭션을 커밋하기 전에 의도적으로 신뢰구간만큼 기다림으로써 이를 해결한다. 너무 많은 지연이 되지 않기 위해서 구글은 각 데이터센터 내에 GPS 수신기나 원자 시계를 배치하여 약 7밀리초 이내로 동기화시킨다.

프로세스 중단

프로세스는 종종 다음과 같은 이유로 중단된다

이 경우 실행 중인 쓰레드는 어떤 시점에 선점하고 얼마간의 시간이 흐른 후 재개할 수 있는데, 선점된 쓰레드는 이를 알지 못한다. 이런 경우 멈춘 시간 동안 불필요하게 변화가 발생할 수 있다 (리더의 선점권 잃음 등)

단일 장비에서는 프로세스 중단을 대비할 많은 방법이 있으나 분산 시스템은 이를 바로 적용할 수 없다.

응답 시간 보장

위의 프로세스 중단은 충분히 많은 노력을 들여 원인을 제거할 수는 있지만 이는 리얼타임 운영체제가 필요하고, 우리가 사용하는 시스템에서 원하지 않는 결과를 갖는다.

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

가비지 컬렉션으로 인해 stop-the-world 가 발생하기 전에 애플리케이션에 알려줘서 요청을 중지하거나 하는 방법이 있다.

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

다음주에..