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

sjhello·2022년 11월 15일
0
post-thumbnail

이 책에 대하여

Designing Data-Intensive Applications


이 책은 OReilly 에서 출간한 Designing Data-Intensive Applitions의 한국어 번역본인 데이터 중심 애플리케이션 설계 를 읽고 정리한 포스팅입니다.

주위에서 추천이 많은 책이기도 하고 내용도 좋은 명저이기도 해서 정리도 할겸 당분간 이 책의 내용을 정리하여 포스팅할 생각입니다. 블로그 주인이 책을 읽고 정리한 글이기 때문에 잘못된 내용이나 부족한 내용이 있을 수 있습니다. 이에 대해서는 댓글로 지적해주시면 좋을 것 같습니다.


신뢰성

SW에 대해 신뢰성을 갖는다라는 말은 무엇일까요? 책에서는 이렇게 말 하고 있습니다.

  • 사용자가 기대한 기능을 수행한다
  • 허가되지 않은 접근, 오남용에 대해 방지한다
  • 사용자의 실수나 예상치 못한 오류를 허용한다
  • 시스템 성능은 예상된 부하와 데이터 양에서 필수적인 사용 사례를 충분히 만족한다

즉 신뢰성은 무언가 잘못되더라도 올바르게 동작한다는 것을 의미한다.

결함

잘못될 수 있는 일을 결함(fault)이라 부릅니다. 그리고 결함에 대해서 대처할 수 있는 성질을 내결함성(fault-tolerant) 혹은 탄력성(resilient)라고 한다

장애(failure)에 대해서도 얘기가 나오는데, 장애와 결함은 다르다.

장애는 시스템이 멈춰 사용자에게 서비스를 제공하지 못하는 경우를 의미하고, 결함은 사양에서 벗어난 시스템의 한 구성 요소이다.

중요한것은 결함 확률을 0으로 줄이는 건 불가능하지만, 결함으로 인해서 장애가 발생하지 않게해야한다. 다시 말해 시스템은 내결함성을 가지는 구조로 설계하는 것이 좋다.

테스트도 중요하다. 고의적으로 결함을 일으켜(예를들어 갑자기 네트워크를 끊어버린다던가..) 내결함성 시스템을 지속적으로 훈련하고 테스트하여 어느 부분에서 문제가 있는지 확인하고 발견된 부분을 수정하는 식으로 내결함성을 가지게 노력한다.

카오스 엔지니어링

