본문 바로가기
데이터 중심 애플리케이션 설계

신뢰성(Reliability), 확장성(Scalability), 유지보수성(Maintainability)

by 원준식 2023. 3. 2.

데이터 중심 애플리케이션 설계를 읽고 작성하는 글입니다.

 


 

책에서는 소프트웨어 시스템에서 중요하게 여기는 신뢰성, 확장성, 유지보수성에 대해 다룹니다.

 

1. 신뢰성

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

 

'소프트웨어를 신뢰한다'는 어떤 의미일까요?

 

책에서는 "무언가 잘못되더라도 지속적으로 올바르게 동작함"을 신뢰성의 의미로 표현합니다.

 

  • 결함(fault): 잘못될 수 있는 일, 사용에서 벗어난 시스템의 한 구성 요소
  • 장애(failure): 사용자에게 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 경우
  • 내결함성(fault-tolerant) 또는 탄력성(resilient)을 지님 = 결함을 예측하고 대처할 수 있음

 

이때 내결함성은 모든 결함을 견딜 수 있다는 뜻이 아닙니다. 블랙홀이 지구를 삼켰을 때도 작동할 수 있는 시스템은 존재할 수 없기 때문입니다. 따라서 특정 유형의 결함에 관해서만 이야기하는 것이 타당합니다.

 

결함 확률을 0으로 만드는 것은 불가능하기 때문에 결함으로 인한 장애가 발생하지 않도록 내결함성 구조를 설계하는 것이 가장 좋습니다.

 

내결함성 구조를 위해 고의로 결함을 유도할 수 있습니다. 실제로 많은 중대한 버그가 미흡한 오류처리에 기인하기 때문에 고의로 일으킨 결함을 통해 시스템을 지속적으로 훈련하고 테스트할 수 있습니다. 넷플릭스의 카오스 몽키가 이런 접근 방식의 예입니다.

 

1-1. 하드웨어 결함

 

하드디스크 고장, 램에 결함 발생, 정전, 실수로 케이블을 뽑는 행위 등의 결함을 하드웨어 결함이라고 합니다. 많은 장비를 다루는 규모가 큰 데이터센터에서는 늘 일어나는 일이라고 합니다.

 

하드웨어 결함을 통해 생기는 시스템 장애율을 줄이기 위해 사용하는 일반적인 방법으로는 각 하드웨어 구성 요소에 중복(redundancy)을 추가하는 방법이 있습니다. 이 방법을 사용하면 구성 요소 하나가 죽어 교체해야 한다면 그동안 중복된 구성 요소를 대신 사용할 수 있게 됩니다. 또한 장비를 재부팅 해야 하는 경우(예를 들어 운영체제 보안 패치 적용) 전체 시스템의 중단시간 없이 할 수 있게 됩니다.

 

1-2. 소프트웨어 오류

 

위에서 다룬 하드웨어 결함은 무작위적이고 서로 독립적이라고 생각합니다. 어떤 장비에 장애가 생겼다고 해서 다른 장비에 같은 장애가 거의 생기지 않는다는 뜻입니다. 아예 생기지 않는 것은 아닐 것입니다. 예를 들어 같은 공간에 있는 장비들인데 온도가 원인이라면 같은 장애가 생길 수도 있습니다. 하지만 동시에 다수의 하드웨어 구성 요소에 장애가 발생하지는 않습니다.

 

다른 부류의 결함으로 시스템 내 체계적 오류(systematic error)가 있습니다. 이 결함은 예상하기 어렵고 상관관계가 있기 때문에 하드웨어 결함보다 시스템 오류를 더 유발하는 경향이 있습니다.

 

소프트웨어의 체계적 오류 문제는 신속한 해결책이 없습니다. 책에서는 아래와 같은 방법이 도움을 줄 수 있다고 합니다.

 

  • 시스템의 가정과 상호작용에 대해 주의 깊게 생각하기
  • 빈틈없는 테스트
  • 프로세스 격리(process isolation)
  • 죽은 프로세스의 재시작 허용
  • 프로덕션 환경에서 시스템 동작의 측정
  • 모니터링
  • 분석하기

 

