7P by GN⁺ | ★ favorite | 댓글 1개
  • 분산 시스템의 핵심 난점은 지연 시간보다 부분 실패이며, 한 머신에서 단순한 예외 처리도 여러 머신에서는 프로토콜 설계의 일부가 됨
  • 견고한 분산 시스템은 실제 여러 머신, 데이터센터 네트워크 조건, 큰 데이터셋에서만 드러나는 버그가 있어 단일 머신 시스템보다 비용이 큼
  • 운영 가능한 시스템을 만들려면 조율을 줄이고 독립성을 높이며, 백프레셔·부분 가용성·메트릭·백분위 지표를 기본 도구로 삼아야 함
  • 기능 플래그, ID 공간 설계, 데이터 지역성, 서비스 추출은 마이그레이션과 확장 과정에서 실패 범위와 조율 비용을 낮추는 실무 기법임
  • CAP 정리는 시스템 구축의 출발점보다 설계 비판 도구에 가깝고, 분산 시스템에서는 기술 문제만큼 팀과 조직 사이의 사회적 조율도 어려움

분산 시스템을 처음 배울 때 놓치기 쉬운 것

  • 분산 시스템 엔지니어의 많은 교훈은 프로덕션 트래픽에서 겪은 실수와 운영 상처에서 나오지만, 초보 엔지니어에게는 더 직접적이고 실행 가능한 맥락이 필요함
  • Fallacies of Distributed ComputingCAP theorem은 자기 학습에 도움이 되지만, 경험이 적은 엔지니어가 바로 움직이기에는 추상적임
  • 분산 시스템은 더 많은 머신과 자본이 필요하므로 더 많은 팀과 큰 조직이 얽히기 쉬움
    • 사회적 문제는 소프트웨어 개발자의 일에서 대체로 가장 어려운 부분이며, 분산 시스템 개발에서는 특히 두드러질 수 있음
    • 기술 해법보다 사회적 해법이 더 효율적이고 만족스러울 때도 있지만, 배경·교육·경험은 엔지니어를 기술 해법 쪽으로 기울게 함

실패와 비용을 전제로 설계하기

  • 분산 시스템은 자주 실패하고, 더 나쁘게는 부분적으로 실패
    • 단일 프로세스의 mutex unlock 실패는 프로세스를 크래시시키면 될 수 있지만, 분산 mutex의 unlock 실패는 락 프로토콜 안에 포함돼야 함
    • “두 머신에 모두 write를 보내면 된다”거나 “성공할 때까지 재시도하면 된다”는 접근은 한쪽 write만 성공하고 다른 쪽은 실패하는 상황을 충분히 다루지 못함
    • 스위치 장애, GC pause로 인한 리더 실종, 성공한 것처럼 보이는 socket write의 원격 실패, 느린 디스크 하나가 클러스터 통신 프로토콜 전체를 늦추는 상황이 생김
  • 견고한 분산 솔루션은 단일 머신 솔루션보다 더 많은 비용이 듦
    • 여러 머신에서만 발생하는 실패가 있고, VM과 클라우드는 비용을 낮추지만 이미 가진 컴퓨터 한 대에서 설계·구현·테스트하는 것만큼 싸지는 않음
    • 공유 머신에 담기 어려운 데이터셋 크기나 데이터센터 네트워크 조건에서만 드러나는 실패는 실제 분산 환경이 필요함
    • 시뮬레이션은 유용하지만, 분산 시스템 버그를 모두 대체하지는 못함
  • 견고한 오픈소스 분산 시스템은 견고한 단일 머신 시스템보다 훨씬 드묾
    • 많은 머신을 오래 실행하는 비용은 오픈소스 커뮤니티에 부담이 됨
    • 취미 개발자와 딜레탕트는 이미 가진 머신과 여가 시간으로 오픈소스를 만들기 때문에, 여러 머신을 띄우고 유지하며 비용을 지불할 개발자를 찾기 어려움
    • 기업 엔지니어가 일부 공백을 메우지만, 그 조직의 우선순위가 사용자의 조직 우선순위와 맞지 않을 수 있음

