Chapter 1: 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션

💡 책에서 기억하고 싶은 내용

데이터 시스템에 대한 생각

  • 일반적으로 데이터베이스, 큐, 캐시 등을 매우 다른 범주에 속하는 도구로 생각한다

    • 데이터베이스와 메시지 큐는 표면적으로 비슷하더라도 (둘 다 얼마 동안 데이터를 저장) 매우 다른 접근 패턴 을 갖고 있어 서로 다른 특성 이 있기 때문에 구현 방식이 매우 다르다

  • 데이터 저장과 처리를 위한 새로운 도구들

    • 메시지 큐로 사용하는 데이터스토어인 Redis 가 있고,

    • 데이터베이스처럼 지속성(durability) 을 보장하는 메시지 큐인 Apache Kafka 도 있다

  • 점점 더 많은 애플리케이션이 단일 도구로는 더 이상 데이터 처리저장 모두를 만족시킬 수 없는, 과도하고 광범위한 요구사항을 갖고 있다

    • 대신 작업(work)은 단일 도구에서 효율적으로 수행할 수 있는 태스크(task)로 나누고, 다양한 도구들은 애플리케이션 코드를 이용해 서로 연결한다

  • 서비스 제공을 위해 각 도구를 결합할 때 서비스 인터페이스애플리케이션 프로그래밍 인터페이스 (API) 는 보통 클라이언트가 모르게 구현 세부 사항 을 숨긴다

    • 좀 더 작은 범용 구성 요소들로 새롭고 특수한 목적의 데이터 시스템을 만든다

    • 복합 데이터 시스템(composite data system) 은 외부 클라이언트가 일관된 결과를 볼 수 있게끔 쓰기에서 캐시 를 무효화하거나 업데이트하는 등 특정 보장 기능 을 제공할 수 있다

  • 대부분의 소프트웨어 시스템에서 중요하게 여기는 세 가지 관심사

    • 신뢰성 (Reliability)

      • 하드웨어나 소프트웨어 결함, 인적 오류 (human error) 같은 역경에 직면하더라도 시스템은 지속적으로 올바르게 동작 (원하는 성능 수준에서 정확한 기능을 수행) 해야 한다

    • 확장성 (Scalability)

      • 시스템의 데이터 양, 트레픽 양, 복잡도가 증가 하면서 이를 처리할 수 있는 적절한 방법이 있어야 한다

    • 유지보수성 (Maintainability)

      • 시간이 지남에 따라 여러 다양한 사람들이 시스템 상에서 작업 (현재 작업을 유지보수하고, 새로운 사용 사례를 시스템에 적용하는 엔지니어링과 운영) 할 것이기 때문에, 모든 사용자가 시스템 상에서 생산적 으로 작업할 수 있게 해야한다

신뢰성

  • 소프트웨어의 일반적인 기대치

    • 애플리케이션은 사용자가 기대한 기능 을 수행한다

    • 시스템은 사용자가 범한 실수예상치 못한 소프트웨어 사용법허용 할 수 있다

    • 시스템 성능은 예상된 부하데이터 양 에서 필수적인 사용 사례를 충분히 만족한다

    • 시스템은 허가되지 않은 접근오남용방지한다

  • 무언가 잘못되더라도 지속적으로 올바르게 동작함 을 신뢰성의 의미로 이해할 수 있다

  • 잘못될 수 있는 일을 결함 (fault) 라 부른다

    • 결함을 예측하고 대처할 수 있는 시스템을 내결함성 (fault-tolerance) 또는 탄력성 (resilient) 을 지녔다고 말한다

  • 결함장애 (failure) 와 동일하지 않다

    • 결함은 사양에서 벗어난 시스템의 한 구성 요소로 정의되지만,

    • 장애는 사용자에게 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 경우다

  • 결함으로 인해 장애가 발생하지 않게끔 내결함성 구조 를 설계하는 것이 가장 좋다

    • 결함을 예측하고 대체할 수 있는 시스템

  • 내결함성 시스템 에서 경고 없이 개별 프로세스를 무작위로 죽이는 것과 같이 고의적으로 결함을 일으켜 결함률을 증가시키는 방법

    • 많은 중대한 버그는 미흡한 오류 처리에 기인한다

    • 고의적으로 결함을 유도함으로써 내결함성 시스템을 지속적으로 훈련하고 테스트해서, 결함이 자연적으로 발생했을 때 올바르게 처리할 수 있다는 자신감을 높인다

    • ex) Netflix의 Chaos Monkey