1-3. 인적 오류

 

사람이 시스템을 어떻게 신뢰성 있게 만들 수 있을까요?

 

  • 오류의 가능성을 최소화하는 방향으로 시스템을 설계하기
  • 사람이 실수하는 부분에서 장애가 발생할 수 있는 부분을 분리하기
    • 샌드박스(실제 데이터를 사용해 실험해도 실제 사용자에게는 문제가 없는 연습장 같은 공간) 제공
  • 모든 수준에서 철저하게 테스트하기
    • 단위 테스트부터 전체 시스템 통합 테스트까지
    • 수동 테스트, 자동 테스트(자동 테스트가 무조건 좋은 것은 아닙니다.)
    • 코너 케이스를 다루는 데 유용함
  • 장애 발생 영향을 최소화하기 위해 인적 오류를 빠르고 쉽게 복구할 수 있게 하기
  • 성능 지표와 오류율 같은 상세하고 명확한 모니터링 대책을 마련하기
    • 조기에 경고 신호를 보내줄 수 있고 특정 가정이나 제한을 벗어나는지 확인할 수 있음
    • 문제 발생 시 지표는 문제 분석에 매우 중요함

 

 

2. 확장성

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

 

확장성은 증가한 부하에 대처하는 시스템의 능력을 설명하는 용어입니다. 다만 일차원적으로 '확장 가능', '확장성 없음' 등의 말은 의미가 없을 것입니다. 대신 '부하가 커졌을 때 어떤 선택을 할 것인가?' 와 같은 말이 적절할 것입니다.

 

2-1. 부하 기술하기

 

부하에 대한 질문을 할 수 있도록 시스템의 현재 부하를 간결하게 기술할 수 있어야 합니다. 부하는 시스템 설계에 맞는 부하 매개변수(load parameter)로 나타낼 수 있습니다. 웹 서버의 초당 요청 수, 데이터베이스의 읽기 대 쓰기 비율 등이 될 수 있습니다.

 

책에서는 트위터의 사례를 들어 부하 매개변수에 관해 설명합니다.

 

사용자의 트위터 홈 타임라인을 어떻게 띄울 수 있을까요? 책에서는 두 가지 방법을 소개합니다.

 

a. 사용자가 자신의 홈 타임라인을 요청하면 팔로우하는 모든 사람을 찾고, 이 사람들의 모든 트윗을 찾아 시간순으로 정렬해서 합친다.

b. 사용자의 홈 타임라인 캐시를 유지하고 누가 글을 올리면 캐시에 새로운 트윗을 삽입한다.

 

트위터는 1번 방법에서 2번 방법으로 전환했다고 합니다. 쓰기 시점에 많은 일을 하고, 읽기 시점에 적은 일을 하는 것입니다. 하지만 팔로워가 매우 많은 사용자의 경우 2번 방법이 불리할 수 있습니다. 따라서 트위터는 2번 방법을 견고하게 구현한 뒤 두 방법의 혼합형으로 바꾸고 있다고 합니다. (2012년 데이터 기준)

트위터의 사례가 중요하다기보다는 부하 매개변수가 무엇인지 알기 위한 예시 사례로 이해하면 됩니다. 위 사례의 경우 사용자당 팔로워의 분포 또는 사용자의 트윗 빈도 등이 부하를 결정할 것입니다. 따라서 이런 요소들이 핵심 부하 매개변수가 될 것입니다.

 

2-2. 성능 기술하기

 

시스템 부하를 기술할 수 있다면 부하가 증가할 때 어떤 일이 일어나는지 알아봐야 할 것입니다.

 

  • 시스템 자원은 유지한 채로 부하 매개변수를 증가시키면 시스템 성능이 어떻게 될까?
  • 부하 매개변수를 증가시켰을 때 성능이 변하지 않게 하려면 자원을 얼마나 늘려야 할까?

 

