-
[데이터 중심 애플리케이션 설계] 분산시스템의 골칫거리📚 개발 도서/데이터 중심 애플리케이션 설계 2025. 3. 2. 22:12
결함과 부분 장애
- 단일 시스템은 결정적이다. (하드웨어가 올바르게 동작하면, 같은 연산은 항상 같은 결과를 낸다.)
- ↔ 분산 시스템은 비결정적이다. (부분장애 → 어떨 때는 동작, 어떨 때는 실패)
컴퓨터는 구현 기반이 되는 불분명한 물리적 현실을 감추고 수학적 완벽함을 갖고 동작하는 이상화된 시스템 모델을 보여준다.
하지만, 네트워크로 연결된 여러 컴퓨터에서 실행되는 소프트웨어를 작성할 때는 근본적으로 상황이 다르다. 분산 시스템에서는 더이상 이상화된 시스템 모델에서 동작하지 않는다.
분산 시스템에서는 시스템의 어떤 부분은 잘 동작하지만 다른 부분은 예측할 수 없는 방식으로 고장날 수 있다.
이를 부분 장애(partial failure)라고 한다. 부분 장애는 비결정적이라서 예측하기 어려운 문제를 일으킨다.
클라우드 컴퓨팅과 슈퍼컴퓨팅
- 대규모 컴퓨터 시스템
- 클라우드 컴퓨팅
- ↔ 슈퍼컴퓨팅(수천개의 CPU, 계산비용이 매우 높다 - HPC)
이는 부분 장애를 전체 장애로 확대하는 방법으로 처리한다(체크포인트 기반). 하지만 이는 인터넷 서비스를 구현하는 시스템과 매우 다르다.
- 결함이 생기면 우리는 소프트웨어가 어떻게 동작할지 알아야 한다.
결함이 드물 것이라고 가정하고 최선의 상황을 바라기만 하는 것은 현명하지 못하다. - 발생 가능성이 상당히 낮을지라도 생길 수 있는 결함을 광범위하게 고려하고, 테스트 환경에서 인위적으로 이런 상황을 만들어서 어떤 일이 생기는지 보는 게 중요하다.
분산 시스템에서 의심, 비관주의, 편집증은 그 값어치를 한다.
** 카오스 몽키: 서비스를 공급하는 인스턴스에 일부러 랜덤으로 장애를 일으키는 것. 목적: 가장 약한 부분이 어디인지 파악하여, 엔지니어가 문제에 대처하는 자동화된 트리거를 설정
신뢰성 없는 네트워크
타임아웃과 기약 없는 지연
- 타임아웃이 짧으면 노드가 죽었다고 잘못 선언할 위험이 높아진다. 성급하게 노드가 죽었다고 선언하면 문제가 된다.
노드가 죽었다고 선언되면 그 노드의 책무는 다른 노드로 전달돼야 해서 다른 노드와 네트워크에 추가적인 부하를 준다. 이는 문제를 악화시킬 수 있다.
특히 노드가 실제로는 죽지 않았고, 과부하 때문에 응답이 느릴 뿐일 수도 있다. 그 노드의 부하를 다른 노드에 전달하면 연쇄 장애를 유발할 수 있다. - 더 좋은 방법은 고정된 타임아웃을 설정하는 대신 시스템이 지속적으로 응답 시간과 그들의 변동성(Jitter)를 측정하고 관찰된 응답시간 분포에 따라 타임아웃을 자동으로 조절하게 하는 것이다.
(파이 증가 장애 감지기(Phi Accrual failure detector)를 쓰면 된다 (활용: Akka와 카산드라))
동기 네트워크 대 비동기 네트워크
고정회선 전화 네트워크: 동기식, 데이터가 여러 라우터를 거치더라도 큐 대기 문제를 겪지 않는다. 네트워크의 다음 홉에 통화당 16비트의 공간이 이미 할당됐기 때문이다.
ㄴ TCP 연결과 매우 다르다. TCP 연결의 패킷은 가용한 네트워크 대역폭을 기회주의적으로 사용한다. TCP는 가용한 네트워크 용량에 맞춰 데이터 전송률을 동적으로 조절한다.
신뢰성 없는 시계
스레드는 다양한 이유로 오랫동안 멈출 수 있다.
- 가비지 컬렉터(GC)
- 가상환경에서 가상장비의 suspend
- 운영체제의 다른 스레드로의 컨텍스트 스위치
- 스레드가 느린 디스크 I/O 연산
- 디스크로 스왑(paging)
→ 많은 프로그래밍 언어와 운영체제에서 스레드와 프로세스는 기약 없는 시간동안 중단될 수 있다.
가비지 컬렉션의 영향을 제한하기
- 아이디어: GC 중단을 노드가 잠시동안 계획적으로 중단되는 것으로 간주하고, 노드가 GC를 하는 동안 클라이언트로부터의 요청을 다른 노드들이 처리하게 하는 것이다.
ㄴ 이렇게 하면 GC 중단을 클라이언트로부터 감추고, 응답 시간의 상위 백분위를 줄일 수 있다. (이 방법은 지연 시간에 민감한 금융 거래 시스템에서도 사용된다.) - 위 아이디어의 변형: 수명이 긴 객체의 전체 GC가 필요한 만큼 객체가 쌓이기 전에 주기적으로 프로세스를 재시작하는 방법
ㄴ 트래픽을 재시작하려는 노드에서 다른 노드로 옮길 수 있다.
지식, 진실, 그리고 거짓말
- 분산 시스템에서 우리는 동작(시스템 모델)에 관해 정한 가정을 명시하고, 이런 가정을 만족시키는 방식으로 실제 시스템을 설계할 수 있다.
- 분산 시스템은 한 노드에만 의존할 수는 없다. 노드에 언제든 장애가 나서 잠재적으로 시스템이 멈추고 복구할 수 없게 될 수도 있기 때문이다.
대신 여러 분산 알고리즘은 정족수(quorum), 즉 노드들 사이의 투표에 의존한다.
펜싱 토큰
파일저장소 같은 리소스에 대한 접근을 보호하기 위해 잠금이나 임차권을 쓸 때, 자신이 “선택된 자”라고 잘못 믿고 있는 노드가 나머지 시스템을 방해할 수 없도록 보장해야 한다.
ㄴ 펜싱 (상당히 단순한 기법)
펜싱 토큰은 잠금이 승인될 때마다 증가하는 (ex. 잠금 서비스가 증가시키는) 숫자다. 그러면 클라이언트가 쓰기 요청을 저장소 서비스로 보낼 때마다 자신의 현재 펜싱 토큰을 포함하도록 요구할 수 있다.
ex) 잠금서비스로 주키퍼를 사용하면 트랜잭션 ID zxid나 노드 버전 cversion을 펜싱 토큰으로 사용할 수 있다. 이들은 단조 증가가 보장되므로 필요한 속성을 지닌다.
서버 측에서 토큰을 확인하는 것은 결점으로 보이지만 거의 틀림없이 좋다. 서비스의 클라이언트들이 항상 잘 동작할 것이라고 가정하는 것은 현명하지 못하다. 클라이언트는 서비스를 실행하는 사람들의 우선 사항과 매우 다른 우선 사항을 가진 사람들이 실행하는 경우가 흔하기 때문이다.
따라서 스스로를 뜻하지 않게 폭력적인 클라이언트로부터 보호하려는 서비스는 서버 측에서 토큰을 확인하는 게 좋다.
분산 시스템 문제는 노드가 “거짓말” (임의로 결함이 있거나 오염된 응답을 보냄)을 할지도 모른다는 위험이 있다면 훨씬 더 어려워진다. 예를 들어 어떤 노드가 실제로는 받지 않은 특정 메시지를 받았다고 주장할 수도 있다.
→ 비잔틴 결함(Byzantium fault)라고 하며, 이렇게 신뢰할 수 없는 환경에서 합의에 도달하는 문제를 비잔틴 장군 문제라고 한다.
웹 애플리케이션은 최종 사용자가 제어하는 웹브라우저 같은 클라이언트의 행동이 임의적이고 악의적이라고 예상해야한다.
ㄴ 입력 확인(input validation), 살균(sanitization), 출력 이스케이핑(output escaping)이 매우 중요한 이유다.
안전성과 활동성
유일성과 단조 일련번호 → 안전성 / 가용성 → 활동성
이 두가지 속성을 구별하는 방법은? 활동성 속성은 흔히 그 정의에 “결국에(eventually)”라는 단어를 포함한다는 것이다.
- 안전성 속성이 위반되면 그 속성이 깨진 특정 시점을 가리킬 수 있다. (ex. 유일성 속성이 위반되면 중복된 펜싱 토큰을 반환한 특정 연산을 식별할 수 있다.) 안전성 속성이 위반된 후에는 그 위반을 취소할 수 없다. 이미 손상된 상태다.
- 활동성 속성은 반대로 동작한다. 어떤 시점을 정하지 못할 수 있지만(ex. 노드가 요청을 보냈지만 아직 응답을 받지 못했을 수 있다.) 항상 미래에 그 속성을 만족시킬 수 있다는 (다시 말해 응답을 받음으로써) 희망이 있다.
흔히 비공식적으로는 안전성은 나쁜 일이 일어나지 않는다라고, 활동성은 좋은 일은 결국 일어난다 라고 정의한다.
머릿속으로는 알고 있었지만, 분산 시스템을 다루기 까다롭다는 것을 다시 한번 깨달았다.
회사에서 분산 시스템을 다루고 있기에 그 고충들이 다시 떠올랐다.
개발을 시작하기 전 설계 단계에서 상사에게 “실패하는 케이스”에 대한 고려를 요청받곤 했다.이 책을 읽으면서 그분의 의도를 확실히 이해할 수 있었다. (+ 존경 🥹)
- “결함이 생기면 우리는 소프트웨어가 어떻게 동작할지 알아야 한다. 결함이 드물 것이라고 가정하고 최선의 상황을 바라기만 하는 것은 현명하지 못하다.”
- “분산 시스템에서 우리는 동작(시스템 모델)에 관해 정한 가정을 명시하고, 이런 가정을 만족시키는 방식으로 실제 시스템을 설계할 수 있다.”
발생 가능한 결함들을 고려하는 자세가 엔지니어로서 가져야 할 중요한 마인드셋임을 알 수 있었다. 결함이 드물다고 가정하고 최선의 상황만을 바라보는 것은 결국 예상치 못한 상황에서 대응이 어려워질 수 있기 때문이다.
결함이 발생했을 때 빠르게 대처할 수 있도록, 이를 미리 예상하고 준비하는 것이 시스템의 안정성뿐만 아니라 엔지니어로서의 신뢰성을 높이는 길임을 느꼈다.
또한, 정족수(quorum) 알고리즘의 중요성, 펜싱 토큰, 가비지 컬렉션 영향을 제한하는 방법 등 다양하고 유익한 내용들이 많아 재미있게 읽은 챕터였다 😆
반응형'📚 개발 도서 > 데이터 중심 애플리케이션 설계' 카테고리의 다른 글
[데이터 중심 애플리케이션 설계] 응답 시간 (p50, p95, p99) (0) 2023.04.13 [데이터 중심 애플리케이션 설계] BASE와 ACID (0) 2023.04.12