개발독서/데이터

[고투런1기] 데이터 중심 애플리케이션 설계 (1장 신뢰성/확장성/유지보수성 갖춘 애플리케이션)

보리시스템 2024. 5. 5.
<더 생각해보기>
- SLA(서비스 수준 협약서)에서 SLO(서비스 수준 목표)를 정하는 기준(응답 시간 중앙값 n초, 서비스 제공 시간 n.n% 이상)은 어떻게 정하는지?
* "응답 시간 중앙값이 200밀리초 미만이고 99분위가 1초 미만인 경우 정상 서비스 상태로 간주하며 서비스 제공 시간은 99.9% 이상이어야 한다."

 


 

[1부 데이터 시스템의 기초]
1장 신뢰성/확장성/유지보수성 갖춘 애플리케이션

1. 데이터 시스템에 대한 생각

2. 신뢰성
2.1 하드웨어 결함
2.2 소프트웨어 오류
2.3 인적 오류
2.4 신뢰성은 얼마나 중요할까?

3. 확장성
3.1 부하 기술하기
3.2 성능 기술하기
3.3 부하 대응 접근 방식

4. 유지보수성
4.1 운용성: 운영의 편리함 만들기
4.2 단순성: 복잡도 관리
4.3 발전성: 변화를 쉽게 만들기

 

[정리: 데이터 중심 애플리케이션 설계를 뒷받침하는 근본 개념을 설명]

1. 책 전반에 걸쳐 사용하는 전문 용어와 접근 방식을 소개

1. 신뢰성, 확장성, 유지보수성 같은 단어의 실제 의미와 이 같은 목표를 달성하기 위해 어떻게 해야 하는지 알아봄

- 애플리케이션이 유용하려면 기능적/비기능적 요구사항을 충족해야 함

- 비기능적 요구사항에는 신뢰성, 확장성, 유지보수성이 있음
1) 신뢰성: 결함이 발생해도 시스템이 올바르게 동작하도록 해야 함
=> 결함은 하드웨어 결함/소프트웨어 오류/인적 오류 등이 있음
=> 내결함성 기술은 최종 사용자에게 특정 유형의 결함을 숨길 수 있게 해줌

2) 확장성: 부하가 증가해도 좋은 성능을 유지하기 위한 전략
=> 양적인 부하(응답 시간 백분위 등), 성능과 관련
=> 확장 가능한 시스템에서는 부하가 높은 상태에서 신뢰성을 유지하기 위해 처리 용량을 추가할 수 있음

3) 유지보수성: 시스템을 작업하는 엔지니어와 운영 팀의 삶을 개선하는 데 있음
=> 좋은 추상화는 복잡도를 줄이고, 쉽게 시스템을 변경할 수 있게 하고, 새로운 사용 사례에 적용하는 데 도움이 됨
=> 좋은 운용성은 시스템의 건강 상태를 잘 관찰할 수 있고, 시스템을 효율적으로 관리하는 방법을 가지고 있다는 의미

 

<더 찾아본 내용>

소프트웨어 기술성 평가기준(과학기술정보통신부고시 제2018-83호, 2018. 11. 21., 일부개정)

 


 

1장 신뢰성/확장성/유지보수성 갖춘 애플리케이션

- 오늘날의 많은 애플리케이션은 계산 중심(compute-intensive)보다는 데이터 중심(data-intensive)적임

- 데이터 중심 애플리케이션은 공통으로 필요로 하는 기능을 제공하는 표준 구성 요소(standard bulding block)로 만듦
=> 데이터베이스, 캐시, 검색 색인, 스트림 처리, 일괄 처리(batch processing) 등

 


 

1. 데이터 시스템에 대한 생각

- 데이터베이스, 큐, 캐시 등은 매우 다른 범주에 속하는 도구이나 '데이터 시스템'이라는 포괄적인 용어로 묶는 이유?

