본문 바로가기
스터디

[데이터 중심 애플리케이션 설계] 1장 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 어플리케이션

by 메릴린 2022. 12. 28.
728x90

[데이터 중심 애플리케이션 설계] 를 읽고 정리하는 글

 

데이터 중심 애플리케이션 설계 | 마틴 클레프만 - 교보문고

데이터 중심 애플리케이션 설계 | 데이터는 오늘날 시스템을 설계할 때 마주치는 많은 도전 과제 중에서도 가장 중심에 있다. 확장성, 일관성, 신뢰성, 효율성, 유지보수성과 같은 해결하기 어려

product.kyobobook.co.kr


애플리케이션 표준 구성요소(standard building block)

데이터 중심(data intensive) 애플리케이션은 많은 애플리케이션이 공통으로 필요로 하는 기능을 제공하는 표준 구성요소(standard building block)로 만든다

  • 데이터베이스 : 데이터 저장
  • 캐시 :  읽기 속도 향상
  • 검색 색인(search index) : 다양한 데이터 검색
  • 스트림 처리(stream processing) : 비동기 처리 위해 다른 프로세스에 메시지 보내기
  • 일괄 처리(batch processing) : 주기적으로 대량의 누적된 데이터 분석

 

좋은 시스템 설계를 위해 고려해야 할 세 가지

  1. 신뢰성 (Reliability) : 결함이 발생하더라도 최종 사용자가 어플리케이션 사용시 이를 알지 못하도록 시스템이 지속적으로 올바르게 동작해야 한다.
  2. 확장성 (Scalability): 시스템 부하에 잘 대처할 수 있어야 한다.
  3. 유지보수성 (Maintainability) : 계속되는 다양한 작업에 대해 모든 사용자가 시스템 상에서 생산적으로 작업할 수 있게 해야 한다.

 

1. 신뢰성 Reliability

"무언가 잘못되더라도 지속적으로 올바르게 동작함"

내결함성(fault-tolerant), 탄력성(resilient)

결함(잘못될 수 있는 일)을 예측하고 대처할 수 있는 시스템을 내결함성(fault-tolerant), 탄력성(resilient)을 지녔다고 말한다.

📌 장애(failure) vs 결함(fault)

장애와 결함은 다르다.
장애는 사용자에게 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 경우
대개 결함으로 인해 장애가 발생한다.
📌 넷플릭스의 카오스 몽키(Chaos Monkey)

내결함성을 지속적으로 테스트하기 위해 고의적으로 결함을 유도해 시스템을 훈련하고 테스트해서 결함이 자연적으로 발생했을 때 올바르게 처리할 수 있도록 하는 접근 방식의 한 예

결함(잘못)의 발생 원인

미흡한 오류 처리에 기인한다.

  1. 하드웨어 결함
  2. 소프트웨어 오류
  3. 인적 오류

하드웨어 결함에 대응

각 하드웨어 구성 요소에 "중복(redundancy)"을 추가

소프트웨어 오류에 대응

이런 종류의 버그는 특정 상황에 의해 발생하기 전까지 오랫동안 나타나지 않는다. 따라서 신속한 해결책이 없다.

 

시스템의 가정과 상호작용에 대해 주의 깊게 생각하기, 빈틈없는 테스트, 프로세스 격리(process isolation), 죽은 프로세스의 재시작 허용, 프로덕션 환경에서 시스템 동작의 측정, 모니터링, 분석하기와 같이 여러 작은 일들이 문제 해결에 도움이 될 수 있다.

 

지속적인 확인과 경고 발생을 통해 시스템 자체가 뭔가를 보장할 수도 있다.

시스템 내 체계적 오류 (systematic error)

  • 잘못된 특정 입력이 있을 때 모든 애플리케이션 서버 인스턴스가 죽는 소프트웨어 버그
  • CPU 시간, 메모리, 디스크 공간, 네트워크 대역폭처럼 공유 자원을 과도하게 사용하는 일부 프로세스
  • 시스템의 속도가 느려져 반응이 없거나 잘못된 응답을 반환하는 서비스
  • 한 구성 요소의 작은 결함이 다른 구송요소의 결함을 야기하고 차례차례 더 많은 결함이 발생하는 연쇄 장애(cascading failure)

인적 오류에 대응

