01장 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션
- 오늘날 많은 애플리케이션은 계산 중심(compute-intensive)이 아닌 데이터 중심(data-intensive)적이다.
- CPU 성능은 애프리케이션을 제한 하는 요소가 아니다
- 중요한 것은 데이터의 양, 복잡도, 변화 속도다.
- 데이터 중심 애플리케이션은 다음을 필요로 한다.
- 데이터를 데이터베이스에 저장
- 읽기 속도 향상을 위해 값비싼 수행 결과를 기억(캐시)
- 다양한 방법으로 데이터를 검색할 수 있게 제공 (검색 색인)
- 비동기 처리를 위해 다른 프로세스로 메시지 보내기 (스트림)
- 주기적으로 대량의 누적된 데이터를 분석 (일괄 처리)
데이터 시스템에 대한 생각
- 데이터를 처리하는 새로운 도구들은 다양한 사용 사례에 최적화됐기에 더 이상 전통적 분류에 딱 들어맞지 않는다.
- 애플리케이션이 단일 도구로는 더 이상 데이터 처리와 저장 모두 만족시킬 수 없는 광범위한 요구사항을 가지고 있다.
- 데이터베이스, 큐 캐시 등이 다른 범주에 속하는 도구처럼 보일지 몰라도 데이터 시스템이라는 점에선 같다.
- 이 책에선 다음 세 가지 관심사에 중점을 둔다.
신뢰성
- 소프트웨어에 대한 일반적인 기대치는 다음과 같다.
- 사용자가 기대한 기능을 수행
- 사용자가 범한 실수나 예상치 못한 사용법을 허용
- 시스템의 성능은 예상된 부하와 데이터 양에서 필수적인 사용 사례를 충분히 만족
- 시스템은 허가되지 않은 접근과 오남용을 방지
- 이 모든 것이 ‘올바르게 동작함’을 의미한다면 ‘무언가 잘못되더라도 지속적으로 올바르게 동작함’을 신뢰성으로 받아들일 수도 있다.
- 결함 (fault)
- 잘못될 수도 있는 일, 사양에서 벗어난 시스템의 한 구성 요소를 결함이라 부른다.
- 결함을 대처할 수 있는 시스템을 내결함성 또는 탄력성을 지녔다고 말한다.
- 결함은 사용자에게 서비스를 제공하지 못하고 멈추는 상황인 장애(failure)와 동일하지 않다.
하드웨어 결함
- 시스템 장애의 원인으론 하드웨어 결함이 대표적이다.
- 시스템 장애율을 줄이기 위한 첫 번째 대응으로 중복(redundancy)을 추가하는 방법이 일반적이다.
- 하나가 죽으면 중복된 구성 요소를 대신 사용 가능
- 다중 장비 중복은 고가용성이 절대적으로 필수적인 애플리케이션에 필요
- 최근엔 데이터 양과 애플리케이션의 계산 요구가 늘면서 더 많은 장비를 사용하게 됐고 이는 하드웨어 결함율을 증가시켰다.
- 따라서 소프트웨어 내결함성 기술을 사용하거나 하드웨어 중복성을 추가해 전체 장비 손실을 견디는 시스템으로 점점 변화하고 있다.
소프트웨어 오류
- 시스템 내 체계적 오류는 예상하기 힘들고 노드 간 상관관계 때문에 시스템 오류를 더 유발하는 경향이 있다.
- 하드웨어 결함은 다른 노드에 영향을 전파하진 않는다.
- 소프트웨어 오류의 예
- 잘못된 특정 입력이 있을 때 모든 서버 인스턴스가 죽는 버그
- CPU 시간, 메모리, 디스크 공간 등의 공유 자원을 과도하게 사용하는 일부 프로세스
- 시스템 속도가 느려져 반응이 없거나 잘못된 응답을 반환하는 서비스
- 소프트웨어의 체계적 오류는 신속한 해결책이 없고 여러 작은 일들이 문제 해결에 도움을 줄 수 있다.
- 시스템 가정과 상호작용에 대해 주의 깊게 생각하기
- 빈틈없는 테스트
- 프로세스 격리
- 죽은 프로세스 재시작 허용
- 모니터링, 분석하기
인적 오류
- 사람은 소프트웨어 시스템을 설계하고 구축하며 운영자로서 시스템을 계속 운영한다.
- 최선의 의도를 가지더라도 사람은 미덥지 않다고 알려져 있다.
- 사람이 미덥지 않음에도 시스템을 신뢰성 있게 만들어야 한다.
- 오류 가능성을 최소화
- 사람이 실수하기 쉬운 장소와 장애가 발생할 수 있는 부분을 분리
- 철저한 테스트
- 명확한 모니터링 대책 마련
확장성
- 확장성은 증가한 부하에 대처하는 시스템 능력을 뜻한다.
- 시스템이 현재 안정적이더라도 미래에 안정적이라는 보장은 없다.
부하 기술하기
- 부하 성장 질문을 논의하려면 현재 시스템 부하를 간결하게 기술해야 한다.
- 부하 성장 질문 예 - 부하가 두 배로 되면 어떻게 될까?
- 부하는 부하 매개변수로 나타낼 수 있다.
- ex) 웹 서버 초당 요청 수, 데이터베이스 읽기 대 쓰기 비율, 캐시 적중률 등
- 부하를 정의하고 개선하는 작업을 트위터로 예를 들어보자
- 트위터의 확장성 문제는 트윗 양이 아닌 팬 아웃에서 발생한다.
- 사람들의 트윗 작성 양 보다 자신이 팔로우한 사람의 트윗을 홈 피드에서 읽은 ‘읽기 요청’이 압도적으로 많다.
- 때문에 트위터는 개별 사용자의 홈 타임라인 캐시를 유지해 트윗이 작성되면 캐시에 트윗을 삽입하고 홈 타임라인 읽기 요청은 결과를 미리 계산했기에 비용이 저렴해진다.
- 즉 트윗 쓰기 시점에 더 많은 일을 하고 읽기 시점엔 적은 일을 하는 것
- 하지만 이 방식의 단점은 일부 팔로워가 많은 유명인의 트잇이 작성되면 쓰기 비용이 엄청 커지기 때문에 적시에 트윗을 캐시에 전송하는 작업이 중요한 도전 과제가 된다.
- 때문에 확장성 논의 시 팔로워 분포는 팬아웃 부하를 결정하기에 핵심 부하 매개변수가 된다.
- 현재 트위터는 홈 타임라인 캐시 삽입 방식을 견고하게 구현한 뒤, 팔로워가 많은 일부 사용자는 팬 아웃에서 제외해서 읽기 시점에 타임라인을 계산하는 방식을 적용하고 있다.
성능 기술하기
- 성능 수치에 대해
- 처리량 (throughput)
- 초당 처리할 수 있는 레코드 수나 일정 크기의 데이터 집합으로 작업을 수행할 때 걸리는 전체 시간
- 응답 시간(response time)
- 클라이언트가 요청을 보내고 응답을 받는 사이의 간격
- 클라이언트 관점에서 본 시간으로 요청 처리 시간 외에도 네트워크 지연, 큐 지연 등도 포함한다.
- 동일한 요청에 매번 응답 시간은 다르기에 단일 숫자가 아닌 측정 가능한 값의 분포로 생각해야 한다.
- 지연 시간 (latency)
- 요청이 처리되길 기다리는 시간으로 서비스를 기다리며 휴지(latent) 상태인 시간을 뜻한다.
- ‘전형적인’ 응답 시간을 알고 싶다면 응답 시간의 평균은 좋은 지표가 아니다.
- 평균은 사용자가 경험한 지연에 대해 알려주지 않기 때문
- 일반적으로 평균보다는 백분위를 사용하는 편이 더 좋다.
- 백분위에서의 중앙값
- 가장 빠른 시간부터 느린 시간까지 정렬했을 때의 중간 지점
- 사용자가 보통 얼마나 기다려야 하는지 알고 싶다면 중앙값이 좋은 지표다.
- 사용자가 여러 요청을 보낼 때 최소 하나 이상의 요청이 중앙값보다 느릴 확률이 50%보다 훨씬 높다.
- 특이 값과 상위 백분위
- 특이 값이 얼마나 좋지 않은지 보려면 상위 백분위를 살피는 것이 좋다.
- 특이 값이란 가끔 꽤 오래 걸리는 응답 시간 수치를 의미한다.
- 95분위, 99분위, 99.9분위를 사용하는 것이 일반적
- ex) 95분위 응답 시간이 1.5초라면 100개 요청 중 95개는 1.5초 미만이고, 5개는 1.5초보다 더 걸린다.
- 꼬리 지연 시간(tail latency)과 상위 백분위 응답 시간은 사용자 경험에 직접 영향을 주기에 중요하다.
- 아마존은 내부 서비스 응답 시간 요구사항을 99.9분위로 기술하는데 이는 1000명 중 1개만 영향이 있음을 뜻한다.
- 보통 응답 시간이 가장 느린 요청을 경험한 고객은 많은 구매로 인해 데이터가 많아서 느린 것
- 구매가 많은 고객의 경험을 개선하는 것이 중요했기에 99.9분위로 책정했지만 99.99분위는 또 최적화에 비용이 너무 많이 들어 최적의 이익을 가져다주지 못하다고 여겨진다고 한다.
부하 대응 접근 방식
- 부하 매개변수가 증가하더라도 좋은 성능을 유지하려면 어떻게 해야 할까?
- 급성장하는 서비스를 맡고 있다면 자주 아키텍처를 검토할 필요가 있다.
- 확장성을 고려한 장비 운용
- 용량 확장 (scaling up)
- 규모 확장 (scaling out)
- 현실적으로 좋은 아키텍처는 적절한 사양의 장비 몇 대가 좋다. (다량의 낮은 사양의 장비 보다는)
- 일부 시스템은 탄력적이다.
- 부하 증가를 감지하면 컴퓨팅 자원을 자동으로 추가
- 탄력적이지 않은 시스템은 수동으로 확장해야하기에 많은 고려가 필요
- stateful한 노드의 확장은 아주 많은 복잡도를 요구한다.
- 때문에 DB를 분산으로 만들어야 하는 고가용성 요구가 있을 때까지 단일 노드에 DB를 유지하는 것이 최근까지의 통념이다.
- 대개 대규모 시스템 아키텍처는 해당 애플리케이션에 특화돼 있다.
- 범용적인 확장 아키텍처는 없다.
- 특정 애플리케이션에 적합한 확장성을 가진 아키텍처는 주요 동작과 주요 동작이 아닌 것이 무엇인지에 대한 가정을 바탕으로 하는데 이는 곧 부하 매개변수가 된다.
유지보수성
- 소프트웨어 비용의 대부분은 초기 개발인 아닌 유지보수에 들어간다.
- 유지보수 고통을 최소화하고 레거시를 만들지 않게끔 소프트웨어를 설계하는 설계 원칙이 존재한다.
- 운용성: 운영팀이 시스템을 원할하게 운영할 수 있게 쉽게 만들어라
- 단순성: 시스템 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들어라
- 발전성: 엔지니어가 이후에 시스템을 쉽게 변경할 수 있게 하라.
운용성: 운영의 편리함 만들기
- “좋은 운영은 종종 나쁜 소프트웨어 제약을 피하는 대안이 될 수 있다. 하지만 좋은 소프트웨어라도 나쁘게 운영하면 작동을 신뢰할 수 없다.”
- 시스템이 원활히 작동하려면 운영팀이 필수이고 좋은 운영팀은 다음 책임을 가진다.
- 시스템 상태를 모니터링하고 상태가 좋지 않다면 빠르게 복원
- 시스템 장애, 성능 저하 등의 문제 원인을 추적
- 보안 패치를 포함해 소프트웨어아 플랫폼을 최신으로 유지
- 시스템 간 영향을 체크해 문제가 생길 수 있는 변경을 사전에 차단
- 발생 가능성이 있는 문제를 예측해 미리 해결 (ex. 용량 계획)
- 배포, 설정 관리 등을 위한 모범 사례와 도구 마련
- 애플리케이션을 다른 플랫폼으로 이동하는 등 복잡한 유지보수 태스크 수행
- 설정 변경으로 생기는 시스템 보안 유지보수
- 예측 가능한 운영과 안정적인 서비스 환경을 유지하기 위한 절차 정의
- 개인 인사 이동에도 시스템에 대한 조직의 지식을 보존
- 좋은 운영성을 위해 데이터 시스템은 아래 항목 등을 포함해 다양한 일을 할 수 있다.
- 좋은 모니터링으로 런타임 동작과 시스템 내부에 대한 가시성 제공
- 표준 도구를 이용해 자동화와 통합을 위한 우수한 자원을 제공
- 개별 장비 의존성 회피
- 좋은 문서과 이해하기 쉬운 운영 모델 제공
- 만족할만한 기본 동작을 제공하고 필요할 때 기본값을 재정의할 수 있도록
- 적절한 자기 회복이 가능할 뿐 아니라 필요에 따라 수동으로 제어할 수 있게 함
- 예측 가능하게 동작
단순성: 복잡도 관리
- 프로젝트가 커짐에 따라 시스템은 매우 복잡하고 이해하기 어려워진다.
- 복잡도는 다양한 증상으로 나타난다.
- 상태 공간의 급증, 모듈 간 강한 커플링, 복잡한 의존성, 일관성 없는 명명과 용어, 임시방편으로 문제를 해결한 특수 사례 등
- 시스템을 단순하게 만드는 일은 우발적 복잡도를 줄인다는 뜻일 수도 있다.
- 우발적 복잡도 - 문제 자체가 아닌 구현 과정에서 불필요하게 발생하는 복잡성을 의미
- 우발적 복잡도를 제거하기 위한 최상의 도구는 추상화이다.
- 깔끔하고 직관적인 외관 아래 많은 세부 구현을 숨길 수 있다.
발전성: 변화를 쉽게 만들기
- 시스템의 요구사항은 계속 변화한다.
- 조직 프로세스 측면에서 애자일 작업 패턴은 변화에 적응하기 위한 프레임워크를 제공한다.
- 데이터 시스템을 쉽게 변경하게 하는 방법은 시스템의 간단함과 추상화와 밀접한 관련이 있다.