데이터 중심 애플리케이션 설계 - 8장

공상현 (Kong Sang Hyean)·2024년 7월 3일
0

K DEVCON DDIA STUDY

목록 보기
8/12

😊 Go to Learn이란?

K-devcon에서 주최하는 멘토링 프로그래밍으로 각 분야에서 전문가이신 멘토분들의 멘토링을 통하여 약 2-3달간 진행하는 프로그램입니다.

Go to Learn 1기 같은 경우 Flutter, Back-end, Full-stack, Writing 등 여러가지 주제가 담긴 멘토링 프로그램이 있었습니다.

그 중 Back-end를 중심으로 진행하는 DDIA 프로그램 같은 경우 데이터 중심 애플리케이션 설계라는 책을 매주 1장 씩 정독하고 요약하면서 괸련된 이야기를 논의하면서 진행하고 있습니다.

K-devcon 이란? : IT 정보를 공유하거나 위에서 설명한 Go to Learn 스터디 및 밋업을 개최하는 활동을 하고있는 IT 커뮤니티입니다.
K-devcon 홈페이지 바로가기


📖 8장 요약 및 정리

8장은 분산 시스템을 다루면서 일어나는 문제점을 설명하는 장이다.
위의 장은 크게 네트워크 관련 문제 & 타이밍 문제 & 분산 시스템의 상태에 대해 추론하는 방법 순으로 설명하고 있다.
다음 장인 9장에서는 이러한 분산 시스템에서 문제에 대처하도록 설계되어지거나 보장을 제공해주는 알고리즘에 대하여 설명한다.

부분 장애

부분 장애(partial failure) : 분산 시스템에서는 시스템의 어떤 부분은 잘 동작하지만 다른 부분은 예측할 수 없는 방식으로 고장하는 현상을 일컫는다.

  • 비결정적이라서 확인하기 어렵다. 따라서 분산 시스템을 다루기 어렵게 만든다.
  • 조만간 시스템의 어떤 부분에 결함이 생길 것을 가정하고 그 결함을 처리해야만 한다.

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

고성능 컴퓨팅 : 수천개의 CPU를 가진 컴퓨터로 보통 계산 비용이 매우 높은 과학 계산 작업이 쓰인다.

  • 보통 계산 상태를 지속성 있는 저장소에 체크포인트로 저장한다.
  • 클러스터 작업부하를 중단한 후 마지막 체크포인트부터 계산을 시작한다.
  • 단일 노드 컴퓨터와 가까운 편이다. (인터넷 서비스를 구현하는 시스템과 다른 편)

클라우드 컴퓨팅 : 보통 멀티 테넌트 데이터센터, IP 네트워크로 연결된 컴퓨터, 자원 할당 등과 관련 되어있다.


신뢰성 없는 네트워크

인터넷과 데이터센터 내부 네트워크 대부분은 비동기 패킷 네트워크로 이루어져 있다.

이런 종류의 네트워크에서 노드는 다른 노드로 메시지를 보낼 수 있지만 언제 메시지가 도착하기 할 것인지 보장하지 않는다. 요청을 보내고 응답을 기다릴 때 여러 가지가 잘못 될 수 있기 때문이다.

이러한 문제들은 비동기 네트워크에서 구별할 수 없다. 따라서 이러한 문제를 다루는 흔한 방법은 타임아웃이다.

현실의 네트워크 결함

네트워크 문제는 놀랄만큼 흔하게 발생되어지는 문제이다. 따라서 네트워크 결함이 드물더라도 결함이 일어날 수 있다.

네트워크 결함의 오류 처리가 정의되고 테스트되지 않는다면 나쁜 일이 제멋대로 생길 수 있다.

  • 하지만 결함이 견뎌내도록 처리할 필요는 없다. 하지만 문제를 어떻게 반응하는지 알고 복구 할 수 있도록 보장해야한다.

많은 시스템은 결함 노드를 자동으로 감지할 수 있어야한다. 그러나 네트워크에 대한 불확실성 때문에 구별하기 어렵다.

  • 요청이 성공했음을 확신하고 싶다면 애플리케이션 자체로부터 긍정 응답을 받아야한다.
  • 뭔가 잘못되어진다면 스택의 어떤 수준에서 오류 응답을 받을지도 모르지만 아무 응답도 받지 못할 것을 가정해야한다.

타임아웃

타임아웃의 지연

  • 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어진다.
  • 타임아웃 선언이 짧아지면 다른 노드가 역할을 넘겨 받아 그 동작을 두 번 실행하게 될지도 모른다.
  • 서버 구현은 대부분 어떤 최대 시간 내에 요청을 처리한다고 보장 할 수 없다.