하드웨어 결함

  • 시스템 장애율을 줄이기 위한 첫 번째 대응으로 각 하드웨어 구성 요소에 중복(redundancy) 을 추가하는 방법이 일반적이다

    • 디스크는 RAID (Redundant Array of Independent Disk) 구성으로 설치할 수 있고,

    • 서버는 이중 전원 디바이스와 hot-swap 가능한 CPU를,

      • 데이터센터는 건전지와 예비 전원용 디젤 발전기를 갖출 수 있다

소프트웨어 오류

  • 시스템 내 체계적 오류 (systematic error) 는 예상하기 더 어렵고, 노드 간 상관관계 때문에 상관관계 없는 하드웨어 결함보다 오히려 시스템 오류를 더욱 많이 유발하는 경향이 있다

    • ex)

      • 잘못된 특정 입력이 있을 때 모든 애플리케이션 서버 인스턴스가 죽는 소프트웨어 버그

      • CPU 시간, 메모리, 디스크 공간, 네트워크 대역폭처럼 공유 자원 을 과도하게 사용하는 일부 프로세스

      • 시스템의 속도가 느려저 반응이 없거나, 잘못된 응답을 반환하는 서비스

      • 한 구성 요소의 작은 결함이 다른 구성 요소의 결함을 야기하고 차례차례 더 많은 결함이 발생하는 연쇄 장애 (cascading failure)

  • 이 같은 소프트웨어 결함을 유발하는 버그는 특정 상황 에 의해 발생하기 전까지 오랫동안 나타나지 않는다

  • 소프트웨어의 체계적 오류 문제 는 신속한 해결책이 없지만 아래와 같은 일들이 문제 해결에 도움을 줄 수 있다

    • 시스템의 가정(presume) 과 상호 작용에 대해 주의 깊게 생각하기

    • 빈틈없는 테스트

    • 프로세스 격리

    • 죽은 프로세스의 재시작 허용

    • 프로덕션 환경에서 시스템 동작의 측정

    • 모니터링

    • 분석하기

  • 시스템이 무언가 보장하길 기대한다면, 수행 중에 이를 지속적으로 확인해 차이가 생기면 경고를 발생시킬 수 있다

인적 오류

신뢰성 있는 시스템을 만들기위한 접근 방식

  • 오류의 가능성최소화 하는 방향으로 시스템을 설계

    • 잘 설계된 추상화, API, 관리 인터페이스를 사용하여 옳은 일 은 쉽게하고, 잘못된 일 은 막을 수 있다

  • 사람의 실수로 장애가 발생할 수 있는 부분을 분리

    • 실제 데이터를 사용해 안전하게 살펴보고 실험할 수 있지만, 실제 사용자에게는 영향이 없는 비 프로덕션 샌드박스 (sandbox) 를 활용

  • 단위 테스트 → 전체 시스템 통합 테스트, 수동 테스트까지 모든 수준에서 철저하게 테스트

  • 인적 오류를 빠르고 쉽게 복구 할 수 있게 하기

    1. 설정 변경 내역에 대한 빠른 롤백

    2. 새로운 코드를 서서히 롤아웃 하기 (예상치 못한 버그가 일부 사용자에게만 영향이 미치게 함)

    3. 데이터 재계산 도구 제공

  • 상세하고 명확한 모니터링 대책 마련

    • 문제가 발생했을 때 지표 (metrics) 는 문제를 분석하는 데 매우 중요하다

  • 조작 교육과 실습을 실행