앞에서 부하 매개변수는 기술했으니 추가로 성능 수치를 기술할 수 있으면 됩니다. 성능 수치로는 처리량(throughput), 응답 시간(response time) 등이 있을 것이고 어떤 시스템인지에 따라 그에 맞는 성능 수치를 사용해야 할 것입니다. 책에서는 응답 시간의 예시를 들어 설명하고 있습니다.

 

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


클라이언트의 요청에 대해 응답 시간은 항상 다릅니다. 따라서 응답 시간은 어떤 값이라기보다 값들의 분포로 생각해야 합니다. 이때 얼마나 많은 사용자가 실제로 지연을 경험했는지 알고 싶다면 응답 시간의 평균보다는 백분위를 사용하는 것이 좋습니다. 예를 들어 95분위의 응답 시간이 1.5초라면 100개의 응답 중 5개가 1.5초보다 오래 걸렸다는 뜻입니다. 이러한 상위 백분위 응답 시간을 꼬리 지연 시간(tail latency)이라고 하고 이는 사용자 경험에 직접 영향을 주기 때문에 중요합니다.

백분위는 서비스 수준 목표(service level objective, SLO)와 서비스 수준 협약서(service level agreement, SLA)에 자주 사용하고 기대 성능과 서비스 가용성을 정의하는 계약서에도 자주 등장합니다. 다음은 서비스 수준 협약서의 예입니다. '응답 시간 중앙값이 200밀리초 미만이고 99분위가 1초 미만인 경우 정상 서비스 상태로 간주하며 서비스 제공 시간은 99.9% 이상이어야 한다.'

큐 대기 지연(queueing delay)은 높은 백분위에서 응답 시간의 상당 부분을 차지합니다. 서버는 소수의 작업만을 병렬로 처리할 수 있기 때문에 후속 요청 처리가 지체될 수 있고 이를 선두 차단(head-of-line blocking)이라고 합니다. 따라서 클라이언트 관점에서 본 시간인 응답 시간이 중요하고 응답 시간 데이터를 올바르게 집계하기 위해 히스토그램을 사용할 수 있습니다.

 

2-3. 부하 대응 접근 방식

 

부하와 성능을 기술했기 때문에 확장성에 대해 논의할 수 있습니다. 보통 확장성을 말할 때 아래와 같이 구분합니다.

 

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


단일 장비에서 해결할 수 있다면 간단하겠지만 고사양 장비는 매우 비쌉니다. 따라서 규모 확장을 피할 순 없습니다. 대개 고가용성 요구가 있기 전까지는 용량 확장을 하는 것이 통념이라고 합니다.

일부 시스템은 부하가 증가하면 자원을 자동으로 추가하는 탄력적(elastic)인 시스템입니다. 이는 부하를 예상할 수 없을 때 유용하겠지만 수동으로 확장하는 것이 간단하고 예상치 못한 일이 덜 발생합니다.

모든 상황에 맞는 시스템 아키텍처는 없습니다. 읽기의 양, 쓰기의 양, 저장할 데이터의 양, 데이터의 복잡도 등 필요한 조건에 특화된 아키텍처를 사용해야 할 것입니다. 이를 위해 아키텍처의 주요 동작이 무엇인지, 잘 하지 않는 동작이 무엇인지 아는 것이 중요합니다. 이는 곧 부하 매개변수가 되기 때문입니다.

이러한 특화된 아키텍처는 보통 익숙한 패턴으로 나열된 범용적인 구성 요소로 구축합니다. 따라서 책에서는 이러한 구성 요소와 패턴에 대해 다룹니다.

 

 

 

3. 유지보수성

시간이 지남에 따라 여러 다양한 사람들이 시스템상에서 작업(현재 작업을 유지보수하고 새로운 사용 사례를 시스템에 적용하는 엔지니어링과 운영)할 것이기 때문에 모든 사용자가 시스템상에서 생산적으로 작업할 수 있게 해야 한다.

 

