개요

  • 이 문서는 [[Designing-Data-Intensive-Applications]]책의 1장을 공부하며 메모한 것입니다.
  • 이 문서는 메모일 뿐이니 자세한 내용은 교재를 참고해야 합니다.

많은 애플리케이션은 다음을 필요로 한다.

  • 나중에 다시 데이터를 찾을 수 있게 저장 : 데이터베이스
  • 읽기 속도 향상을 위해 비싼 수행 결과를 기억 : 캐시
  • 검색/필터링 : 검색 색인(search index)
  • 비동기 처리를 위해 다른 프로세스로 메시지 보내기 : 스트림 처리(stream processing)
  • 주기적으로 대량의 데이터 분석/처리 : 일괄 처리(batch processing)

세 가지 주요 관심사

  • 신뢰성(Reliability)
  • 확장성(Scalability)
  • 유지보수성(Maintainability)

신뢰성

신뢰성

  • 무언가 잘못되더라도 지속적으로 올바르게 동작함.
  • 결함이 발생해도 시스템이 올바르게 동작한다는 의미.
  • 내결함성(fault-tolerant), 탄력성(resilient) : (특정 유형의) 결함을 견딜 수 있는 시스템.
    • 결함 때문에 장애가 발생하지 않도록 내결함성 구조를 설계하자.
    • 중대한 버그 대부분은 미흡한 오류 처리 때문에 발생한다.
    • Netflix의 Chaos Monkey : 고의적으로 결함을 유도하여 내결함성 시스템을 훈련.

결함(fault) : 장애(failure)와는 다른 개념.

  • 결함은 사양에서 벗어난 시스템의 한 구성 요소.
  • 장애는 사용자에게 서비스를 제공하지 못하고 시스템 전체가 멈춘 경우.

하드웨어 오류

하드디스크의 평균 장애 시간(mean time to failure, MTTF)는 약 10~50년.

  • 디스크가 10000 개 있는 저장 클러스터는 평균적으로 하루에 디스크 한 개씩 죽는 셈.

데이터 양과 계산 요구가 늘어나면서 애플리케이션은 많은 수의 장비를 사용하게 됐음.

  • AWS 같은 클라우드 플랫폼은 가상 장비 인스턴스가 경고 없이 사용할 수 없게 되는 상황이 종종 일어난다.
  • AWS 플랫폼은 단일 장비 신뢰성보다 유연성(flexability)과 탄력성(elasticity)을 우선적으로 처리하도록 설계됐기 때문.
  • 소프트웨어 내결함성 기술을 사용하거나 하드웨어 중복성을 추가해 전체 장비의 손실을 견딜 수 있는 방향으로 옮겨가는 중.

소프트웨어 오류

  • 예상하기 어렵다.
  • 연쇄 장애도 발생할 수 있음.
  • 열심히 생각하고, 빈틈없이 테스트하고, 프로세스를 격리하고…

인적 오류

  • 오류를 최소화하는 방향으로 설계한다.
  • 통합 테스트, 수동 테스트, 자동 테스트.
  • 설정을 변경했을 때 빠르게 롤백할 수 있도록 준비한다.
  • 모니터링 : 성능 지표, 오류 비율 등등..

확장성

성능 저하를 유발하는 가장 흔한 이유 : 부하 증가.

  • 확장성
    • 부하가 증가해도 좋은 성능을 유지하기 위한 전략을 의미한다.
    • 증가한 부하에 대처하는 시스템 능력을 설명하는 데 사용하는 용어

“X는 확장 가능하다”, “Y는 확장성이 없다” 같은 말은 의미가 없음.

  • 확장성을 논한다는 것
    • “시스템이 특정 방식으로 커지면 이에 대처하기 위한 선택은 무엇인가?”
    • “추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?”

부하 매개변수(load parameter)

  • 웹 서버의 초당 요청 수
  • DB의 읽기/쓰기 비율
  • 동시 활성 사용자(Active User)
  • 캐시 적중률

성능 기술하기

두 가지 질문으로 부하가 증가했을 때 일어나는 일을 조사할 수 있다.

  • 부하 매개변수를 증가시키고 시스템 자원(CPU, 메모리, 네트워크 대역폭 등)은 변경하지 않고 유지하면 시스템 성능은 어떻게 영향을 받을까?
  • 부하 매개변수를 증가시켰을 때 성능이 변하지 않고 유지되길 원한다면 자원을 얼마나 많이 늘려야 하나?

