////
Search

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

Date
2025/01/17 09:00
Tags

1. 데이터 시스템

오늘날 많은 애플리케이션은 계산 중심과 다르게 데이터 중심적이다.
CPU 성능은 애플리케이션을 제한하는 요소가 아니고, 데이터의 양과 복잡도 그리고 변화 속도가 문제다.
일반적으로 데이터 중심 어플리케이션의 표준 구성요소
데이터베이스 = 나중에 다시 데이터를 찾을 수 있게 저장
캐시 = 읽기 속도 향상을 위함
검색 색인 = 데이터를 검색하거나 다양한 방법으로 필터링 할 수 있도록 제공
스트림 처리 = 비동기 처리를 위해 다른 프로세스로 메시지 보내기
배치 처리 = 주기적으로 대량의 누적된 데이터를 분석
너무 뻔하게 들린다면 데이터 시스템이 성공적으로 추상화됐기 때문이다.

1.1 데이터 시스템에 대한 생각

일반적으로 DB, 큐, 캐시 등을 매우 다른 범주에 속하는 도구로 생각한다.
그러면 왜 데이터 시스템이라는 포괄적 용어로 묶어야 할까?
새로운 도구들은 다양한 사용 사례에 최적화됐기 때문에 더이상 전통적인 분류에 딱 들어맞지 않음
분류 간 경계가 흐려지고 있음 (카프카, 레디스 등)
단일 도구로는 더 이상 데이터 처리와 저장 모두를 만족시킬 수 없는 과도하고 광범위한 요구사항을 가짐
대신 단일 도구에서 효율적으로 수행할 수 있는 태스크로 나누고 다양한 도구들은 애플리케이션 코드를 이용해 서로 연결함

1.2 소프트웨어 시스템에서 중요하게 여기는 세가지 관점사

신뢰성(Reliability) : 하드웨어나 소프트웨어 결함, 인적 오류에 직명해도 시스템은 지속적으로 올바르게 동작해야 한다.
확장성(Scalability) : 데이터의 양, 트래픽, 복잡도가 증가해도 이를 처리할 수 있는 적절한 방법이 있어야한다.
유지보수성(Maintainability) : 모든 사용자가 시스템 상에서 생산적으로 작업할 수 있어야 한다.

1.3 신뢰성

누구나 어떤 것을 신뢰하거나 신뢰하지 않는다는 의미가 무엇인지에 대한 직관적인 개념을 가지고 있다.
소프트웨어의 일반적인 기대치
애플리케이션은 사용자가 기대한 기능을 수행한다.
시스템은 사용자가 범한 실수나 예상치 못한 소프트웨어 사용법을 허용할 수 있다.
시스템 성능은 예상된 부하와 데이터 양에서 필수적인 사례를 충분히 만족한다.
시스템은 허가되지 않은 접근과 오남용을 방지한다.
결함(fault)을 예측하고 대처할 수 있는 시스템을 내결함성(fault-tolerant) 또는 탄력성(resilient) 있다고 함
결함 = 사양에 벗어난 시스템의 구성 요소
장애 = 유저에게 서비스를 제공하지 못하고 시스템이 멈춤 경우
결함을 0으로 줄일 수는 없다.
결함으로 인해 장애가 발생하지 않도록 내결함성 구조로 설계하는 것이 가장 좋다.

1.3.1 하드웨어 결함

시스템 장애의 원인을 생각할 때 하드웨어 결함이 바로 떠오른다.
하드디스크 고장, 램 결함, 대규모 정전 사태 발생, 네트워크 케이블 결함 등
규모가 큰 데이터센터에서 일하는 사람은 많은 장비를 다룰 경우 이 같은 일은 늘상 일어난다고 말한다.
AWS같은 일부 클라우드 플랫폼 인스턴스도 이 같은 상황이 상당히 일반적이다.
대응 방안
각 하드웨어 구성요소에 중복(redundancy)을 추가
단일 장비 신뢰성보다 유연성과 탄력성을 우선적으로 처리하게끔 설계

1.3.2 소프트웨어 결함

시스템 내 체계적 오류(systematic error)는 더 예상하기 어렵고 오류를 더욱 많이 유발한다.
잘못된 특정 입력이 있을 때 애플리케이션 서버 인스턴스가 죽는 소프트웨어 버그 ⇒ 2012년 6일 30일 리눅스 윤초
CPU 시간 / 메모리 / 디스크 등 공유자원을 과도하게 사용하는 일부 프로세스
시스템의 속도가 느려져 반응이 없거나 잘못된 응답을 반환하는 서비스
하나 요소의 결함이 다른 구성 요소의 결함을 야기하고 차례차례 더 많은 결함이 발생하는 연쇄 장애
소프트웨어의 체계적 오류 문제는 신속한 해결책이 없다.
시스템의 가정과 상호작용에 대해서 주의깊게 생각하기
빈틈없는 테스트
프로세스 격리
모니터링

