ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 8장 - 분산 시스템의 골칫거리
    CS/데이터 중심 애플리케이션 설계 요약 2022. 12. 22. 00:01
    728x90

    개요

    복제 서버 장애 복구, 복제 지연, 트랜잭션의 동시성 제어를 설명하며 시스템이 잘못된 것을 처리하는 방법에 대해 다루었습니다.

    하지만 현실은 훨씬 더 암울합니다.

    이제는 최대한 비관적으로 어떤 것이든지 잘못된 가능성이 있다면 잘못된다고 가정합니다.

    이번장에서는 분산 시스템에서 네트워크 관련 문제와 시계 및 타이밍 문제를 조사하고 어느 정도로 회피할 수 있는지 설명합니다.

     

    결함과 부분 장애

    가끔 운수 나쁜 날재부팅하면 흔히 고쳐지는 것처럼 보이지만 보통 잘못 작성한 소프트웨어의 결과입니다.

    CPU 인스트럭션을 항상 같은 일을 하고 메모리나 디스크에 데이터를 쓰면 온전하게 남아 있고 제멋대로 오염되지 않습니다.

     

    물리적 세계에서는 매우 광범위한 것들이 잘못될 수 있습니다.

    네트워크 분단, 전원장애, 스위치 장애, 전체 랙에서 주기적으로 일어난 전원 사고, 픽업 트럭을 데이터센터의 통풍 시스템에 박는 등이 있습니다.

     

    분산 시스템에서는 이런 예측할 수 없는 방식으로 고장이 나는 것도 무리가 아닙니다.

    메시지가 네트워크를 거쳐 전송되는 시간도 비결정적입니다.

    따라서 여러 노드와 네트워크와 관련된 뭔가를 시도하면 예측할 수 없는 방식으로 실패합니다.

     

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

    인터넷 관련 애플리케이션은 언제라도 사용자에게 낮은 지연 서비스를 제공해야 한다는 점에서 슈퍼 컴퓨터 같은 단일 노드를 사용할 수 없습니다.

    따라서 클라우드 컴퓨터를 사용할 때 수천 개의 노드가 있다면 항상 뭔가 고장 난 상태라고 가정하는 게 합리적입니다.

    이런 부분 장애 가능성을 받아들이고 소프트웨어에 내결함성 매커니즘을 넣어야 합니다.

    분산 시스템에서 의심, 비관주의, 편집증은 그 값어치를 합니다.

     

    신뢰성 없는 구성 요소를 사용해 신뢰성 있는 시스템 구축하기란 IP를 사용할 때 패킷이 누락 또는 지연 중복될 수 있으며, 순서가 바뀔 수 있습니다. 하지만 TCP를 통해 신뢰성이 높은 전송 계층을 제공합니다.

     

    신뢰성 없는 네트워크

    분산 시스템은 네트워크로 연결된 다수의 장비입니다.

    인터넷과 데이터센터 내부 네트워크 대부분은 비동기 패킷 네트워크입니다.

    메시지가 언제 도착할지 혹인 메시지가 도착하기는 할 것인지 보장하지 않습니다.

     

    잘못될 수 있는 다양한 예시들이 존재합니다.

    -  요청이 손실됨(누군가 네트워크 케이블 뽑음)

    -  요청이 큐에서 대기하다 나중에 전송됨(과부하)

    -  원격 노드에 장애 발생

    -  원격 노드가 일시적으로 응답을 멈춤(gc가 길어짐)

    -  원격 노드가 요청을 처리했지만 응답이 손실됨(네트워크 스위치 설정 잘못됨)

    -  요청을 처리했지만 응답이 지연되다가 나중에 전송됨(과부하)

     

    이런 문제들을 비동기 네트워크에서 구별할 수 없고 흔히 타임아웃으로 다룹니다.

    타임아웃이 발생해도 원격 노드가 응답을 받았는지 아닌지는 여전히 알 수 없습니다.

    심지어 응답에서 타임아웃이 발생해 이미 요청을 처리한 상황일 수도 있습니다.

     

    결함 감지

    시스템은 결함 있는 노드를 자동으로 감지할 수 있어야 합니다.

    -  로드 밸런서는 죽은 노드로 요청을 그만 보내야 함

    -  단일 리더 복제를 사용하는 분산 데이터베이스는 리더가 장애가 나면 팔로워 중 하나가 리더로 승격되어야 함

     

    타임아웃과 기약없는 지연

    타임아웃을 얼마나 기다려야 할까?

    타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어집니다

    타임아웃이 짧으면 노드가 일시적으로 느려진 것뿐인데도 죽었다고 잘못 선언할 수 있습니다.

    타임아웃은 시스템마다 적절하게 설정해야 합니다.

     

    네트워크 혼잡과 큐 대기

    -  네트워크 스위치에서 패킷이 큐 대기를 할 수 있습니다. 만약 큐가 가득차면 패킷이 유실되어 재전송될 수 있습니다.

    -  패킷이 도착해도 CPU 코어가 바쁘다면 윤영체제 큐에서 대기합니다.

    -  가상환경에서는 다른 가상 장비가 CPU를 사용하는 순간 멈추게 됩니다. 이때에도 데이터를 큐에 넣어 대기합니다.

    -  TCP 자체에서도 혼잡 회피를 위해 네트워크로 들어가기 전 큐 대기를 할 수 있습니다.

     

    공개 클라우드와 멀티 테넌트 데이터센터에서는 여러 소비자가 자원을 공유합니다.

    이때 자원을 많이 쓰는 누군가가 가까이 있다면 네트워크 링크를 포화시키기 쉬움으로 네트워크 지연 변동이 클 수 있습니다.

     

    멀티 테넌시란?

    클라우트 컴퓨팅에서 서로 다른 고객이 서버 리소스를 나누어 사용하는 공유 호스팅

     

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

    패킷 전송 지연 시간의 최대치가 고정되어 있고 패킷을 유실하지 않는 네트워크에 기댈 수 있다면 분산 시스템은 훨씬 더 단순해집니다.

    왜 하드웨어 수준에서 이 문제를 해결하고 네트워크를 신뢰성 있게 만들지 않을까요?

    이 질문에 답하기 위해서는 전통적인 고정 회산 전화 네트워크를 비교해 보면 좋습니다.

    전화 네트워크는 극단적인 신뢰성을 가집니다.

    컴퓨터에서도 비슷한 신뢰성과 예측 가능성이 있으면 좋지 않을까요?

    전화 네트워크는 회선이 만들어지고 통화하는 순간 보장된 양의 대역폭이 할당됩니다.

    또한 통화가 끝날 때까지 회선은 유지됩니다.

    이런 네트워크 종류는 동기식입니다. 데이터가 여러 라우터를 거쳐도 큐 대기 문제를 겪지 않고 홉에 통화당 16비트의 공간이 이미 할당되어 있습니다.

    큐 대기가 없어서 네트워크 종단 지연 시간의 최대치가 고정되어 있고, 이를 제한 있는 지연이라고 합니다.

     

    그러면 네트워크 지연을 예측 가능하게 만들 수 없을까?

    TCP 연결의 패킷은 네트워크 대역폭을 기회주의적으로 사용합니다.

    TCP에 가변 크기의 데이터 블록을 보내면 짧은 시간 안에 전송하려고 할 것입니다.

    TCP 연결이 쉬고 있는 동안은 어떤 대역폭도 사용하지 않습니다(Keep-alive 패킷은 제외)

    하지만 이더넷과 IP는 큐 대기의 영향을 받아 네트워크에 기약 없는 지연이 있습니다.

     

    그러면 왜 네트워크와 인터넷은 패킷 교환을 사용할까요?

    답은 순간적으로 몰리는 트래픽에 최적화되었기 때문입니다.

    회선은 통화하는 동안 보내는 초당 비트 개수가 상당히 고정되어 있는 음성과 영상 통화에 적합합니다.

    하지만 웹 페이지 요청, 이메일 전송, 파일 전송은 특별한 대역폭 요구사항이 없고 가능한 한 빨리 완료되기를 바랍니다.

    회신을 통해 파일을 전송하고 싶다면 대역폭 할당을 추정해야 합니다.

    이 과정에서 추정치가 낮으면 전송이 불필요하게 느려지고, 너무 높으면 회선이 대역폭 할당을 보장하지 못해서 생성이 허용되지 않습니다.

    반대로 TCP는 가용한 네트워크 용량에 맞춰 전송률을 동적으로 조절합니다.

    회선 교환과 패킷을 모두 지원하는 하이브리드 네트워크를 만들려는 시도도 있었습니다.

    회선 교환과 패킷은 네트워크 자원의 정적 분할, 동적 분할을 차이로 네트워크의 변동이 큰 지연은 자연법칙이 아니라 단지 트레이드오프의 결과일 뿐입니다.

     

    신뢰성 없는 시계

    애플리케이션은 다음과 같은 질문에 대답하기 위해 다양한 방식으로 시계에 의존합니다.

    -       이 요청은 타임아웃되었나?

    -       이 서비스의 99 분위 응답 시간은 어떻게 되나?

    -       이 서비스는 지난 5분 동안 평균 초당 몇 개의 질의를 처리했나?

    -       사용자가 우리 사이트에서 시간을 얼마나 보냈나?

    -       이 기사가 언제 게시되었나?

    -       며칠 몇 시에 미리 알림 이메일을 보내야 하나?

    -       이 캐시 항목은 언제 만료되나?

    -       로그 파일에 남은 이 오류 메시지의 타임스탬프는 무엇인가?

     

    1~4는 지속 시간을 측정하는 반면 5~8은 시점에 발생한 이벤트를 기술합니다.

    분산 시스템에서는 통신이 즉각적이지 않아 시간을 다루기 까다롭습니다.

    네트워크에 있는 개별 장비는 자신의 시계를 가지고 있습니다.

    보통 네트워크 시간 프로토콜로 서버 그룹에서 보고한 시간에 따라 컴퓨터 시계를 조정함으로 시간을 어느 정도 동기화할 수 있습니다.

     

    단조 시계 대 일 기준 시계

    현재 컴퓨터는 최소 두 가지 종류의 시계를 갖고 있습니다.

    일 기준 시계와 단조 시계입니다.

    일 기준 시계는 직관적으로 시계에 기대는 일을 합니다.

    어떤 달력에 따라 현재 날짜와 시간을 반환합니다.

    예를 들어 리눅스의 clock_gettime과 자바의 System.currentTiemMillis()는 에포크 이래로 흐른 초 수를 반환합니다. 에포크는 그레고리력에 따르면 UTC 197011일 자정을 가리킵니다.

     

    단조 시계는 타임아웃이나 서비스 응답 시간 같은 지속 시간을 재는 데 적합합니다.

    자바의 System.nanoTime()이 존재하고 시계의 절대적인 값은 의미가 없습니다.

    컴퓨터가 시작한 이래 흐른 나노초 일 수도 있고 비슷한 어떤 것일 수 있습니다.

    특히 두 대의 다른 컴퓨터에서 나온 단조 시계 값을 비교하는 것은 의미가 없습니다.

     

    하드웨어 시계와 NTP의 몇 가지 예시

    컴퓨터의 시계 드리프트는 장비의 온도에 따라 변합니다.

    구글은 자신들의 서버에 200ppm의 시계 드리프트가 있다고 가정합니다.

     

    컴퓨터 시계와 NTP(네트워크 시간 프로토콜) 서버와 차이가 너무 많이 나면 로컬 시계가 강제로 리셋될 수 있습니다. 리셋 전 후에 시간을 관찰한 애플리케이션은 시간이 거꾸로 흐르거나 갑자기 앞으로 뛰는 것을 볼지도 모릅니다.

     

    뜻하지 않게 노드와 NTP 서버 사이가 방화벽으로 막히면 잘못된 설정이 얼마 동안 알려지지 않을 수도 있습니다.

     

    모바일 장치에서 소프트웨어를 실행하면 아마도 그 장치의 시계를 믿을 수 없습니다.

    어떤 사용자들은 하드웨어의 시간을 고의로 과거나 미래로 설정합니다.

     

    동기화된 시계에 의존하기

    시계는 간단하고 사용하기 쉬워 보이지만 놀랄 만한 수의 함정이 존재합니다.

    하루는 정확히 86,400초가 아닐 수 있고, 시계가 거꾸로 갈 수 있으며, 노드의 시간이 다른 노드의 시간과 차이가 많이 날 수도 있습니다.

    이때 시계가 잘못된다는 것을 눈치채지 못할 수 있습니다.

    소프트웨어의 어떤 부분이 정확히 동기화된 시계에 의존한다면 그 결과는 극적인 고장보다는 조용하고 미묘한 데이터 손실이 발생할 가능성이 높습니다.

    따라서 동기화된 시계가 필요한 소프트웨어를 사용한다면 필수적으로 모든 장비 사이의 시계 차이를 조심스럽게 모니터링해야 합니다.

     

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

    시계의 의존하고 싶은 유혹이 들지만 위험함 특정 상황 하나를 고려해보겠습니다.

     

    다중 리더 복제를 쓰는 데이터베이스에서 일 기준 시간을 위험하게 사용하는 예입니다.

    클라이언트 A가 노드 1x=1을 씁니다.

    이 쓰기는 노드 3으로 복제됩니다.

    클라이언트 B가 노드 3에 잇는 x를 증가시킵니다. (이제 x=2)

    마지막으로 두 쓰기는 노드 2로 복제됩니다.

     

    이때 클라이언트 B는 클라이언트 A보다 인과성 측면에서는 나중에 쓰지만 B가 쓸 때 사용하는 타임스탬프가 더 이를 수 있습니다.

    따라서 클라이언트 B의 증가 연산은 손실되고 A의 연산만 반영됩니다.

    최종 쓰기 증리(LWW)라 불리며 이전 챕터에서 알아보았습니다.

     

    어떻게 잘못된 순서화가 발생하지 않도록 NTP 동기화를 할 수 있을까요?

    아마도 불가능합니다.

     

    이벤트 순서화를 위해서는 논리적 시계를 통해 해결합니다.

    논리적 시계는 일 기준 시간이나 경과한 초 수를 측정하지 않고 이벤트의 상대적인 순서만 측정합니다. (이후 순서화 보장파트에서 자세히 다룸)

     

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

    어떤 시스템은 현재 시간의 해당 분의 10.3초와 10.5초 사이에 있다고 95% 확신할 순 있으나 그 보다 더 정확히는 알지 못합니다.

     

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

    흔한 스냅숏 격리 구현은 단조 증가하는 트랜잭션 ID가 필요합니다.

    하지만 데이터베이스 여러 장비에 분산되어 있다면 전역 단조 증가 트랜잭션 ID를 생성하기 어렵습니다.

    만약 시계의 타임스탬프를 트랜잭션 ID로 사용한다면 동기화가 잘되어 있어야 합니다.

    , 시계 정확도에 대한 불확실성이 문제입니다.

     

    프로세스 중단

    분산 시스템에서 시계를 위험하게 사용하는 다른 예입니다.

    파티션마다 리더가 하나씩 존재할 때 노드가 여전히 리더인지 어떻게 알 수 있을까요?

    임차권을 얻어 특정 시점에 오직 하나의 리더만 갖도록 합니다.(타임아웃이 있는 잠금과 유사)

    이후 이 임차권을 주기적으로 갱신하고 만료되었다면 다른 노드가 리더 역할을 넘겨받습니다.

     

    이때 임차권 만료 시간이 다른 장비에서 설정되었는데 로컬 시스템과 비교하는 경우가 있을 수 있습니다.

    또한 시간을 확인하는 시점과 요청이 처리되는 시점 사이에 매우 짧은 시간이 흐른다고 가정합니다.

    하지만 예상치 못한 중단이 존재하게 되고 이 순간 다른 노드가 리더의 역할을 넘겨받는다면 안전하지 않은 일을 할 수 있습니다.

     

    예를 들어 다음과 같은 상황이 존재할 수 있습니다.

    -  Gcstop-the-world가 때로는 몇 분 동안 지속될 수 있다.

    -  가상 환경에서 가장 장비는 suspend, resume 된다.된다. 이때 프로세스 실행 중에 이런 일이 임의의 시간 동안 지속될 수 있습니다.

    -  노트북 같은 최종 사용자 기기에서도 사용자가 노트북 덮개를 닫은 경우 실행이 제멋대로 suspend, resume 될 수 있습니다.

     

    단일 장비에서는 다중 스레드 코드를 작성할 때 thread-safe 하게 만들 수 있는 많은 도구들이 존재합니다.

    하지만 분산 시스템의 노드에서는 공유 메모리가 없고 단지 신뢰성 없는 네트워크를 통해 메시지를 보낼 수밖에 없습니다.

    이때 외부 시계는 계속 움직이지만 분산 시스템의 노드는 어느 시점에 실행이 상당한 시간 동안 멈출 수 있다고 가정해야 합니다.

     

    응답 시간 보장

    위의 예시와 같이 스레드와 프로세스는 기약 없는 시간동안 중단될 수 있습니다.

    어떤 소프트웨어는 명시된 시간 안에 응답하는데 실패하면 심각한 손상을 유발할 수 있습니다.

    항공기, 로켓, 로봇, 자동차 등등 물리적 물체를 제어하는 센서 입력에 빠르고 예측 가능하게 응답해야 합니다.

    이때는 응답을 위한 데드라인이 명시되고 만족시키지 못하면 전체 시스템의 장애를 유발할 수 있습니다.

    사실 실시간은 고성능과 동일하지 않고 실시간 시스템은 무엇보다도 제때에 응답하는 것을 우선시해야 하므로 처리량이 더 낮을 수 있습니다. (전화 회선과 TCP와 같이)

     

    GC의 영향 제한하기

    수명이 짧은 객체만 GC를 사용하고 수명이 긴 객체는 GC보다는 주기적으로 프로세스를 재시작하는 방법으로 GC의 영향을 제한할 수 있습니다.

    순회식 업그레이드를 할 때처럼 계획된 재시작을 하기 전에 트래픽을 재시작하려는 노드에서 다른 노드로 옮길 수 있습니다.

     

    진실은 다수결로 결정된다

    어떤 노드가 진짜로 죽었는지 확인하기 위해서 노드의 과반수 이상을 정족수로 사용합니다.

    하지만 실제로 노드는 GC 중이거나 네트워크 문제로 응답, 요청이 유실되었을 수 있습니다.

     

     

    펜싱 토큰

    임차권을 가진 노드가 자신이 선택된 자라고 잘못 믿고 있는 노드가 나머지 시스템을 방해할 수 없도록 보장해야 합니다.

                                        

    이 목적을 달성하는 단순한 기법으로 펜싱이 있습니다.

    잠금 서버가 임차권을 승인할 때마다 펜싱 토큰을 반환합니다.

    펜싱 토큰은 잠금이 승인될 때마다 증가하는 숫자입니다.

    이제 쓰기 요청을 보낼 때 자신의 펜싱 토큰을 요구하게 되고 이미 더 큰 펜싱 토큰(34번 토큰)이 처리되었다면 작은 펜싱 토큰(33번 토큰)을 거부합니다.

     

    보통 주키퍼를 사용하면 트랜잭션 IDzxid나 노드 버전 cversion을 펜싱 토큰으로 사용할 수 있습니다.

    이들은 단조 증가가 보장되므로 필요한 속성을 지닙니다.

     

    비잔틴 장군 문제

    하지만 누군가 노드가 고의로 시스템의 보장을 무너뜨리려 한다면 가짜 펜싱 토큰을 포함한 메시지를 보내면 끝입니다.

     

    여기서는 노드가 진실되었다고 가정하지만 노드가 거짓말을 할지도 모른다는 위험이 있다면 더 어려워집니다.

     

    이를 비잔틴 장군 문제라고 합니다.

     

    악의적인 공격자가 네트워크를 방해하더라도 시스템이 잘 동작하는 경우 비잔틴 내결함성을 지닌다고 합니다.

     

    이런 관심사는 특정 환경에서 유의미합니다.

    -  항공 우주 산업에서 컴퓨터나 메모리 CPU 레지스터에 저장된 데이터가 방사선에 오염되어 다른 노드에게 전혀 예측할 수 없는 방식으로 반응하는 경우 시스템에서 장애가 나면 대규모의 사람들이 죽는 일이 발생할 수 있기 때문에 비잔틴 결함을 견딜 수 있어야 합니다.

    -  비트코인처럼 여러 조직이 참여하는 시스템에서 다른 노드의 메시지를 그냥 믿는 것은 안전하지 않습니다.

     

    하지만 대부분의 서버 측 데이터 시스템에서 비잔틴 내결함성 솔루션을 배치하는 것은 비용이 커서 실용적이지 않습니다.

    따라서 전통적인 인증, 접근 제어, 암호화, 방화벽등으로 여전히 공격자로부터 보호하는 수단을 사용합니다.

     

    정리

    분산 시스템에서 나타날 수 있는 광범위한 문제를 설명했습니다.

    -  네트워크는 언제나 지연될 수 있으며 심지어 응답, 요청이 손실될 수 있다.

    -  노드의 시계는 다른 노드와 심하게 맞지 않을 수 있으며 심지어 시간이 과거로 갈 수도 있다.

    -  프로세스는 실행 중 어느 시점에 상당히 멈출 수 있고 죽었다고 선언되었지만 다시 살아났을 때 자신은 죽었다는 사실을 인지하지 못한다.

     

    신뢰성 없는 네트워크에서 다른 노드의 도움을 요청하고 동의할 정족수를 이루려고 시도하는 프로토콜이 필요합니다.

    일반적으로 단일 컴퓨터가 할 수 있는 일이라면 그냥 단일 컴퓨터는 사용하는 게 좋습니다.

     

    이번 장에서는 모든 문제에 대한 암울한 관점을 보여주었으며, 다음 장에서는 분산 시스템의 모든 문제에 대처하도록 설계된 알고리즘에 대해 알아봅니다.

     

    'CS > 데이터 중심 애플리케이션 설계 요약' 카테고리의 다른 글

    데이터 중심 애플리케이션 설계 10장 - 일괄 처리  (0) 2023.01.11
    9장 - 일관성과 합의  (0) 2023.01.09
    7장 - 트랜잭션  (0) 2022.12.15
    6장 - 파티셔닝  (1) 2022.12.10
    5장 - 복제  (0) 2022.12.02

    댓글

Designed by Tistory.