조율을 줄이고 장애를 흡수하기

  • 머신 간 조율은 가능한 피해야 함
    • 수평 확장성의 핵심은 머신들이 서로 통신하고 합의하는 일을 최소화할 수 있을 만큼 독립적으로 데이터를 배치하는 것임
    • 두 머신이 어떤 것에 합의해야 할 때마다 서비스 구현은 더 어려워짐
    • 정보 전달 속도에는 상한이 있고, 네트워크 통신은 생각보다 불안정하며, 합의에 대한 직관은 틀릴 수 있음
    • Two Generals, Byzantine Generals, Paxos 구현의 어려움을 이해하는 것이 도움이 됨
  • 문제가 메모리에 들어가면 분산 시스템 엔지니어에게는 대체로 사소한 문제에 가까움
    • 데이터가 포인터 몇 번이 아니라 스위치 몇 개 건너편에 있을 때 빠르게 처리하기가 더 어려움
    • 단일 머신 알고리듬과 구현은 풍부하지만, 분산 시스템을 위한 문헌과 구현은 훨씬 적음
  • “느리다”는 가장 디버깅하기 어려운 문제임
    • 사용자 요청에 관여하는 여러 시스템 중 하나 이상이 느릴 수도 있고, 여러 머신을 지나는 변환 파이프라인 일부가 느릴 수도 있음
    • 문제 설명 자체가 결함 위치에 대한 단서를 거의 주지 않으며, 평소 보는 그래프에 드러나지 않는 부분 실패가 숨어 있을 수 있음
    • 성능 저하가 아주 명확해지기 전까지 시간·돈·도구 같은 리소스를 충분히 받기 어려움
    • DapperZipkin은 이런 이유로 만들어짐
  • 시스템 전반에 백프레셔를 구현해야 함
    • 백프레셔는 서비스하는 시스템이 요청 시스템에 실패를 신호하고, 요청 시스템이 그 실패를 처리해 자신과 상대 시스템의 과부하를 막는 방식임
    • 과부하나 시스템 실패 동안 리소스 사용을 제한하는 것이 견고한 분산 시스템의 기본 구성 요소임
    • 구현은 리소스가 제한되거나 실패가 발생했을 때 새 메시지를 버리거나 사용자에게 에러를 돌려주는 방식이 많으며, 두 경우 모두 메트릭을 증가시켜야 함
    • 다른 시스템 연결과 요청에는 타임아웃과 지수 백오프가 필수임
    • 백프레셔가 없으면 연쇄 실패나 의도치 않은 메시지 손실이 발생하기 쉬움
  • 부분 가용성을 제공하는 방법을 찾아야 함
    • 부분 가용성은 시스템 일부가 실패해도 일부 결과를 반환할 수 있는 능력임
    • 검색 시스템은 결과 품질과 사용자를 기다리게 하는 시간 사이에서 절충하며, 제한 시간이 지나면 수집한 결과만 반환할 수 있음
    • 프라이빗 메시징에서는 일부 사용자에게만 장애가 나는 편이 모든 사용자에게 일부 메시지가 사라지는 것보다 나을 수 있음
    • 프라이빗 메시징 장애가 공개 이미지 업로드 같은 무관한 기능에 영향을 주지 않게 실패 도메인을 얼마나 분리할지 결정해야 함