1.3.3 인적오류

최고의 시스템은 다양한 접근 방식을 결합한다.
오류의 가능성을 최소화하는 방향으로 시스템을 설계하라. 잘 설계된 추상화, API, 관리 인터페이스를 사용하자.
실제 데이터를 볼 수 있지만 사용자에게는 영향이 없는 비-production 환경의 샌드박스를 제공하라.
단위 테스트, 전체 시스템 통합 테스트, 수동 테스트까지 모든 수준에서 철저히 테스트하라.
설정 변경은 빠르게 롤백 가능하게, 새로운 코드는 서서히 롤 아웃
성능 지표와 오류 rate 같은 모니터링
조작 교육과 실습

1.2 신뢰성의 중요도

비즈니스 애플리케이션에서 버그는 생산성 저하의 원인이자 매출과 명성에 타격을 줄 수 있다.

2. 확장성

현재 시스템이 안정적으로 동작한다고 해서 미래에도 안정적으로 동작을 보장하지는 않는다.
성능 저하를 유발하는 가장 흔한 유형은 부하 증가
확장성은 증가한 부하에 대처하는 시스템 능력을 설명하는 데 사용하는 용어
시스템에 부여하는 일차원적 표식이 아님에 주의

2.1 부하 기술하기

부하는 부하 매개변수(load parameter)라 부르는 몇 개의 숫자로 나타낼 수 있다.
적합한 부하 매개변수의 선택은 시스템 설계에 따라 달라진다.
부하 매개변수가 될 수 있는것
웹 서버의 초당 요청 수
데이터베이스의 읽기 대 쓰기 비율
캐시 적중률
대화방의 동시 활성 사용자(Active User)
평균적인 경우가 중요할 수 있고 소수의 극단적인 경우가 병목의 현상의 원인일 수도 있다.

2.2 성능 기술하기

일단 시스템 부하를 기술하면 부하가 증가할 때 어떤 일이 일어나는지 조사할 수 있다.
부하 기술 방법
부하 매개변수를 증가시키고 시스템 자원은 변경하지 않고 유지하면 시스템 성능은 어떻게 영향을 받을까?
부하 매개변수를 증가시켰을 때 성능이 변하지 않고 유지되길 원한다면 자원을 얼마나 많이 늘려야 할까?
하둡같은 일괄처리 시스템은 보통 처리량에 관심을 가지지만 온라인 시스템에서 더 중요한 사항은 응답시간이다.
지연 시간(latencty)과 응답 시간(response time)을 종종 같은 뜻으로 사용하지만 동일하지 않다. 응답 시간은 클라이언트에서 본 관점으로, 요청을 처리하는 실제 시간 외에도 네트워크 지연과 큐 지연도 포함한다. 지연 시간은 요청이 처리되길 기다리는 시간으로, 서비스를 기다리며 휴지(Latent) 상태인 시간을 말한다.
클라이언트가 반복해서 동일한 요청을 보내도 매번 응답시간이 다르다.
따라서 응답 시간은 단일 숫자가 아니라 분포로 생각해야 한다.
일반적으로 평균보다는 백분위를 사용하는 편이 좋다.
가장 빠른 시간부터 제일 느린 시간까지의 중앙값
사용자가 보통 얼마나 오랫동안 기다려야 하는지 알고 싶다면 중앙값이 좋은 지표다.
특이 값이 렁마나 좋지 않은지 보려면 상위 백분위를 살표보자
백분위는 서비스 수준 목표(service level objective, SLO)와 서비스 수준 협약서(service level agreement, SLA)에 자주 사용하고 기대 성능과 서비스 가용성을 정의하는 계약서에서 자주 등장한다.
환불에 대한 지표가 된다.
큐 대기 지연은 응답시간의 상당 부분을 차지한다.
선두 차단 = 서버는 병렬로 소수 작업만 처리할 수 있기때문에, 첫 요청을 처리하는데 느려진다면 후속 요청이 모두 영향받는다.
꼬리 지연 증폭 = 여러 API를 호출했을 때, 적은 API만 느려도 전체가 느려지게 된다.
시스템에 인위적으로 부하를 생성하는 경우, 클라이언트는 응답시간과 독립적으로 요청을 계속 보내야한다.
응답을 기다리고 다음 요청을 보내면 의미없다.