대규모 인터넷 서비스에 대한 연구에 따르면 운영자의 설정 오류가 중단의 주요 원인이다.

 

최고의 시스템은 다양한 접근 방식을 결합한다.

  • 오류의 가능성을 최소화하는 방향의 시스템 설계 → 잘 설계된 추상화, API, 관리 인터페이스 등등을 사용
  • 사람이 가장 많이 실수해 장애가 발생할 수 있는 부분은 분리하라. 실제 데이터를 사용해 테스트하더라도 실제 사용자에게는 영향이 없는 비 프로덕션 샌드박스(sandbox)를 제공하라.
  • 단위 테스트, 전체 시스템 통합 테스트, 자동테스트, 수동테스트까지 테스트를 철저히 하라. 자동 테스트는 코너 케이스(corner case)를 다루는 데 유용하다.
  • 장애 발생의 영향을 최소화하기 위해 인적 오류를 빠르고 쉽게 복구할 수 있도록
    • 설정 변경 내역 빠르게 롤백(roll back), 새로운 코드 서서히 롤아웃(roll out)
    • 이전 계산이 잘못된 경우를 대비해 데이터 재계산 도구 제공
  • 모니터링 대책 마련
  • 조작 교육과 실습 실행

 

2. 확장성 (Scalability)

증가한 부하에 대처하는 시스템 능력

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

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

부하 기술하기 - 부하 매개변수(load parameter)

시스템의 특성에 따라 적합한 부하 매개변수를 선택해야 한다. 부하 매개변수를 통해 시스템의 현재 부하를 간결하게 기술해야 한다.

예시: 트위터 홈 타임라인

<트위터의 주요 두 가지 동작>

  • 트윗 작성 : '쓰기' 동작, 새로운 메시지 게시 (평균 초당 4.6k, 피크 시 초당 12k 이상)
  • 홈 타임라인 : '읽기' 동작, 팔로우한 사람이 작성한 트윗 보기 (초당 300k 요청)

<팬 아웃(fan-out) 문제>

한 사람이 '쓰기'를 해 트윗을 작성하면 이 글을 이 사람을 팔로워하고 있는 모든 사람의 홈 타임라인에 보여줘야 한다('읽기').

 

기존 방식 1.

트윗의 작성 → 트윗 전역 컬랙션에 삽입 → 임의의 사용자의 홈 타임라인 요청 → 매번 사용자의 팔로잉을 찾고, 팔로잉의 모든 트윗 찾아 보여주기

❌ 읽기에서 너무 많은 부하를 보인다. 

 

새로운 방식 2.

개별 사용자의 홈 타임라인 캐시 유지 트윗 작성 작성한 사용자의 팔로워 모두 찾기 팔로워의 개별 캐시에 해당 트윗 삽입

⭕ 읽기 요청량이 쓰기 요청량보다 수백 배 많기 때문에 읽기 시점에 더 적은 일을 해야 한다.

 

혼합형(hybrid) 접근 방식

팔로워 수가 매우 많은 소수 사용자의 경우 '쓰기'작업을 모든 팔로워의 캐시에 해야 하기 때문에 이는 또다른 부하를 낳는다.

⇒ 소수 사용자는 방식1.로 나머지는 방식2. 사용 (소수 사용자 팬 아웃에서 제외)

 

사용자당 팔로워의 분포가 팬 아웃 부하를 결정 ⇒ 핵심 부하 매개변수!

성능 기술하기

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

  • 부하 매개변수를 증가시키고 시스템 자원(CPU, 메모리, 네트워크 대역폭 등)은 변경하지 않고 유지하면 시스템 성능은 어떻게 영향을 받을까?
  • 부하 매개변수를 증가시켰을 때 성능이 변하지 않고 유지되길 원한다면 자원을 얼마나 많이 늘려야 할까?

성능 수치 필요!

  1. 처리량 (throughput) : 일괄 처리 시스템에 필요
  2. 응답 시간(response time) : 온라인 시스템에서 필요

응답 시간(response time)

클라이언트가 동일한 요청을 반복해서 하더라도 그 요청에 대한 응답 시간은 매번 같지 않다. 그러므로 응답시간은 단일 숫자가 아니라 측정 가능한 값의 분포로 생각해야 한다.

 