기약 없는 지연 : 패킷을 가능한 빨리 보내려고 하지만 패킷이 도착하는 데 걸리는 시간에 상한치는 없다.

네트워크 혼잡과 큐 대기

  • 컴퓨터 네트워크에서 패킷 지연의 변동성은 큐 대기 때문인 경우가 많다.
  • TCP 같은 경우 시간 안에 확인 응답을 받지 않는다면 패킷이 손실되었다고 간주하고 패킷을 재전송한다.
  • 공개 클라우드 및 멀티 테넌트 데이터센터 같은 경우 여러 소비자가 자원을 공유하기 떄문에 누군가 자원을 많이 사용할 경우 네트워크 지연 변동이 클 수 밖에 없다. -> 변동성을 측정하고 자동으로 타임 아웃을 조절하는 방법이 있다.

동기 네트워크 & 비동기 네트워크

왜 하드웨어 수준에서 패킷을 유실하게 만드는 문제를 해결하고 신뢰성을 높힐 수 없을까?

전화 네트워크 같은 경우 통화를 할 때 회선이 만들어지는 방식으로 진행하게된다.

  • 이런 종류의 네트워크는 동기식으로 이루어진다. (제한 있는 지연)
  • 제한 있는 지연 : 큐 대기 시간이 없으므로 네트워크 종단 지연 시간의 최대치가 고정되어 있다.
  • TCP 연결 같은 경우 전화와 다르게 가능하면 최대한 짧은 시간 안에 전송하려는 것이 목적이다. (순간적으로 몰리는 트래픽에 최적화)

신뢰성 없는 타이밍

분산 시스템에서는 통신이 즉각적이지 않으므로 타이밍을 다루기 까다롭다. 이러한 이유로 어떤 일이 발생한 순서를 알아내기 어렵게 만든다.

단조 시계 & 일 기준 시계

일 기준 시계 : 직관적으로 시계에 기대하는 일을 한다. 보통 NTP로 동기화한다. 그러나 로컬 시계가 NTP 서버보다 너무 앞서면 강제로 리셋되어 과거 시점으로 거꾸로 뛰는 것처럼 볼 수 있다.

  • clock_gettime(CLOCK_REALTIME) / System.currentTimeMillis()

단조 시계 : 타임아웃이나 서비스 응답 시간 같은 지속 시간을 재는 데 적합하다. 항상 앞으로 흐른다는 사실에서 따왔다. 두 시점의 단조 시계의 값을 확인하고 시간이 얼마나 흘렀는지 알 수 있다. 그러나 시계의 절대적인 값은 의미가 없다.

  • clock_gettime(CLOCK_MONOTONIC) / System.nanoTime()
  • 여러 개의 CPU 소켓이 있는 서버 같은 경우 각 차이를 보정해서 단조적으로 보이게 하려고 한다.
  • 분산 시스템에서 경과 시간을 재는 데 단조 시계를 쓰는 것은 일반적으로 괜찮다.

시계 동기화와 정확도

시계가 정확한 시간을 알려주게 하는 방법은 기대만큼 신뢰성이 있거나 정확하지 않다.

  • 드리프트 현상 (drift) : 컴퓨터의 수정 시계 내에서 예상 되는 시각보다 더 빠르거나 느리게 실행되는 현상, 위의 현상은 장비의 온도에 따라 변하게 된다.
  • NTP 동기화는 잘해야 네트워크 지연만큼만 좋을 수 있다. 따라서 패킷 지연의 변화가 큰 혼잡한 네트워크에서는 정확도에 한계가 있다.
  • 문지름 (smearing) : 윤초를 처리하는 최선의 방법 중 하나로 윤초 조정을 하루에 걸쳐서 서서히 수행함으로써 NTP 서버가 거짓말을 하게 처리할 수 있는 현상이다.
  • 정확도를 높힐지라도 NTP 데몬 설정이 잘못되었거나, 방화벽이 NTP 트래픽을 파단한다면 드리프트에 따른 시계 오류는 빠르게 커질 수 있다.

시계 또한 대부분의 시간에 아주 잘 동작하지만 견고한 소프트웨어는 잘못된 시계에 대해 대비할 필요가 있다.

  • 시계가 잘못되었다는 사실을 눈치채지 못할 수 있기 때문에 극적인 고장보다 조용하고 미묘한 데이터 손실이 발생 될 수 있다. -> 모니터링이 필요하다.

최종 쓰기 승리 (last write wins, LWW) : 다중 리더 복제와 리더 없는 데이터 베이스에 널리 쓰이지만 근본적인 문제를 바꾸지는 못한다.

  • "최근"의 정의는 로컬 일 기준 시계에 의존하며 그 시계는 틀릴 수도 있다는 것을 아는 것이 중요하다.
  • 논리적 시계 (logical clock) : 증가하는 카운터를 기반으로 하여 이벤트 순서화의 안전한 대안으로 사용되어진다. -> 단조 시계 및 일 기준 시계는 실제 경과 시간을 측정한다. (물리적 시계)