2.3 부하 대응 접근 방식

Scaling up(용량 확장, vertical scaling), scaling out(규모 확장, horizontal scaling)과 같은 방법이 있다.
탄력적인 시스템은 부하 증가를 감지하면 컴퓨팅 자원을 자동으로 추가한다.
그렇지 않은 시스템은 수동으로 확장해야 한다.
탄력적인 시스템은 부하를 예측할 수 없을 만큼 높은 경우 유용하지만 수동으로 확장하는 시스템이 더 간단하고 운영상 예상치 못한 일이 더 적다.
다수의 장비에 Stateless 서비스를 배포하는 일은 간단하다.
그러나 Stateful 시스템을 분산 설치하는 것은 복잡하다.
특정 애플리케이션에 적합한 확장성을 갖춘 아키텍처는 주요 동작이 무엇이고 잘 하지 않는 동작이 무언지에 대한 가정을 바탕으로 구축한다.
이 가정은 곧 부하 매개변수가 된다.

3. 유지보수성

소위 레거시 시스템 유지보수는 대부분의 소프트웨어 개발자가 싫어하기 때문에 설계에 주의를 기울여야한다.
좋은 소프트웨어 시스템 설계 원칙 세가지
운용성 = 운영팀이 원활하게 운영할 수 있도록 쉽게 만들어라
단순성 = 복잡도를 최대한 제거해서 만들어라
발전성 = 이후 시스템을 쉽게 변경할 수 있게하라.
속성 = 유연성, 수정가능성, 적응성

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

좋은 운영팀에서 책임지는 일
시스템 상태를 모니터링하고 상태가 좋지 않다면 빠르게 복원
시스템 장애, 성능 저하 등의 문제와 원인 추적
보안 패치를 포함해 소프트웨어와 플랫폼 최신 상태로 유지
시스템 간 영향을 확인해 문제 차단
문제를 예측해 미리 해결 (예를들어 용량 계획)
배포, 설정 관리 등을 위한 모범 사례와 도구 마련
플랫폼 이전과 같은 복잡한 유지보수 태스크 수행
설정 변경으로 인한 시스템 보안 유지보수
데이터 시스템에 포함된 다양한 일
좋은 모니터링으로 런타임 동작과 시스템 내부에 대한 가시성 제공
표준 도구를 이용해 자동화와 통합을 위한 우수한 지원 제공
개별 장비의 의존성 회피, 유지보수를 위해 장비를 내리더라도 시스템 전체에 영향을 주지 않고 계속해서 운영 가능해야 함.
좋은 문서와 이해하기 쉬운 운영 모델 제공
만족할 만한 기본 동작을 제공하고, 필요할 때 기본 값을 다시 정의할 수 있는 자유를 관리자에게 부여
적절하게 자기 회복이 가능할 뿐 아니라 필요에 따라 관리자가 수동으로 제어할 수 있게 함.
예측 가능하게 동작하고 예기치 않은 상황을 최소화 함.

3.2 단순성: 복잡도 관리

복잡도의 수렁에 빠진 소프트웨어 프로젝트를 때론 커다란 진흙 덩어리로 묘사한다.
복잡도는 다양한 형태로 나타난다.
상태 공간의 급증, 모듈 간 강한 커플링, 복잡한 의존성, 일관성 없는 명명, 성능 문제 해결을 목표로 한 해킹, 임시방편으로 문제를 해결한 특수 사례
개발자가 시스템을 이해하고 추론하기 어려워지면 시스템에 숨겨진 가정과 의도치 않은 결과 및 예기치 않은 상호작용을 간과하기 쉽다.
반대로 복잡도를 줄이면 소프트웨어 유지보수성이 크게 향상된다.
시스템을 단순하게 만드다는 것이 기능을 줄인다는 것이 아닌 우발적 복잡도를 줄인다는 것이다.
우발적 복잡도 = 사용자에게 보이는 문제에 내재하고 있지 않고 구현에서만 발생하는 것
우발적 복잡도를 제거하기 위한 가장 좋은 방법은 추상화다.
좋은 추상화는 깔끔하고 직관적인 외관 아래로 많은 세부 구현을 숨길 수 있다.

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

시스템 요구사항이 끊임없이 변화할 가능성이 크다.
해결 방법
변화에 적응하기 좋은 애자일, TDD, 리팩토링과 같은 기술 도구와 패턴을 사용한다.
발전성을 좋게 만드는 것은 시스템의 단순함과 추상화와 밀접한 연관이 있다.