평균보다는 백분위를 사용하자

  • 중앙값(median) : p50, 사용자의 절반이 중앙값보다 느린 응답시간을 경험한다.
  • 꼬리 지연 시간(tail latency) : 95분위(p95), 99분위(p99), 99.9분위(p999) 와 같은 상위 백분위 응답시간, 95분위 응답시간이 1.5초라면 100개의 요청중 95개는 1.5초 미만이고 나머지 5개는 1.5초 이상이라는 듰이다.

꼬리 지연 시간은 서비스의 사용자 경험에 직접 영향을 주기 때문에 중요하다.

 

부하 대응 접근 방식

  1. 용량 확장 (scaling up) - 수직 확장(vertical scaling), 더 강력한 장비로의 이동
  2. 규모 확장 (scailing out) - 수평 확장(horizontal scaling), 다수의 낮은 사양 장비에 부하를 분산

실용적인 접근 방식의 조합: 둘 사이의 균형을 맞추어 시스템을 설계해야 한다.

 

자원 관리 방식

수동 vs. 자동(탄력적(elastic) 시스템)

 

부하가 예측할 수 없을 정도로 높아지는 경우 '자동'이 좋지만 그게 아닌 대부분의 경우 '수동'으로 확장해야 시스템이 더 간단하고 운영상 예상치 못한 일들이 적어진다.

 

대규모 시스템의 아키텍처

  • one-size-fits-all (범용적이고 모든 상황에 맞는) ❌
  • 특화된 ⭕

시스템에서 읽기의 양, 쓰기의 양, 저장할 데이터의 양, 데이터의 복잡도, 응답 시간 요구사항, 접근 패턴 등을 고려하고 주요 동작은 무엇인지, 잘 하지 않는 동작은 무엇인지 파악 

⇒ 부하 매개변수 결정

 

 

3. 유지보수성 (Maintainability)

버그 수정, 시스템 운영 유지, 장애 조사, 새로운 플랫폼 적응, 기능 추가 등등 시스템엔 유지보수가 지속적으로 필요하다.

 

유지보수는 귀찮음을 동반하는데 이를 최소화하기 위해 다음 세 가지 소프트웨어 시스템 설계 원칙에 주의를 기울여라.

  1. 운용성 (operability) : 좋은 운영, 원활한 운영
  2. 단순성 (simplicity) : 시스템 복잡도 제거
  3. 발전성 (evolvability) : 쉬운 시스템 변경, 유연성(extensibility), 수정 가능성(modifiability), 적응성(plasticity)

신뢰성과 확장성의 경우 쉬운 해결책이 없기에 보다 더 유지보수성을 염두에 두고 시스템을 생각하려 노력해야 한다.

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

"좋은 운영은 종종 나쁜(또는 불완전한) 소프트웨어의 제약을 피하는 대안이 될 수 있다. 하지만 좋은 소프트웨어라도 나쁘게 운영할 경우 작동을 신뢰할 수 없다."

 

반복되는 task들은 쉽게 수행되도록 할 것. 자동화할 수 있는 것들은 자동화 해야 한다.

단순성: 복잡도 관리

복잡도가 커질수록 시스템은 매우 복잡해지고 이해하기 어려워진다. 이에 따라 유지보수도 힘들어진다.

 

복잡도는 상태 공간의 급증, 모듈 간 강한 커플링(tight coupling), 복잡한 의존성, 일관성 없는 명명(naming)과 용어, 성능 문제 해결을 목표로 한 해킹, 임시방편으로 문제를 해결한 특수 사례 (special-casing) 등이 있다.

 

우발적 복잡도(accidental complexity)를 줄여야 한다.

📌 우발적 복잡도(accidental complexity)

소프트웨어가 풀어야 할 (사용자에게 보이는) 문제에 내재하고 있지 않고 구현에만 발생하는 것 - 모슬리, 마스크

 

좋은 추상화를 통해 해결 가능! : 재사용도 가능해 효율성도 높일 수 있음

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

계속 변화하는 요구사항에 대응

  1. 애자일 (agile)
  2. 테스트 주도 개발 (test-driven development (TDD))
  3. 리팩토링 (refactoring)

데이터 대규모 시스템 수준에서 민첩성(발전성)을 높이는 방법은 일반적으로 애자일 기법에서 초점을 맞추고 있는 '매우 작고, 로컬 규모'의 방법과는 다르다.

 

728x90
반응형

댓글