결함 유형

  • 하드웨어 결함
    • 디스크 고장, 램에 결함 발생, 정전과 같은 결함
    • 하드웨어로 인한 결함을 줄이는 방법
      • 하드웨어 구성요소에 중복을 추가하기
        • 디스크의 경우 RAID를 구성하기
        • 서버는 이중전원 디바이스와 핫 스왑이 가능한 CPU를
        • 즉 고장난 하드웨어와 똑같은 하드웨어를 대비시켜 놓음으로써 서비스를 지속할 수 있게 하는 것
      • 최근에는 데이터 양이 증가하고 애플리케이션이 하는 일이 많아지는 추세 → 따라서 많은 수의 장비를 사용하게 됬고 이에 따라서 하드웨어 결함율도 증가하게 됬다.
        • 일부 클라우드 플랫폼의 경우(ex> AWS) 신뢰성보다는 유연성, 탄력성을 우선적으로 처리하게끔 되어 있다.
        • 따라서 소프트웨어 내결함성 기술을 사용하거나, 하드웨어의 중복을 통해 전체 장비의 장애에 대해서 견딜 수 있는 시스템으로 점점 옮겨가고 있다.
  • 소프트웨어 결함
    • 무작위적이고 독립적으로 발생하는 하드웨어 오류와는 다르게 노드간의 상관관계 때문에 시스템 오류를 더 많이 유발한다(체계적 오류(systematic error))
      • 잘못된 입력으로 인해 모든 애플리케이션이 죽는 경우(ex> 리눅스 커널의 윤초)
      • 시스템의 공유자원(cpu, mem, disk)를 과도하게 사용하는 일부 프로세스
      • 시스템이 느려져 반응이 없거나, 잘못된 응답을 하는 서비스
      • 연쇄 장애: 하나의 구성요소에서 작은 결함이 발생하여 그 결함이 다른 구성요소에 영향을 주는 경우
    • 이러한 결함은 특정 상황에 의해 발생하기 전까지 나타나지 않는다
    • 해결책
      • 신속한 해결책은 없다
      • 시스템의 가정과 상호작용에 대해서 주의깊게 생각하기
      • 빈틈없는 테스트
      • 프로세스 격리
      • 죽은 프로세스 재시작
      • 모니터링
  • 인적오류
    • 대규모 인터넷 서비스에 대한 연구에 따르면 운영자의 설정 오류가 시스템 중단의 주요 원인인 반면 하드웨어 결함은 중단 원인의 10~25%이다
      • 사람은 미덥지 못하다
    • 그럼에도 시스템을 어떻게 하면 신뢰성있게 할 수 있는지?
      • 오류의 가능성을 최소화 하는 방향으로 시스템 설계하기
        • 잘 설계된 추상화
        • API
        • 관리 인터페이스
      • 사람의 실수로 장애가 발생하는 부분 분리하기
        • 사용자에게 영향이 없는 비 프로덕션 샌드박스 제공
      • 테스트
        • 단위 테스트부터 통합 테스트와 수동테스트
        • 모든 수준에서 철저하게 테스트하기
        • 엣지케이스, 코너케이스에 대해서 빈틈없이 테스트
      • 빠른 롤백
      • 성능지표, 오류율 같은 모니터링 및 지표 분석

확장성

시스템이 현재 잘 동작한다고 하여 미래에도 잘 동작한다는 보장은 없다. 예를들어 현재에는 동시 사용자가 1만명이고, 미래에는 동시 사용자가 1000만명으로 늘어났을때 현재 시스템이 미래의 동시 사용자의 요청을 잘 처리할 수 있을까?

사용자가 비약적으로 늘어난 경우 시스템이 처리해야하는 데이터의 양도 그만큼 늘어나게 될 것이고 그만큼 시스템은 처리해야할 계산이 늘어난 거다.

확장성은 이렇게 사용자의 요청이 많아지거나 어떠한 부하에 있어서 이것을 어떻게 대처해야하는지에 대한 선택과 부하를 다루기위해서 어떤 자원을 어떻게 투입해야하는지? 에 대한 것들을 고려하겠다는 의미이다.

부하 기술하기

부하에 대해 기술해야 부하가 늘어나면 어떻게 되지? 에 대해 얘기할 수 있다. 부하 매개변수(load parameter)는 숫자로 표현할 수 있는데 부하 매개변수의 선택은 시스템 설계에 따라 달라진다.

  • 부하 매개변수가 될 수 있는것
    • 웹 서버의 초당 요청 수
    • 데이터베이스의 읽기 대 쓰기 비율
    • 캐시 적중률
    • 대화방의 동시 활성 사용자(Active User)
  • 예시(트위터)
    • 트위터의 주요 두 가지 동작
      • 트윗 작성(write): 초당 4.6k, 피크일때 초당 12k
      • 홈 타임라인(read): 초당 300k
    • 트위터의 확장성 문제
      • 한명의 사용자가 트윗을 작성하면 작성자 팔로워들의 타임라인에 들어가야한다.
        • fan-out(하나의 입력을 처리하는데 필요한 다른 서비스의 요청 수) 발생
      • 해결 전개
        • 홈 타임라인(read)를 요청하면 팔로우하는 모든 사람을 찾아 시간순으로 트윗을 정렬해서 merge한다
          • 읽기 비용이 너무 크다.
        • 각각의 사용자용 홈 타임라인의 캐시를 유지한다.
          • 사용자가 트윗을 작성하면 그 사용자의 팔로워를 찾고 캐시에 새로운 트윗을 삽입한다.
          • 사용자 각각의 캐시를 두었으니 읽기 비용은 줄었다. 하지만 쓰기비용은 늘어났다.
          • 때문에 엄청나게 많은 팔로워를 가지고 있는 사용자에 대해서는 쓰기 비용이 너무 큰 단점이 존재한다.
        • 1번과 2번의 혼합
          • 2번의 방식이 견고하게 구현되었고 그에 따라 혼합형으로 문제를 해결하려 시도한다.
          • 대부분의 사용자는 2번방식을 따르고 팔로워가 엄청나게 많은 사용자는 1번 방식을 따르게 한다.