처리량과 응답 시간

  • 일괄 처리 시스템이라면 부하 증가시 처리량(throughput)에 관심을 갖는다.
    • 처리량 : 초당 처리할 수 있는 레코드 수, 작업 수행 전체 시간 등.
  • 온라인 시스템이라면 서비스 응답 시간(response time)이 중요하다.
    • 응답 시간 : 클라이언트가 요청을 보내고 응답을 받는 사이의 시간.
    • 참고 : 지연 시간과 응답 시간
      • 지연 시간(latency) : 요청이 처리되길 기다리는 시간으로, 서비스를 기다리며 휴지(latent) 상태인 시간.
      • 응답 시간 : 클라이언트 관점. 요청을 처리하는 시간 + 네트워크 지연 + 큐 지연 등등
    • 사용자가 얼마나 기다리는지 알고 싶다면 산술 평균을 쓰지 말 것.
    • 중앙값을 쓰도록 한다.

중앙값

  • 중앙값은 p50으로도 표기한다.
  • 사용자 요청의 절반은 중앙값 응답 시간보다 짧게 걸린다.
  • 사용자 요청의 절반은 중앙값 응답 시간보다 오래 걸린다.

상위 백분위를 보기

  • p95, p99, p999 등을 본다. 각각 95%, 99%, 99.9% 를 말한다.
  • p999는 요청 1000개 중 가장 느린 1개의 요청을 본다.
  • 아마존의 경우 p999를 본다.
    • 구매 이력이 많으면 조회 시간이 오래 걸리기 때문에, p999에 해당하는 고객들이야말로 가장 소중한 고객이다.

부하 대응 접근 방식

  • 용량 확장(scaling up)
    • 수직 확장(vertical scaling) : 좀 더 강력한 장비로 이동
  • 규모 확장(scaling out)
    • 수평 확장(horizontal scaling) : 다수의 낮은 사양 장비에 부하를 분산
    • 비공유(shared-nothing) 아키텍처라 부름.

유지보수성

유지보수성의 본질은 시스템에서 작업하는 엔지니어와 운영 팀의 삶을 개선하는 데 있다.

신뢰성, 확장성을 달성하기 위한 쉬운 해결책은 없다.

그보다 운용성, 단순성, 발전성을 염두에 두고 시스템을 생각하자.

  • 운용성(operability) : 운영하기 쉽게 만든다.
  • 단순성(simplicity) : 복잡도를 최대한 제거한다.
  • 발전성(evolvability) : 쉽게 변경할 수 있게 한다.
    • 유연성(extensibility)
    • 수정 가능성(modifiability)
    • 적응성(plasticity)

운용성: 운영의 편리함 만들기

  • 좋은 모니터링으로 runtime 동작과 시스템의 내부에 대한 가시성 제공
  • 좋은 문서와 이해하기 쉬운 운영 모델(X를 하면 Y가 발생합니다).
  • 필요할 때 관리자가 기본값을 다시 설정할 수 있도록 해준다.
  • 예측 가능하게 동작하고 예기치 않은 상황을 최소화.
  • 특정 장비 의존성을 회피한다.
    • 해당 장비를 끄더라도 시스템 전체에 영향을 주지 않고 계속해서 운영 가능해야 한다.

단순성: 복잡도 관리

  • 복잡도를 줄이면 소프트웨어 유지보수성이 크게 향상된다.
  • 따라서 단순성이 시스템의 핵심 목표여야 한다.
  • 우발적 복잡도(accidental complexity)를 제거하기 위한 최상의 도구는 추상화.
    • 좋은 추상화는 깔끔하고 직관적인 외관 아래로 많은 세부 구현을 숨길 수 있다.
    • 좋은 추상화는 재사용에도 유리하다.

예를 들어, 고수준 프로그래밍 언어는 기계 언어, CPU 레지스터, 시스템 호루을 숨긴 추상화다. SQL은 디스크에 기록하고 메모리에 저장한 복잡한 데이터 구조와 다른 클라이언트의 동시 요청과 고장 후 불일치를 숨긴 추상화다. 물론 고수준 언어로 프로그래밍해도 여전히 기계어를 사용한다. 단지 직접 사용하지 않을 뿐이다. 프로그래밍 언어의 추상화 덕분에 기계어를 생각할 필요가 없기 때문이다.

발전성: 변화를 쉽게 만들기

  • TDD
  • Refactoring

Links

  • [[Designing-Data-Intensive-Applications]]