관측, 용량, 배포를 운영 도구로 삼기

  • 메트릭은 프로덕션에서 시스템이 실제로 무엇을 하는지 알기 위한 필수 수단
    • 지연 시간 백분위, 특정 동작의 증가 카운터, 변화율 같은 메트릭은 믿고 있는 시스템 동작과 실제 동작 사이의 간극을 줄임
    • 20일째의 시스템 동작이 15일째와 어떻게 다른지 아는 것이 성공적인 엔지니어링과 실패한 주술을 가름함
    • 메트릭은 문제와 동작을 이해하는 데 필요하지만, 다음에 무엇을 해야 하는지 알려주기에는 충분하지 않음
  • 로그 파일은 유용하지만 거짓말을 하기 쉬움
    • 몇몇 에러 클래스 로그가 파일 공간의 큰 비중을 차지해도 실제 요청 비율은 낮을 수 있음
    • 성공 로그는 대부분 중복이고 디스크를 터뜨릴 수 있으며, 엔지니어는 어떤 에러 로그가 유용한지 자주 잘못 추측함
    • 로그는 코드를 본 적 없는 사람이 읽는다고 가정하고 남기는 편이 좋음
    • 로그에서 본 이상한 점을 메트릭으로 확인하지 않고 과대평가하면 장애가 길어질 수 있음
  • 평균보다 백분위를 써야 함
    • 50th, 99th, 99.9th, 99.99th 백분위는 대부분의 분산 시스템에서 평균보다 정확하고 유용함
    • 평균은 평가 지표가 종 모양 분포를 따른다고 가정하지만, 엔지니어가 중요하게 보는 지표 중 그런 경우는 매우 적음
    • 분산 시스템 지연 시간이 종 모양 분포를 따르지 않으면 평균 지연 시간은 잘못된 의사결정과 이해로 이어짐
  • 용량 추정 능력을 익혀야 함
    • 작업에 필요한 머신 수를 아는 것은 오래가는 시스템과 3개월 만에 교체해야 하는 시스템을 가름함
    • 2012년 말 기준 일반적인 머신은 24GB 메모리를 갖고, OS에 4~5GB, 요청 처리에 최소 몇 GB가 필요하며, tweet id는 8바이트라는 식의 대략 계산이 필요함
    • Jeff Dean의 Numbers Everyone Should Know는 기대치를 잡는 데 도움이 됨
  • 기능 플래그는 인프라를 롤아웃하는 방법임
    • 기능 플래그는 프론트엔드 A/B 테스트뿐 아니라 인프라 교체에도 강력하게 쓰임
    • “big cutover” 방식은 늦게 발견된 버그 때문에 롤백을 강요받으며 실패한 프로젝트가 많음
    • 단일 데이터베이스에서 새 저장소를 감춘 서비스로 옮길 때, 새 서비스로의 write 병렬 전송을 천천히 늘리고, 백필 완료 후 read를 사용자 응답에 쓰지 않는 상태로 시험하고, 구·신 시스템 read 비교를 수행한 뒤 실제 read를 점진적으로 늘릴 수 있음
    • 문제가 생기면 플래그 값을 낮추거나 0으로 즉시 되돌릴 수 있고, 트래픽 양을 조절하며 디버깅과 실험을 할 수 있음
    • 기능 플래그는 코드 안의 지역 복잡성을 전역 단순성과 회복력으로 교환하는 절충임
    • 여러 버전의 인프라와 데이터가 예외가 아니라 일반적이라는 사실을 받아들여야 함