성능 기술하기

  • 부하를 기술했으니, 부하가 증가할때 어떤일이 일어나는지 2가지 질문으로 살펴 볼 수 있다.
    • 부하를 증가 시켰을때 시스템 자원(CPU, 메모리, 네트워크 대역폭 등)은 변경하지 않고 유지하면 시스템 성능은 어떤 영향을 받는지?
    • 부하를 증가 시켰을때 성능이 변하지 않고 유지되길 원한다면 어떤 자원을 얼만큼 늘려야 하는지?
  • 2가지 질문 모두 성능 수치가 필요하다.
    • 성능수치는 시스템이 무엇에 관심이 있는지에 따라 달리 표현 할 수 있다
      • 하둡과 같이 일괄처리에 관심이 있는 시스템이라면 처리량(Throughput)에 관심이 있을 것 이고,
      • 채팅 프로그램과 같이 즉각적인 응답에 관심이 있는 시스템(온라인 시스템)이라면 응답(response)시간에 관심
        • 응답 시간
          • 클라이언트 관점에서 본 요청을 처리하는데 걸린 시간
          • 요청을 처리하는 실제 시간 외에도 네트워크 지연 등과 같이 걸린 시간도 모두 포함된 시간이다
        • 지연 시간
          • 요청이 처리되길 기다리는 시간
  • 어떻게 측정해야할까?
    • 다양한 요청을 다루는 시스템에선 응답 시간이 매번 다르다
      • 왜 그럴까? 백그라운드 프로세스 때문
        • 컨텍스트 스위치
        • 페이지 폴트
        • TCP 재전송
        • 가비지 컬렉션
        • 서버랙의 기계적인 진동
    • 따라서 응답 시간을 콕 찝어서 표현 하는게 아니라 측정 가능한 값의 분포로 생각해야 한다
      • 중앙값
      • 평균 응답 시간
        • 산술 평균: n개 값이 주어졌을때 모든 값을 더하고 n으로 나눈 것
      • 백분위

부하 대응 접근 방식

  • 부하가 증가하더라도 좋은 성능을 유지하고 싶다 어떻게 해야할까?
    • Scaling Up: 양 보단 질, 좀 더 강력한 장비를 착용하여 더 많은 부하를 견딜 수 있게하자
    • Scaling Out: : 질 보단 양, 여러대의 낮은 사양의 장비를 두어 부하를 분산하자
      • 비공유 아키텍처(shared - nothing): 다수의 장비에 부하를 분산하는 아키텍처
  • 일부 시스템은 탄력적(elastic) 이다
    • 자동 확장
      • 부하가 증가하면 자동으로 컴퓨팅 자원을 추가하게 된다
      • 시스템에 가해지는 추가적인 부하가 예상이 되지 않을때 유용하다
    • 수동 확장
      • 사람이 직접 시스템에 가해지는 추가적인 부하를 예상하여 컴퓨팅 자원을 추가한다
      • 자동 확장에 비해 운영이 쉽고 예상치 못한 일이 적다
  • 상태 저장(stateful) vs 상태 비저장(stateless)
    • 상태를 저장하지 않은 다수의 장비에 서비스를 배포하는 일은 쉽다 → Scale Out이 쉽다
    • 상태를 저장하는 하나의 장비를 분산 설치하는 것은 쉽다 → Scale Out이 어렵다
      • DB가 보통 이러한 경우인데 때문에 DB는 더 많은 부하를 처리하기 위해 Scale Up을 한다
  • 요즘은?
    • 분산 시스템을 위한 도구와 추상화가 좋아져 분산 데이터 시스템이 향후 기본 아키텍처로 자리 잡을 가능성이 있다
  • 범용적이고 모든 상황에 맞는 확장 아키텍처는 없다
    • 특정 애플리케이션에 적합한 확장성을 갖는 아키텍처는 주로 어떤 동작을 많이하는지? 잘 하지 않는 동작은 무엇인지에 따라 아키텍처가 달라진다
      • 이러한 동작들이 곧 부하 매개변수가 된다.
        • 읽기의 양?
        • 쓰기의 양?
        • 저장할 데이터의 크기?
        • 데이터의 복잡도
        • 응답 시간의 요구사항?
        • 접근 패턴
        • etc…
      • 이 가정이 잘못되면 확장에 대한 노력이 헛수고가 된다.