1) 데이터 저장/처리를 위한 새로운 도구는 최근에 만들어져 더 이상 전통적인 분류에 딱 들어맞지 않음
2) 점점 더 많은 애플리케이션이 단일 도구로는 더 이상 데이터 처리/저장 모두를 만족시킬 수 없는 광범위한 요구사항을 가짐

 


 

2. 신뢰성

신뢰성(Reliability)?

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

 

- 결함으로 인한 장애가 발생하지 않도록 내결함성 구조를 설계하는 것이 가장 좋음
=> 결함 예방을 넘어 내결함성을 갖도록 하는 것이 일반적임
=> 하지만 보안의 경우 결함 예방이 해결책보다 더 좋음. 공격자가 시스템을 손상시키고 민감한 데이터에 대한 접근 권한을 가지면 이를 되돌릴 수 없음

- 이 책에서는 해결책이 있는 결함 유형을 다룸

 

결함 (fault)  
사양에서 벗어난 시스템의 한 구성 요소

=> 결함 확률을 0으로 줄이는 것은 불가능함
 
내결함성 (fault-tolerant)/
탄력성 (resilient)
결함을 예측하고 대처할 수 있는 시스템
장애 (failure)  
사용자에게 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 경우

=> 결함과는 다른 의미임
 

 


 

2.1 하드웨어 결함

하드웨어 결함?
하드디스크 고장, 램에 결함 발생, 대규모 정전 등 장비 상의 결함

=> 소프트웨어 결함과 달리 장비 간 상관관계는 약함 (한 장비에 장애가 있다고 다른 장비에 장애가 발생하지는 않음)

 

- 시스템 장애율을 줄이기 위한 대응책? 각 하드웨어 구성 요소의 이중화(redundancy)
=> 디스크를 RAID 구성으로 설치
* RAID(Redundant Arrays of Inexpensive Disks)? 개별 디스크드라이브를 묶어서 고가의 대용량 고성능 디스크 드라이브의 성능과 기술을 구현 하기 위해 만들어진 기술
=> 서버를 이중 전원 디바이스와 핫 스왑(hot-swap)이 가능한 CPU로
* 핫 스왑(hot-swap)? 작동 중인 시스템을 끄지 않고 부품을 교체 할수 있는 방식 (USB 등이 그 예)
=> 데이터센터는 건전지와 예비 전원용 디젤 발전기 마련하기

- 최근 데이터 양, 애플리케이션의 계산 요구가 늘어나며 더 많은 애플리케이션이 많은 장비를 사용하게 되었고 이에 따라 하드웨어 결함율도 증가함
=> 소프트웨어 내결함성 기술을 사용하거나 하드웨어 중복성을 추가해 전체 장비의 손실을 견딜 수 있는 시스템으로 점점 옮겨가고 있음

 



2.2 소프트웨어 오류

소프트웨어 오류?
시스템 내 체계적 오류(systematic error)

=> 예상하기 더 어렵고 노드 간 상관관계 때문에 하드웨어 결함보다 더 많이 발생

 

- 소프트웨어 오류는 신속한 해결책이 없음
1) 시스템의 가정과 상호작용에 대해 주의 깊게 생각하기
2) 빈틈없는 테스트
3) 프로세스 격리
4) 죽은 프로세스의 재시작 허용
5) 프로덕션 환경에서 시스템 동작의 측정/모니터링/분석하기 등

 



2.3 인적 오류

- 대규모 인터넷 서비스 관련 연구에 따르면 '운영자의 설정 오류'가 중단의 주요 원인이라고 함 (하드웨어 결함은 10~25%)

- 결국 시스템은 사람이 만드는 것이지만 다양한 접근 방식을 결합함으로써 시스템을 신뢰성 있게 만들 수 있음
1) 오류의 가능성을 최소화하는 방향으로 시스템을 설계해라
2) 샌드박스를 제공해라(실제 데이터를 사용해 안전하게 테스트할 수 있지만 실제 사용자에게는 영향 없는)
3) 모든 수준(단위, 통합 등)에서 철저하게 테스트해라
4) 장애 발생의 영향을 최소화하기 위해 인적 오류를 빠르고 쉽게 복구할 수 있도록 해라
5) 성능 지표, 오류율 등 상세하고 명확한 모니터링 대책을 마련해라
6) 조작 교육과 실습을 시행해라

 