세밀한 측정을 하게 되더라도 정밀성을 제공할만큼 정확해지지 않는다.

  • 네트워크 혼잡이 있으면 쉽게 드리프트 값이 급증한다.
  • 시계 읽기는 어떤 시점보단 신뢰 구간에 속하는 시간의 범위로 읽는 게 낫다.
  • 불확실성 경계는 시간 출처를 기반으로 계산할 수 있지만 이러한 불확실성을 노출시키지는 않는다. -> 예외 : 구글 트루타임 API

스냅숏 격리

  • 가장 흔한 구현은 단조 증가하는 트랜잭션 ID를 이용하여 구현한다. 그러나 여러 장비에 분산되어 있을 경우 이러한 ID를 생성하기 어렵다.
  • 스패너의 스냅숏 같은 경우 시계 신뢰 구간을 사용하여 스냅숏 격리를 구현한다. 인과성을 반영하는 것을 보장하기 위해서이다.

프로세스 중단

노드가 여전히 리더인지, 안전하게 쓰기를 받아들일 수 있는지 확인하는 방법이 없을까?

임차권 (lease)을 얻는 방법 : 특정 시점에서 오직 하나의 리더만 임차권을 얻을 수 있다.

  • 임차권이 만료될 떄 까지 주기적으로 갱신해야하므로 장애가 날 경우 갱신을 멈추게 되므로 다른 노드가 넘겨 받을 수 있다.
  • 동기화 된 시계에 의존할 경우 동기화가 깨지게 된다면 이상하게 작동할 것이다.
  • 로컬 단조 시계만 사용할 경우 프로그램 실행 중 예상치 못한 중단이 일어날 경우 임차권이 만료되었음을 알아채지 못할 가능성이 있다. -> 이런 경우 실행 중인 스레드를 어떤 시점에 선점하고 얼마간의 시간이 흐른 뒤 재개할 수 있다.
  • 단일 장비에서 다중 스레드 코드를 작성할 때 스레드를 안전하게 만드는 도구들이 있다.
  • 분산 시스템 노드 같은 경우 어느 시점에 실행이 상당한 시간 동안 멈출 수 있다고 가정해야한다.

엄격한 실시간 시스템 (hard real-time) : 센서 입력에 빠르고 예측 가능하게 응답해야하는 환경인 경우 데드라인이 명시된다. 만일 데드라인에 만족 시키지 못한다면 전체 시스템에 장애를 유발할 수 있다.

시스템에서 실시간 보장을 제공하려면 소프트웨어 스택의 모든 수준에서 지원이 필요하다.

  • 명시된 간격의 CPU 시간을 할당받을 수 있게 보장되도록 스케줄링해주는 실시간 운영체제가 필요하다. (RTOS, real-rime operating system)

프로세스 중단을 대비하는 방법 중 하나는 가비지 컬렉션의 영향을 제한하는 방법이다.

  • 가비지 컬렉션 중단을 노드가 잠시 동안 계획적으로 중단되는 것으로 간주하고 노드가 가비지 콜렉션을 하는 동안 클라이언트로부터 요청을 다른 노드들이 처리하게 해주는 방법

분산 시스템의 상태

네트워크에 있는 노드들은 어떠한 것들도 확실히 알지 못한다. (부분 장애, 신뢰성 없는 시계, 프로세스 중단)
단지 네트워크를 통해 받은 메시지를 기반으로 추측만 할 수 있을 뿐이다.

진실은 다수결로 결정한다.

분산 시스템은 한 노드에서만 의존 할 수 없다. 노드가 상황에 대한 자신의 판단을 반드시 믿을 수 있지 않는다.

정족수 (quorum) : 특정한 노드 하나에 대한 의존을 줄이기 위해 결정을 하려면 최소한의 수치를 받아야한다.

  • 합의 알고리즘에서 활용

분산 시스템에서 리더와 잠금을 구현하려면 주의가 필요하다. 노드의 과반수가 그 노드가 죽었다고 선언했음에도 그 노드가 계속 행동할 수도 있기 때문이다.

팬싱 (fencing) : 리소스에 대한 접근을 보호하기 위해 잠금 및 임차권을 사용할 때, 자신이 선택된 자라고 잘못 믿고 있는 노드가 나머지 시스템을 방해할 수 없도록 보장하는 기법 중 하나이다.

  • 팬싱 토큰을 활용하여 쓰기 요청 같은 경우 자신의 펜싱 토큰을 요구하도록 유도할 수 있다.
  • 오래된 토큰을 사용해서 쓰는 것을 거부함으로써 토큰을 확인하는 활동적인 역할을 맡아야한다.
  • 스스로 뜻하지 않게 폭력적인 클라이언트로부터 보호하려는 서비스 같은 경우 확인하는 것이 좋다. (부주의에 의한 오류)

