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, 리팩토링과 같은 기술 도구와 패턴을 사용한다.
◦
발전성을 좋게 만드는 것은 시스템의 단순함과 추상화와 밀접한 연관이 있다.