확장성

  • 성능 저하를 유발하는 흔한 이유 중 하나는 부하 증가

  • 확장성을 논한다는 것은 아래와 같은 질문을 고려하는 것이다

    • “시스템이 특정 방식으로 커지면 이에 대처하기 위한 선택은 무엇인가?”

    • “추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?

부하 기술하기

  • 시스템의 현재 부하를 간결하게 기술해야 한다

    • 그래야 부하 성장 질문 (부하가 두 배로 되면 어떻게 될까?)을 논의할 수 있다

  • 부하는 부하 매개변수 (load parameter) 라 부르는 몇 개의 숫자로 나타낼 수 있다

    • ex)

      • 웹 서버의 초당 요청 수 (RPS)

      • DB 읽기 vs 쓰기 비율

      • 동시 활성 사용자 (active user)

      • 캐시 적중률 (cache hit)

  • 평균적인 경우가 중요할 수도 있고, 소수의 극단적인 경우가 병목 현상의 원인일 수 있다

성능 기술하기

  • 시스템 부하를 기술하면, 부하가 증가할 때 어떤 일이 일어나는지 조사할 수 있다

    • 부하 매개변수 를 증가시키고, 시스템 자원(CPU, 메모리, 네트워크 대역폭 등)은 변경하지 않고 유지하면 시스템 성능은 어떻게 영향을 받을까?

    • 부하 매개변수 를 증가시켰을 때, 성능이 변하지 않고 유지되길 원한다면 자원은 얼마나 많이 늘려야할까?

  • 하둡 같은 일괄 처리 시스템은 보통 처리량 (throughput) 에 관심을 가진다

  • 온라인 시스템에서는 서비스 응답 시간 (response time) , 즉 client가 요청을 보내고 응답을 받는 사이의 시간이 중요하다

  • 응답 시간은 단일 숫자가 아니라 측정 가능한 값의 분포 로 생각해야 한다

  • 일반적으로 평균보다는 백분위 (percentile) 를 사용하는 편이 좋다

    • 응답 시간 목록을 가지고 가장 빠른 시간부터 제일 느린 시간까지 정렬하면, 중간 지점이 중앙값 (median) 이 된다

      • 사용자가 보통 얼마나 오랫동안 기다려야 하는지 알고 싶다면 중앙값 이 좋은 지표다

      • 중앙값은 50분위로서, p50 으로 축약할 수 있다

      • 중앙값은 단일 요청을 참고한다!

    • 특이 값이 얼마나 좋지 않는지 알아보려면, 상위 백분위를 살펴보는 것이 좋다

      • 일반적으로 p95, p99, p999를 사용한다

      • 요청의 95%, 99%, 99.9% 가 특정 기준치보다 더 빠르면 해당 특정 기준치가 각 백분위의 응답 시간 기준치가 된다

      • 꼬리 지연 시간 (tail latency) 으로 알려진 상위 백분위 응답 시간은 서비스의 사용자 경험에 직접 영향을 주기 때문에 중요하다

      • 큐 대기 지연 (queueing delay) 은 높은 백분위에서 응답 시간의 상당 부분을 차지한다

        • 서버는 병렬로 소수의 작업만 처리할 수 있기 때문에, 소수의 느린 요청 처리만으로 후속 요청 처리 가 지체 된다

          • 이 현상을 선두 차단 (head-of-line blocking) 이라 한다

        • 서버에서 후속 요청이 빠르게 처리되더라도, 이전 요청이 완료되길 기다리는 시간 때문에 클라이언트는 전체적으로 응답 시간이 느리다고 생각할 것이다

          • 이런 문제 때문에 client side 응답 측정이 중요하다!