데이터 모델과 경계 설계

  • ID 공간은 시스템의 형태를 결정함
    • 데이터 하나에 도달하는 데 필요한 ID가 많을수록 데이터를 파티셔닝할 선택지가 많아짐
    • 데이터 하나에 필요한 ID가 적을수록 시스템 출력을 소비하기 쉬움
    • Twitter API v1은 tweet을 단일 64비트 숫자 ID로 조회·생성·삭제했으며, 이 ID는 다른 데이터와 연결되지 않음
    • tweet 수가 늘어나면 같은 사용자의 tweet을 같은 머신에 저장해 사용자 tweet timeline과 구독 timeline을 효율적으로 구성할 수 있지만, 공개 API가 tweet id만으로 모든 tweet을 주소 지정하게 만들었기 때문에 user가 어떤 tweet id를 소유하는지 아는 조회 서비스가 필요해짐
    • 대안으로 tweet 조회에 user id를 요구하거나, tweet id 자체에 user id를 포함할 수 있지만, 후자는 tweet id가 더 이상 k-sortable numeric이 아니게 되는 비용이 있음
    • ID 구조는 비공개 데이터 익명성 해제, 예상치 못한 크롤링, 자동 증가 ID 문제, Insecure Direct Object References 같은 공격에 영향을 줄 수 있음
  • 데이터 지역성을 활용해야 함
    • 데이터 처리와 캐싱을 영구 저장소 가까이에 둘수록 처리 효율이 높고 캐시 일관성과 속도를 유지하기 쉬움
    • 네트워크는 포인터 역참조나 fread(3)보다 실패와 지연 시간이 많음
    • 지역성은 공간뿐 아니라 시간에도 적용됨
    • 여러 사용자가 거의 동시에 같은 비싼 요청을 하면 하나로 합칠 수 있고, 같은 종류의 데이터 요청이 시간적으로 가깝게 발생하면 더 큰 요청 하나로 묶을 수 있음
    • 이런 방식은 통신 오버헤드를 낮추고 장애 관리를 쉽게 만듦
  • 캐시된 데이터를 영구 저장소에 다시 쓰는 것은 나쁨
    • 특히 분산 시스템 경험이 적은 사람이 처음 설계한 시스템에서 이런 결함이 나타날 수 있음
    • “Russian-doll caching”을 말하는 구현에서는 눈에 띄는 버그를 만날 가능성이 큼
    • 흔한 증상은 사용자 정보, 예를 들어 screenname, email, hashed password가 이전 값으로 되돌아가는 현상임
  • 컴퓨터는 생각보다 더 많은 일을 할 수 있음
    • 2012년 말의 가벼운 웹 서버도 6개 이상 프로세서, 24GB 메모리, 매우 큰 디스크 공간을 갖고 있음
    • 현대 언어 런타임의 비교적 복잡한 CRUD 애플리케이션도 단일 머신에서 수천 requests per second를 수백 밀리초 안에 처리할 수 있음
    • 대부분의 경우 머신당 수백 requests per second는 운영 능력 관점에서 자랑할 만한 수치가 아님
    • 애플리케이션을 프로파일링하고 측정에 기반한 효율화를 도입하면 더 높은 성능을 얻기 어렵지 않음
  • CAP 정리는 시스템 구축법이 아니라 설계 비판 도구로 써야 함
    • CAP 정리는 너무 일반적이고 가능한 해법 공간이 너무 넓어, 첫 원리로 삼아 작동하는 시스템을 도출하기에 적합하지 않음
    • 시스템 설계를 놓고 CAP이 하위 시스템에 부과하는 제약을 반복적으로 검토하면 더 나은 설계에 도달할 수 있음
    • C, A, P 중에서 CA는 선택할 수 없음
  • 서비스를 추출하면 캡슐화와 배포의 장점을 얻을 수 있음
    • 여기서 서비스는 저장 시스템보다 높은 수준의 로직을 포함하고 보통 요청-응답 스타일 API를 가진 분산 시스템을 뜻함
    • 특정 코드가 시스템 내부가 아니라 별도 서비스에 있으면 변경이 쉬울지 계속 살펴야 함
    • 추출된 서비스는 라이브러리처럼 캡슐화를 제공하면서도, 클라이언트 시스템의 라이브러리를 업그레이드하는 것보다 변경 배포를 빠르고 쉽게 만들 수 있음
    • 작은 서비스는 코드와 운영 의존성이 적고, 엄격한 경계가 라이브러리에서 허용되는 지름길을 줄임
    • 여러 클라이언트 시스템이 있을 때 공유 라이브러리는 각 클라이언트 배포를 조율해야 하며, 데이터 손상이 배포 순서에 따라 생길 수 있으면 더 어려워짐
    • 클라이언트 시스템 유지자가 다르면 우선순위가 맞지 않아 업그레이드 동의를 얻는 사회적 조율 비용도 커짐
    • 대표적인 사용 사례는 변경 중인 저장 계층을 더 편리하고 표면적이 작은 API를 가진 서비스 뒤에 숨기는 것임

댓글과 토론