2.4 신뢰성은 얼마나 중요할까?

- 비즈니스 애플리케이션에서 버그는 생산성 저하, 매출 손실, 명성 타격의 원인임

- 스타트업 등에서 증명되지 않은 시장에 대한 시제품을 개발하는 등의 상황에서 신뢰성을 덜 우선 해야 하는 경우, 그 '시점'에 대해 매우 잘 알고 있어야 함

 


 

3. 확장성

확장성(Scalability)?

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

 

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

- 확정성을 논한다는 것?

=> 시스템이 특정 방식으로 커지면 이에 대처하기 위한 방안은 무엇인가?
=> 추가 부하를 다루기 위해 연산 자원을 어떻게 투입할까?

 


 

3.1 부하 기술하기

- 부하는 부하 매개변수(load parameter)로 나타낼 수 있음

- 가장 적합한 부하 매개변수 선택은 시스템 설계에 따라 달라짐
=> 부하 매개변수로 웹 서버의 초당 요청 수, 데이터베이스의 읽기 대 쓰기 비율, 대화방의 동시 활성 사용자(active user), 캐시 적중률 등이 있음
=> 평균적인 경우가 중요할 수도 있고, 소수의 극단적인 경우가 병목 현상의 원인이 될 수도 있음

 

[사례] 트위터: '사용자당 팔로워 분포'가 확장성 논의 시 핵심 부하 매개변수가 됨
=> 버전 1 -> 버전 2 -> 혼합형(버전 1 + 2)으로 바뀜

1. 버전 1로만 사용했을 때, 시스템이 홈 타임라인 질의 부하를 버티기 힘들어 버전 2로 전환

=> 평균적으로 '트윗 작성 요청량 < 홈 타임라인 읽기 요청량'으로 수백 배 적기 때문에 버전 2가 더 효율적

2. 하지만 버전 2로만 사용했을 때, 트윗 작성에 많은 부가 작업이 필요하게 됨
=> 팔로워가 n천만 명인 경우, 하나의 트윗 작성이 홈 타임라인에 n천만 건 이상의 쓰기 요청이 될 수 있음

3. 이에 따라 버전 1과 2의 혼합형으로 전환
=> 대부분의 사용자는 버전 2, 팔로워가 매우 많은 소수의 사용자는 버전 1을 사용

 

  • 버전 1
    • 트윗 작성 시 새로운 트윗을 트윗 전역 컬렉션에 삽입
    • 홈 타임라인 요청 시 사용자의 팔로잉 사용자를 찾고, 이 사용자들의 모든 트윗을 찾아 시간순으로 정렬해 합침
SELECT tweets.*, users.* FROM tweets
  JOIN users ON tweets.sender_id = users.id
  JOIN follows ON follows.followee_id = users.id
  WHERE follows.follower_id = current_user

 

  • 버전 2 (홈 타임라인의 읽기 요청은 요청 결과를 미리 계산했기 때문에 비용이 저렴함)
    • 개별 사용자의 홈 타임라인 캐시를 유지
    • 트윗 작성 시 팔로워를 찾고, 팔로워 각 홈 타임라인 캐시에 새로운 트윗을 삽입

 


 

3.2 성능 기술하기

- 시스템 부하를 기술하면 부하가 증가할 때 어떤 일이 일어나는지 2가지 방법으로 살펴볼 수 있음
1) 부하 매개변수를 증가시켰을 때 시스템 자원(CPU, 메모리, 네트워크 대역폭 등)은 변경하지 않고 유지하면 시스템 성능은 어떤 영향을 받을까?
2) 부하 매개변수를 증가시켰을 때 성능이 변하지 않고 유지되길 원한다면 자원을 얼마나 많이 늘려야 할까?