소프트웨어 비용의 대부분은 초기 개발이 아니라 유지보수에 들어간다고 합니다. 하지만 많은 사람이 소위 레거시 시스템 유지보수를 좋아하지 않습니다. 따라서 레거시 시스템을 만들지 않게끔 소프트웨어를 설계하는 것이 중요합니다. 이를 위해 신경 써야 할 세 가지 소프트웨어 시스템 설계 원칙은 다음과 같습니다.

 

  • 운용성(operability): 운영팀이 시스템을 원활하게 운영할 수 있도록 쉽게 만들기
  • 단순성(simplicity): 시스템에서 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들기(인터페이스의 단순성을 말하는 것이 아님)
  • 발전성(evolvability): 엔지니어가 이후에 시스템을 쉽게 변경할 수 있게 하기(유연성(extensibility), 수정 가능성(modifiability), 적응성(plasticity))

 

3-1. 운용성: 운영의 편리함 만들기

 

  • 시스템 상태를 모니터링하고 상태가 좋지 않다면 빠르게 서비스를 복원
  • 시스템 장애, 성능 저하 등의 문제 원인을 추적
  • 보안 패치를 포함해 소프트웨어와 플랫폼을 최신 상태로 유지
  • 다른 시스템이 서로 어떻게 영향을 주는지 확인해 문제가 생길 수 있는 변경 사항을 문제 전에 차단
  • 미래에 발생 가능할 수 있는 문제를 예측해 문제 발생 전에 해결(예를 들어 용량 계획)
  • 배포, 설정 관리 등을 위한 모범 사례와 도구 준비
  • 애플리케이션을 특정 플랫폼에서 다른 플랫폼으로 이동하는 등의 복잡한 유지보수 수행
  • 설정 변경으로 생기는 시스템 보안 유지보수
  • 예측할 수 있는 운영과 안정적인 서비스 환경 유지를 위한 절차 정의
  • 개인 인사이동에도 시스템에 대한 조직의 지식 보존

 

좋은 운영팀은 위와 같은 작업 등을 책임집니다. 또한 위 작업 중 일부는 자동화할 수 있고 자동화해야만 합니다. 반복되는 작업을 쉽게 수행할 수 있게 한다면 운영팀이 고부가가치 활동에 집중할 수 있게 되기 때문입니다. 데이터 시스템의 동일 반복 작업을 쉽게 하기 위해 아래와 같은 일을 할 수 있습니다.

 

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

 

3-2. 단순성: 복잡도 관리

 

모듈 간 강한 커플링, 복잡한 의존성, 일관성 없는 명명과 용어 등 복잡도는 다양한 증상으로 나타납니다. 시스템을 단순하게 만드는 것과 기능을 줄이는 것은 다릅니다. 우발적 복잡도(accidental complexity)를 줄인다는 의미로 볼 수 있습니다. 책에서는 우발적 복잡도를 소프트웨어가 풀어야 할 문제가 아닌 구현에서만 발생하는 것으로 소개합니다.

우발적 복잡도를 제거하기 위한 최상의 도구는 추상화입니다. 좋은 추상화는 깔끔하고 직관적인 외관 아래로 많은 세부 구현을 숨길 수 있습니다. 또한 좋은 추상화는 다른 애플리케이션에서도 사용할 수 있기 때문에 효율적이고 이는 고품질로 이어집니다. 예를 들어 고수준 프로그래밍 언어는 기계어, CPU 레지스터, 시스템 호출을 숨긴 추상화입니다. (사용자가 직접 사용하지 않을 뿐입니다.)

 

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

 

예상치 못한 사례 발생, 새로운 기술 출시, 비즈니스 우선순위의 변화, 사용자의 새로운 요청 등 다양한 이유로 시스템의 요구사항은 끊임없이 변할 가능성이 큽니다.

애자일 커뮤니티는 자주 변화하는 환경에서의 소프트웨어 개발에 도움이 되는 기술 도구와 패턴을 개발하고 있습니다. (테스트 주도 개발(test-driven development, TDD), 리팩토링(refactoring) 등)