Hacker News 의견들
  • 글에서 CALM(논리적 단조성으로서의 일관성) 을 꼭 다뤘어야 함. CAP보다 훨씬 이해하기 쉽고 더 근본적인 결과이며, 경험이 적은 사람도 매우 견고한 분산 시스템을 만들 수 있게 해줌
    멱등성, CRDT, WAL, Raft는 모두 CALM 원칙의 특수한 경우임
    https://arxiv.org/pdf/1901.01930

    • 그 논문보다 이 글이 6년 먼저 나왔음
    • bloom 저장소를 보니 꽤 정체된 것 같은데, 아직도 작업이 이어지고 있는지 궁금함
  • 여기서 빠졌지만 일반 원칙으로 좋아하는 게 있음. 정확히 한 번 전달은 불가능하다는 점임
    최대 한 번 또는 최소 한 번은 가능하지만, 어떤 실패 방식을 감수할지 선택하고 그에 맞게 설계해야 함

    • 단, 같은 트랜잭션 도메인을 공유하지 않거나 논리적으로 단조롭지 않은 두 분산 시스템 사이에서 그렇다는 뜻임
      클러스터 데이터베이스에서 한 행의 데이터를 다른 행으로 옮기는 일은 가능하고, 이를 메시지가 전달된 것으로 해석할 수도 있음
      전체 시스템이 멱등적이거나, 분산 시스템 전체를 함께 롤백 가능한 단일 단위로 취급할 수 있다면 정확히 한 번 전달을 얻을 수 있음. 즉 도메인 밖의 다른 시스템에 대해 부작용이 없어야 함
      둘 다 일종의 논리적 단조성에 해당함. 멱등성은 알아보기 쉽고, 트랜잭션성도 WAL과 Raft 같은 알고리즘을 통한 단조성에 기반함
      글에서 CALM(논리적 단조성으로서의 일관성) 을 다뤘어야 함. CAP보다 훨씬 이해하기 쉽고 더 근본적인 결과임
      https://arxiv.org/pdf/1901.01930
    • 분산 시스템을 설계할 때 이 개념을 모르는 엔지니어를 커리어 내내 많이 봤고, 정말 강조해도 지나치지 않음
    • 임의 수준의 네트워크 장애가 있는 환경에서는 최소 한 번 전달도 불가능함
    • 이 교훈에서 중요한 부분은 “그리고 사실 그게 필요하지도 않다”는 점임
    • Apache Flink는 체크포인트 메커니즘에 참여하는 데이터 소스와 데이터 싱크와 함께 쓰면 종단 간 정확히 한 번 보장을 제공함
      An Overview of End-to-End Exactly-Once Processing in Apache Flink (with Apache Kafka, too!) — https://flink.apache.org/2018/02/28/an-overview-of-end-to-en...
      Flink's Fault Tolerance Guarantees — https://nightlies.apache.org/flink/flink-docs-release-1.20/d...
  • “문제를 메모리에 올릴 수 있다면 아마 사소한 문제다”의 따름정리로, 메모리에 올릴 수 있는 범위는 생각보다 훨씬 큼
    큰 RAM이 뭔지 안다고 생각했는데, 주요 클라우드들이 SAP HANA용 12TB VM을 제공하기 시작하자 생각이 바뀜
    글에서도 “컴퓨터는 생각보다 많은 일을 할 수 있다”로 아주 짧게 다루긴 하지만, 거기서는 24GB 머신만 이야기함. 2012년 기준이라는 점은 감안해도 당시에도 RAM이 그 10배인 머신은 꽤 있었을 것임

    • 비교적 시니어한 엔지니어들도 이 실수를 꽤 자주 함. 고객당 분석 데이터가 최대 100GB인 SaaS라면, 결국 샤딩한 Postgres면 충분함
  • 같이 일하는 사람 중 가장 유망한 사람들에게 이 문서를 공유함
    Lookout에서 일할 때 Jeff Hodges가 이 에세이를 발표로 공유했고, 마지막에 “엔지니어링이 정치적이지 않은 척하지 말라”는 따름정리를 붙였음
    코드가 스스로 말한다고 생각하는 사람들은, 무언가가 어떻게 만들어질지에 영향을 주고 실제 결과를 얻는 데 중요한 측면을 놓치고 있음
    10년이 지난 지금도 엔지니어링 리더십과 보통 SRE/DevOps의 기본 역량으로 여기는 것들의 교차점을 이렇게 간결히 이해하는 사람은 많지 않음

    • 이 주제에 대해 읽을 만한 다른 좋은 자료가 있다면 궁금함
  • 예전 논의들임
    https://news.ycombinator.com/item?id=5055371
    346 points|jcdavis|12 years ago|42 comments
    https://news.ycombinator.com/item?id=12245909
    386 points|kiyanwang|8 years ago|133 comments

  • 훌륭한 목록이고, 실용적이고 현실적인 설명이 좋음. 유행어도 없고 “마이크로서비스”도 없음
    이 조언 상당수는 단일 머신 시스템에도 적용된다고 봄. 프로그램 간 IPC나 한 프로세스 안의 스레드 조율처럼, 어느 정도 분산된 하위 구성요소가 많을 수 있음
    단일 머신의 통합 메모리라는 개념도 어느 정도는 거짓이지만, 그래도 하드웨어가 “진짜” 분산 환경보다 나은 보장을 제공할 수 있음
    글에서 “분산”과 “단일 머신”을 비교하는 조언 중 상당수는 다중 스레드와 단일 스레드 비교에도 꽤 잘 들어맞음
    또 다른 축으로, 프로그램을 만들어 여러 사람이 실행하게 배포하면 그것도 일종의 “분산” 상황이 됨. 야생에 서로 다른 버전이 존재하고, 호환성과 업그레이드 문제를 신경 써야 하므로 글에서 언급한 기능 플래그도 관련됨
    분산성은 단일 CPU에서 다중 CPU, 촘촘히 연결된 다중 컴퓨터, 전 세계에 분산된 다중 컴퓨터까지 이어지는 스펙트럼에 가깝고, 그 사이에 여러 지점과 여러 차원이 있음

    • “분산 시스템”이라는 말에는 배포 방식에 대한 제약이 들어 있지 않음. 정의에 핵심적인 특성은 서로 다른 제어 흐름이 메시지 전달을 통해 네트워크로 통신한다는 것뿐임
      같은 머신에서 여러 프로세스가 localhost로 통신하는 것도 매우 유명한 분산 시스템 예이고, 실제로 많은 사람이 거기서 분산 시스템을 처음 배움
    • AMD가 각 칩렛마다 다른 메모리 공간을 제공했을 수도 있는, 너무 가까워 더 아쉬운 이웃 우주를 자주 떠올림
      그랬다면 모두가 모든 코드를 아름다운 분산 메모리 MPI 프로그램으로 작성하고 있었을 것임. 거짓 공유도 사라지고, 통신 패턴을 명시적으로 깊이 고민하게 됐을 것임
  • 최근 몇 년 사이 이 글의 저자와 잠깐 함께 일할 기회가 있었음. Jeff는 내가 배운 사람 중 가장 통찰력 있고 긍정적인 사람 중 하나였음
    자신이 겪는 어려움에 대해 놀랄 만큼 솔직했고, 멘토링과 조언에도 기분 좋게 접근 가능했음

  • “분산 시스템은 자주 실패하기 때문에 다르다”에서 핵심은 단순한 실패율이 아니라, 여러 노드로 된 시스템에서의 실패율
    그리고 “분산 시스템 문제”는 네트워크로 연결된 여러 서버에서만 생기지 않음. 서로 관계를 가진 노드들의 집합이라면, 논리적으로 연결된 디스크의 파일이나 서로 다른 입출력 장치의 버퍼도 비슷한 문제를 겪게 됨

    • 맞음. 사실 메모리에 고정된 단일 스레드 프로세스 하나를 넘는 거의 모든 소프트웨어 시스템에서 이런 부류의 문제가 생길 수 있고 실제로 생김
      일부 올드타이머들은 이런 문제를 완화하며 생기는 과도한 복잡성을 비웃고, 그냥 단일 서버에서 소프트웨어를 돌리면 훨씬 단순해진다고 불평함
      현실적으로는 AS/400이나 VAXft 시절에도 그 말은 거의 맞지 않았고, 더 혼란스러운 다중 사용자·다중 프로세스 Unix 세계에는 더더욱 적용되지 않았음
  • “문제를 메모리에 올릴 수 있다면 아마 사소한 문제다”는 일부 분산 시스템 엔지니어들 사이의 흔한 오류임
    전혀 사소하지 않고, 다만 다뤄야 할 문제가 상보적인 영역으로 옮겨갈 뿐임
    이 오류는 제대로 최적화하면 단일 머신에서 할 수 있는 일을 100대 클러스터로 처리해야 하는 상황으로 쉽게 이어짐