지연 시간(latency) 과 응답 시간(response time)

  • 응답 시간

    • client 관점에서 본 시간으로, 요청을 처리하는 실제 시간 (서비스 시간) 외에도 네트워크 지연과 큐 지연도 포함한다

  • 지연 시간

    • 요청이 처리되길 기다리는 시간

    • 서비스를 기다리며 휴지(latent) 상태인 시간

부하 대응 접근 방식

  • 다수의 장비에 부하를 분산하는 아키텍처를 비공유 (shared-nothing) 아키텍처라 부른다

  • 탄력적인 시스템은 부하를 예측할 수 없을 만큼 높은 경우 유용하지만, 수동으로 확장하는 시스템이 더 간단하고 운영상 예상치 못할 일이 적다

  • 다수의 장비에 stateless 서비스를 배포하는 일은 상당히 간단하지만, 단일 노드에 stateful 데이터 시스템을 분산 설치 하는 일은 아주 많은 복잡도가 추가적으로 발생한다

    • 확장 비용이나 데이터베이스를 분산으로 만들어야 하는 고가용성 요구가 있을 때까지, 단일 노드에 데이터베이스를 유지하는 것 (scale-up) 이 최근까지의 통념이다

  • 아키텍처를 결정하는 요소는 읽기의 양, 쓰기의 양, 저장할 데이터의 양, 데이터의 복잡도, 응답 시간 요구사항, 접근 패턴 등이 있다

  • 특정 애플리케이션에 적합한 확장성을 갖춘 아키텍처는 주요 동작이 무엇이고 잘 하지 않는 동작이 무엇인지에 대한 가정 을 바탕으로 구축한다

    • 이 가정은 곧 부하 매개변수 가 된다

    • 이 가정이 잘못되면, 확장에 대한 엔지니어링 노력은 헛수고가 되고 최악의 경우 역효과를 낳는다

    • 검증되지 않은 제품의 경우 미래를 가정한 부하에 대비해 확장하기 보다는, 빠르게 반복해서 제품 기능을 개선하는 작업이 좀 더 중요하다

유지보수성

유지보수 중 고통을 최소화하고 레거시 소프트웨어를 만들지 않기 위한 시스템 설계 원칙은 다음 세 가지다

  1. 운용성 (operability)

    • 운영팀이 시스템을 원활하게 운영할 수 있게, 쉽게 만들어라

  2. 단순성 (simplicity)

    • 시스템의 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들어라

      • 복잡도를 제거하기 위한 최상의 도구는 추상화

  3. 발전성 (evolvability)

    • 엔지니어가 이후에 시스템을 쉽게 변경할 수 있게 하라

      • 그래야 요구사항 변경 같은 예기치 않은 사용 사례를 적용하기가 쉽다

    • 이 속성은 유연성 (extensibility), 수정 가능성 (modifiability), 적응성 (plasticity)으로 알려져 있다

    • 애자일 (agile) 작업 패턴은 변화에 적응하기 위한 프레임워크를 제공한다

정리

  • 신뢰성 은 결함이 발생해도 시스템이 올바르게 동작하게 만든다는 의미다

    • 결함은 하드웨어와 소프트웨어 버그, 그리고 사람에게 있을 수 있다

    • 내결함성 기술은 최종 사용자에게 특정 유형의 결함을 숨길 수 있게 해준다

  • 확장성 은 부하가 증가해도 좋은 성능을 유지하기 위한 전략을 의미한다

    • 확장 가능한 시스템에서는 부하가 높은 상태에서 신뢰성을 유지하기 위해 처리 용량 을 추가할 수 있다

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

    • 좋은 추상화복잡도 를 줄이고, 쉽게 시스템을 변경할 수 있게 하며, 새로운 사용 사례에 적용하는 데 도움이 된다

    • 좋은 운용성 이란 시스템의 건강 상태를 잘 관찰할 수 있고, 시스템을 효율적으로 관리하는 방법을 보유한다는 의미다

Last updated