비잔틴 결함

비잔틴 결함 (Byzantine fault) : 분산 시스템 내에서 노드가 거짓말을 할 지도 모른다는 위험이 있다는 것을 가정하는 경우 -> 신뢰할 수 없는 환경에서 합의에 도달하는 문제를 비잔틴 장군 문제라고 한다.

  • 일부 노드가 오작동하고 프로토콜을 준수하지 않거나 악의적인 공격자가 네트워크를 방해하더라도 시스템이 올바르게 동작할 경우 이 시스템은 비잔틴 내결함성을 지닌다.
  • 웹 애플리케이션 같은 경우 클라이언트의 행동이 임의적이고 악의적이라고 예상해야한다. -> SQL 주입 공격, 크로스 사이트 스크립팅 등

시스템 모델과 현실

분산 시스템의 문제를 해결하기 위해서 많은 알고리즘이 설계되고 있는데 위 알고리즘은 하드웨어와 소프트웨어 설정의 세부 사항에 너무 심하게 의존하지 않는 방식으로 작성해야한다.

  • 보통 시스템 모델을 정의하여 정형화가 이루어진다.

타이밍 가정

  • 동기식 모델 : 네트워크 지연, 프로세스 중단, 시계 오차에 모두 제한이 있다고 가정
  • 부분 동기식 모델 : 대부분의 시간에는 동기적으로 동작하지만 때때로 한계치를 초과할 수 있다.
  • 비동기식 모델 : 타이밍에 대한 어떠한 가정도 할 수 있다.

노드 장애를 고려

  • 죽으면 중단하는 결함 : 노드에 장애가 나는 방식이 죽는 것 하나뿐으로 가정하게 된다.
  • 죽으면 복구하는 결함 : 노드가 어느 순간 죽을 수도 있지만 시간이 흐른 후 다시 응답하기 시작하는 경우
  • 비잔틴 결함 : 다른 노드를 속일 수 있다.

알고리즘이 정확하다는게 어떤 의미인가 정의하기 위해 알고리즘의 속성을 기술할 수 있다.

  • 팬싱 토큰 같은 경우 유일성, 단조 일련번호, 가용성 같은 속성을 기술하게 된다.
  • 상황을 분명히 하기 위해 두 가지 다른 종류의 속성 (안전성, 활동성)을 구별할 필요가 있다.
    - 안전성 : 나쁜 일은 일어나지 않는다 / 활동성 : 좋은 일은 결국 일어난다.
    - 안정성과 활동성 속성을 구별하게 된다면 어려운 시스템 모델을 다루는 데 도움이 된다.

현업에서 알고리즘을 구현할 때 시스뎀 모델은 현실의 단순화된 추상화라는 것이 명백해진다.

  • 알고리즘을 이론적으로 설명할 때는 어떤 일이 일어나지 않는다고 가정할 수 있다. 그러나 실제 구현에는 여전히 불가능하다고 가정했던 일이 발생하는 경우를 처리하는 코드를 포함시켜야 할 수도 있다.
  • 추상 시스템 모델 같은 경우 문제를 이해하고 해결하는데 엄청난 도움이 된다.
  • 알고리즘이 올바르게 증명되었다고 하더라도 현실 시스템의 구현도 언제나 올바르게 동작한다는 뜻은 아니다.

😉 필자의 생각

8장 같은 경우 분산 네트워크에서 발생 될 수도 있는 여러 문제들을 각각 설명하는 내용을 담고 있었다. 내용들을 잘 살펴보면 UDP, TCP, 회로 서킷 같은 학교 내 자료 구조 및 네트워크 시간에서 배웠던 용어들이 많이 등장했던 것을 알 수 있었다.

비록 아직 실무에서 분산 네트워크를 다루는 경험 & 기회가 없었어서 그런지 해당 문제가 발생될 수 있다는 정도만 위의 장을 읽으면서 정리했었던 것 같다.

스터디 모임에서는 네트워크 결함과 관련된 심화적인 내용을 들을 수 있는 주제가 하나 있었는데 전화국 형태로 망이 이루어지고 있는 Fibre Channel를 사용하는 InfiniBand에 대한 설명과 소프트웨어 영역에서 바로 네트워크 패킷을 사용할 수 있는 DPDK (Data Plane Development Kit) 대한 설명을 발표자의 경험에 빗대어서 들었었다.

profile
개발자 같은 거 합니다. 1인분 하는 개발자로서 살아갈려고 노력 중입니다.

0개의 댓글