유지보수성

소프트웨어 비용의 대부분은 유지보수에 들어간다. 유지보수에는 버그 수정, 시스템 운영 유지, 장애 조사, 새로운 플랫폼 적응, 기술 부채 상환, 새로운 기능 개발 등…

대부분의 개발자들은 레거시 시스템을 유지보수 한다. 레거시 시스템을 다루기 위해 최선의 방법을 추천하기란 어려운 일이지만 레거시 시스템을 유지보수 하는 것의 고통을 최소화 해야하는 것은 반드시 해야한다.

그러기 위해서는 3가지 원칙이 필요하다.

  • 운용성
  • 단순성
  • 발전성

운용성

  • 운영을 편리하게 하자
  • 자동화 할 수 있는 것은 자동화 해야한다
  • 시스템 모니터링
  • 성능 지표 확인
    • 시스템 장애, 성능 저하 등의 문제 원인을 추적
  • 보안 패치
  • 다른 시스템이 서로 어떻게 영향을 주는지 확인하여 문제가 생길 수 있는 변경 사항을 사전에 차단
  • 배포, 설정 관리 등을 위한 모범 사례와 도구 마련
  • 설정 변경으로 생기는 시스템 보안 유지보수
  • 안정적인 서비스 환경을 유지하기 위한 절차 정의
  • 시스템에 대한 조직의 지식을 보존할 것

단순성

  • 시스템의 복잡도가 증가하면 같은 시스템에서 작업해야하는 모든 사람의 진행을 느리게 하고 유지보수 비용이 증가한다.
  • 복잡도의 수렁에 빠진 소프트웨어 프로젝를 커다란 진흙 덩어리(big ball of mud)라고 묘사한다
    • 강한 결합도(Tight Coupling)
    • 복잡한 의존성
    • 일관성 없는 네이밍
    • 성능 문제 해결을 목표로 한 해킹
    • 임시방편으로 문제를 해결한 특수 사례
  • 복잡한 시스템은 변경이 있을때 버그가 발생할 확률이 크다
    • 시스템을 이해하고 추론하기 어려워지면 의도치 않은 결과가 발생하기 쉽고 예기치 않은 상호작용에 대해 간과하기 쉽다
  • 단순성을 시스템의 핵심 목표로 삼아야 한다
    • 단순성을 줄이면 시스템을 이해하는데에 쉬워지고 기능을 변경하고 시스템을 유지보수하는 것이 쉬워진다
  • 추상화를 활용하여 우발적 복잡도(Accidental Complexity)를 줄인다
    • 좋은 추상화를 하는 것은 매우 어렵다 그렇지만
    • 비슷한 기능에 대해서 추상화를 해놓으면 코드의 재사용성이 늘어나고 코드가 깔끔해지고 직관적이게 된다
      • 비슷한 기능을 여러 번 구현하는 것을 하지 않아도 된다
    • 또한 코드의 품질이 향상된다

발전성

  • 시스템의 요구사항은 영원하지 않다
  • 시스템의 요구사항은 끊임없이 변할 것이다
    • 애자일
      • 작업의 단위를 작게 쪼개자
      • 애자일을 받쳐주는 것들
        • TDD
        • 리팩토링

0개의 댓글