=> 이를 위해서는 '성능 수치'가 필요함

 

- 시스템 성능
1) 일괄 처리 시스템은 '처리량(throughput)'이 중요
=> 초당 처리할 수 있는 레코드 수나 일정 크기의 데이터 집합으로 작업을 수행할 때 걸리는 전체 시간

2) 온라인 시스템은 서비스 '응답 시간(response time)'이 중요
=> 클라이언트가 요청을 보내고 응답을 받는 사이의 시간

 

  • 지연 시간과 응답 시간은 다른 의미임!
지연 시간 (latency)  
요청이 처리되길 기다리는 시간
=> 서비스를 기다리며 휴지(latent) 상태인 시간
 
응답 시간 (response time)  
클라이언트 관점에서 본 시간
=> 요청을 처리하는 실제 시간(서비스 시간) 외에도 네트워크 지연, 큐 지연도 포함하는 시간
 

 

  • 응답 시간은 단일 숫자가 아닌 측정 가능한 값의 '분포'
    • 응답 시간 데이터를 집계하는 올바른 방법은 히스토그램을 추가한 것임
응답 시간이 다양하게 분포되는 원인?  
- 백그라운드 프로세스의 컨텍스트 스위치
- 네트워크 패킷 손실과 TCP 재전송
- 가비지 컬렉션 휴지(pause)
- 디스크에서 읽기를 강제하는 페이지 폴트
- 서버 랙의 기계적인 진동 등
 

 

  • 특이 값(outlier)이 얼마나 좋지 않은지 보기 위해서는 '상위 백분위(95분위, 99분위, 99.9분위)'를 확인
    • 95분위 응답 시간이 1.5초? 100개의 요청 중 95개는 1.5초 미만, 5개는 1.5초보다 더 걸림
    • 서비스 수준 협약서(SLA, Service Level Agreement), 서비스 수준 목표(SLO, " Objective)에 자주 사용 됨
      • "응답 시간 중앙값이 200밀리초 미만이고 99분위가 1초 미만인 경우 정상 서비스 상태로 간주하며 서비스 제공 시간은 99.9% 이상이어야 한다."
꼬리 지연 시간 (tail latency)  
- 상위 백분위 응답 시간

- 서비스의 사용자 경험에 직접 영향을 주기 때문에 중요함
=> 아마존은 응답 시간이 100밀리초 증가하면 판매량이 1% 줄어들고, 1초가 느려지면 고객 만족도 피교가 16% 줄어들었다고 함
 

 

  • 큐 대기 지연(queueing delay)은 높은 백분위에서 응답 시간의 상당 부분을 차지함
    • '선두 차단' 발생
    • 서버에서 후속 요청이 빠르게 처리되더라도 이전 요청이 완료되길 기다리는 시간 때문에 클라이언트는 전체적으로 응답 시간이 느리다고 생각할 것임
선두 차단 (head-of-line blocking)  
서버는 병렬로 소수의 작업만 처리할 수 있기 때문에 소수의 느린 요청 처리만으로도 후속 요청 처리가 지체되는 현상


=> 꼬리 지연 증폭(tail latency amplification) 효과
 

 


 

3.3 부하 대응 접근 방식

- 부하 매개변수가 증가해도 좋은 성능을 유지하려면?
=> 아키텍처의 확장 필요

 

  • 확장성
    • 현실적으로 좋은 아키텍처는 실용적인 접근 방식의 조합이 필요함
    • 예를 들어
      • 적절한 사양의 장비 몇 대가 다량의 낮은 사양 가상 장비보다 훨씬 간단하고 저렴함
      • 탄력적인 시스템(부하 증가 감지 시 자동으로 컴퓨팅 자원 추가)은 부하를 예측할 수 없을 만큼 높은 경우 유용하지만 수동으로 확장하는 시스템이 더 간단하고 운영상 예상치 못한 일이 더 적음
    • 대개 대규모로 동작하는 시스템의 아키텍처는 해당 시스템을 사용하는 애플리케이션에 특화돼 있음
      • 범용적이고 모든 상황에 맞는(one-size-fits-all) 확장 아키텍처(비공식적으로는 마법의 확장 소스 - magic scaling source)는 없음 
      • 아키텍처를 결정하는 요소는 읽기의 양, 쓰기의 양, 저장할 데이터의 양, 데이터의 복잡도, 응답 시간 요구사항, 접근 패턴 등이 있음
용량 확장 (scaling up) /
수직 확장 (vertical scaling)
좀 더 강력한 장비로의 이동
규모 확장 (scaling out) / 
수평 확장 (horizontal scaling)

다수의 낮은 사양 장비에 부하를 분산

=> 다수의 장비에 부하를 분산하는 아키텍쳐를 '비공유(shared-nothing) 아키텍처'라고 함
 

 


 

4. 유지보수성

유지보수성(Maintainability)?

시간이 지남에 따라 여러 다양한 사람들이 시스템 상에서 작업할 것이기 때문에 모든 사용자가 시스템 상에서 생산적으로 작업할 수 있게 해야 함

 

- 소프트웨어 비용의 대부분은 초기 개발이 아닌 '유지보수'에 들어감
=> 버그 수정, 시스템 운영 유지, 장애 조사, 새로운 플랫폼 적응, 새로운 사용 방식을 위한 변경, 기술 채무(technical debt) 상환, 새로운 기능 추가 등

- 유지보수성을 높이기 위한 소프트웨어 시스템 설계 원칙 3가지?
=> 운용성(operability), 단순성(simplicity), 발전성(evolvability)

 


 

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

운용성(operability)?

운영팀이 시스템을 원활하게 운영할 수 있게 쉽게 만들기

 

- 좋은 운영성?
=> 동일하게 반복되는 테스크를 쉽게 수행하게끔 만들어 운영팀이 고부가가치 활동에 집중할 수 있도록

 

  • 좋은 모니터링으로 런타임 동작, 시스템 내부에 대한 가시성 제공
  • 표준 도구 이용해 자동화, 통합을 위한 좋은 자원 제공
  • 유지보수를 위해 장비를 내리더라도 시스템 전체에 영향을 주지 않고 계속 운영 가능해야 함
  • 좋은 문서와 이해하기 쉬운 운영 모델(X를 하면 Y가 발생한다) 제공
  • 만족할 만한 기본 동작을 제공하고, 필요할 때 기본값을 다시 정의할 수 있는 자유를 관리자에게 부여
  • 적절한 자기 회복이 가능하고, 필요에 따라 관리자가 시스템 상태를 수동으로 제어할 수 있게 함
  • 예측 가능하게 동작하고 예기치 않은 상황을 최소화함

 


 

4.2 단순성: 복잡도 관리

단순성(simplicity)?

시스템에서 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들기
=> 사용자 인터페이스의 단순성과는 다름!

 

  • 복잡도
복잡도 증상
- 상태 공간의 급증

- 모듈 간 강한 커플링
- 복잡한 의존성
- 일관성 없는 명명, 용어
- 성능 문제 해결을 목표로 한 해킹
- 임시방편으로 문제를 해결한 특수 사례 등
 
복잡도의 문제
- 시스템 유지보수가 어려울 때 예산, 일정 초과

- 복잡한 소프트웨어에서의 변경 시 버그 위험성 높음
 

 

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

- 좋은 추상화?
1) 깔끔하고 직관적인 외관 아래로 많은 세부 구현을 숨길 수 있음
2) 다른 다양한 애플리케이션에서도 사용 가능한 재사용성

 


 

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

발전성(evolvability)?

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

 

- 시스템의 요구사항은 끊임없이 변할 가능성이 큼

- 데이터 시스템 변경을 쉽게 하고 변화된 요구사항에 시스템을 맞추는 방법은 시스템의 간단함과 추상화와 밀접한 관련이 있음