데이터중심 애플리케이션 설계 #1 - 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션
본문 바로가기


Programmer/hadoop

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

오늘날 많은 애플리케이션은 계산 중심과는 다르게 데이터 중심적이다. 일반적으로 데이터 중심 애플리케이션은 공통으로 필요로 하는 기능을 제공하는 표준 구성 요소로 만든다. 예를들어 다음과 같다.

  • 구동 애플리케이션이나 다른 애플리케이션에서 나중에 다시 데이터를 찾을 수 있게 데이터를 저장 - 데이터베이스
  • 읽기 속도 향상을 위해 값비싼 수행 결과를 기억 - 캐시
  • 사용자가 키워드로 데이터를 검색하거나 다양한 방법으로 필터링할 수 있게 제공 - 검색 색인
  • 비동기 처리를 위해 다른 프로세스로 메시지 보내기 - 스트림 처리
  • 주기적으로 대량의 누적된 데이터를 분석 - 일괄 처리

이번 장에서는 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 데이터 시스템을 구축하기 위한 기초적인 노력이 무엇인지 살펴보고, 그 노력들인 신뢰성, 확장성, 유지보수성의 의미를 명확히 하고 이를 고려하는 몇 가지 방법을 개략적으로 설명하고 이후 장에서 필요한 기본 사항을 검토한다. 

데이터 시스템에 대한 생각

전통적인 분류에 딱 들어맞지 않는, 다양한 사용 사례에 최적화된 새로운 도구. 그리고 단일 도구로는 더 이상 데이터 처리와 저장 모두를 만족시킬 수 없는 과도하고 광범위한 요구사항을 갖는 애플리케이션이 점점 더 많이 등장하고 있다. 

작업은 단일 도구에서 수행할 수 있는 태스크로 나누고, 다양한 도구들은 애플리케이션 코드를 이용해 서로 연결한다. 서비스 제공을 위해 각 도구를 결합할 때, 서비스 인터페이스나 애플리케이션 프로그래밍 인터페이스(API)는 보통 클라이언트가 모르게 구현 세부 사항을 숨긴다. 이제부터 개발자는 애플리케이션 개발자뿐만 아니라 데이터 시스템 설계자이기도 하다.

데이터 시스템이나 서비스를 설계할 때 까다로운 문제가 많이 생긴다.

  • 내부적으로 문제가 있어도 데이터를 정확하고 완전하게 유지하려면?
  • 시스템의 일부 성능이 저하되더라도 클라이언트에 일관되게 좋은 성능을 어떻게 제공할까?
  • 부하 증가를 다루기 위해 어떻게 규모를 확장할까?
  • 서비스를 위해 좋은 API는 어떤 모습일까?

이 책에서는 대부분의 소프트웨어 시스템에서 중요하게 여기는 세 가지 관심사에 중점을 둔다.

  • 신뢰성
  • 확장성
  • 유지보수성

엔지니어링 관점에서 신중하게 신뢰성, 확장성, 유지보수성을 생각하는 방법을 살펴보자.

신뢰성

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

  • 결함: 잘못될 수 있는 일. 사양에서 벗어난 시스템의 한 구성 요소. (!= 장애)
  • 내결함성, 탄력성: "특정 유형"의 결함을 예측하고 대처할 수 있는 시스템
  • 장애: 사용자에게 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 경우

결함 확률을 0으로 줄이는 것은 불가능하다. 따라서 대개 결함으로 인해 장애가 발생하지 않게끔 내결함성 구조를 설계하는 것이 가장 좋다. 이 책에서는 신뢰할 수 없는 여러 부품으로 신뢰할 수 있는 시스템을 구축하는 다양한 기법을 다룬다.

결함 중 보안 문제처럼 해결책이 없는 결함도 있다. 여기서는 해결책이 있는 결함 유형만 다룬다.

하드웨어 결함

하드디스크의 평균 장애 시간은 약 10~50년으로 보고됐다. 따라서 10,000개의 디스크로 구성된 저장 클러스터는 평균적으로 하루에 한 개의 디스크가 죽는다고 예상해야 한다. 시스템 장애율을 줄이기 위한 대응은 하드웨어 각 구성 요소에 중복을 추가하는 것과, 다중 장비로 하드웨어 중복성을 추가하거나 소프트웨어 내결함성 기술을 사용하는 것이 있다.

  • 하드웨어 각 구성 요소 중복: 디스크는 RAID, 서버는 이중 전원 디바이스와 핫 스왑이 가능한 CPU, 데이터 센터는 건전지와 예비 전원용 디젤 발전기를..
  • 다중장비 중복: 여러 장비에 가상 장비 인스턴스를 띄워서 관리하는 등.. 단일 장비 신뢰성보다 유연성과 탄력성을 우선적으로 처리하게 설계

데이터 양과 애플리케이션의 계산 요구가 늘어나면서 더 많은 수의 장비를 사용하고, 하드웨어 자체의 결함률이 증가하면서 하드웨어 각 구성 요소를 중복하는 것이 아니라 내결함성 기술을 사용하거나 하드웨어 중복성을 추가해 전체 장비의 손실을 견딜 수 있는 시스템으로 점점 옮겨가고 있다. 

소프트웨어 오류

보통 하드웨어 결함은 약하게 상관관계가 있거나 독립적이다. 하지만 시스템 내 체계적 오류는 예상하기가 더 어렵고, 노드간 상관관계가 있어 시스템 오류를 더 많이 유발하는 경향이 있다.
예를들어..

  • 잘못된 특정 입력이 있을 때 모든 애플리케이션 서버 인스턴스가 죽는 소프트웨어 버그. 리눅스 커널의 버그로 2012년 6월 30일 윤초에 발생한 버그.
  • CPU 시간, 메모리, 디스크 공간, 네트워크 대역폭같은 공유 자원을 과도하게 많이 사용하는 프로세스
  • 시스템의 속도가 느려져 반응이 없거나 잘못된 응답을 하는 서비스
  • 한 구성 요소의 작은 결함이 연쇄적으로 많은 결함을 발생시키는 연쇄 장애

이런 상황으로 볼 때 소프트웨어는 환경에 대한 일정의 가정이 있다는 사실을 알 수 있다.

신속한 해결책은 없다. 주의깊게 생각하고, 빈틈없이 테스트하고, 프로세스를 격리하거나 죽은 프로세스를 재 시작 하는 정책을 세우거나, 프로덕션 환경에서 시스템 동작의 측정, 모니터링, 분석하기와 같은 여러 작은 일들과 경고를 발생시키는 프로세스를 만드는 일들이 도움이 될 수는 있다.

인적 오류

사람이 미덥지 않음에도 시스템을 어떻게 신뢰성 있게 만들까?

  • 오류의 가능성을 최소화하는 방향으로, 잘 설계된 추상화, API, 관리 인터페이스를 "적당히" 개발하라. 
  • sandbox를 제공하라.
  • 단위 테스트 부터 전체 시스템 통합 테스트와 수동 테스트까지 모든 수준에서 철저하게 테스트하라
  • 빠르게 롤백하고 천천히 롤백할 수 있게 만들고, 이전 계산이 잘못된 경우를 대비해 데이터 재계산 도구를 제공하라
  • 성능 지표와 오류율 같은 상세하고 명확한 모니터링 대책을 마련하라.
  • 조작 교육과 실습을 시행하라.

확장성

증가한 부하에 대처하는 시스템 능력을 설명하는데 사용하는 용어이다. 확장 가능하다 / 불가능 하다 같은 말보다 오히려 "시스템이 특정 방식으로 커지면 이에 대처하기 위한 선택은 무엇인가?", "추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?" 같은 질문을 고려한다는 의미이다.

부하 기술하기

시스템의 현재 부하를 간결하게 기술 할 수 있는 부하 매개변수를 정하고, 부하 성장 질문을 논의할 수 있다. 가장 적합한 부하 매개변수 선택은 시스템 설계에 따라 달라진다.

  • 웹 서버의 초당 요청 수
  • 데이터베이스의 읽기 대 쓰기 비율
  • 대화방의 동시 활성 사용자
  • 캐시 적중률
  • 평균.. 혹은 소수의 극단적인 경우.. (트위터)

성능 기술하기

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

  • 부하 매개변수를 증가시키고 시스템 자원은 변경하지 않고 유지하면 시스템 성능은 어떻게 영향을 받을까?
  • 부하 매개변수를 증가시켰을 때 성능이 변하지 않고 유지되길 원한다면 자원을 얼마나 많이 늘려야 할까?

성능 수치는 시스템의 유형별로 관심을 갖는 경우가 다르다.

  • 하둡 같은 일괄 처리 시스템은 처리량(throughput): 초당 처리할 수 있는 레코드 수나 일정 크기의 데이터 집합으로 작업을 수행할 때 걸리는 전체 시간
  • 온라인 시스템은 응답 시간(response time): 클라이언트가 요청을 보내고 응답을 받는 사이의 시간. 매번 다르므로, 분포로 생각해야함.

응답 시간의 경우, 백그라운드 프로세스의 컨택스트 스위치, 네트워크 패킷 손실과 TCP 재전송, 가비지 컬렉션 휴지, 디스크에서 읽기를 강제하는 페이지 폴트, 서버 랙의 기계적인 진동 등의 다른 여러 원인으로 추가 지연이 생길 수 있다.

보고된 서비스 응답 시간을 "평균"으로 살피는 것은 일반적이지만, 백분위를 사용하는 편이 더 좋다. 사용자가 보통 얼마나 오랫동안 기다려야 하는지 알고 싶다면 중앙값이 좋은 지표다. 50분위로서 p50으로 축약할 수 있다.

특이 값이 얼마나 좋지 않은지 알아보려면 상위 백분위를 살펴보는 것도 좋다. 이때 사용하는 백분위는 95분위, 99분위, 99.99분위가 일반적이다. 해당 응답시간들은 꼬리 지연 시간이다. 요구사항을 어디까지 정할지는 서비스와 상황에 따라 다르다.

백분위는 서비스 수준 목표(service level objective, SLO)서비스 수준 협약서(service level agreement, SLA)에 자주 사용하고 기대 성능과 서비스 가용성을 정의하는 계약서에도 자주 등장한다.

그 중 큐 대기 지연은 높은 백분위에서 응답 시간의 상당 부분을 차지한다. 서버가 한번에 처리할 수 있는 요청이 제한되므로, 소수의 느린 요청의 처리만으로도 후속 요청 처리가 지체된다. 이 현상을 선두 차단이라고 부른다. 따라서 시스템의 확장성을 테스트하려고 인위적으로 부하를 생성하는 경우, 응답시간과 독립적으로 요청을 지속적으로 보내야 한다.

부하 대응 접근 방식

부하를 주고 그에 따른 성능을 측정 했으니 그 다음은 확장성에 대해 살펴보자. 부하 매개변수가 어느정도 증가하더라도 좋은 성능을 유지하려면 어떻게 해야 할까? 

  • 용량 확장(Scaling up, = 수직 확장(vertical scaling)): 좀 더 강력한 장비로 이동, 간단하지만 매우 비싸다.
  • 규모 확장(Scaling out, = 수평 확장(horizontal scaling)): 다수의 낮은 사양 장비에 부하를 분산, 비공유 아키텍쳐. 일부 시스템은 탄력적(부하 증가 시 자동으로 컴퓨팅 자원을 추가)이다. 하지만 수동으로 확장하는 시스템이 더 간단하고 운영상 예상치 못한 일이 더 적다.

최근까지의 통념은 확장 비용이나 데이터 베이스를 분산으로 만들어야하는 고가용성 요구가 있기 전까지는 단일 노드에 데이터베이스를 유지하는 것이다. 단일 노드에 상태 유지 데이터 시스템을 분산 설치하는 일은 아주 많은 복잡도가 추가적으로 발생하기 때문이다.

하지만 점점 분산 시스템을 위한 도구와 추상화가 좋아지면서 일부 애플리케이션에서는 바뀌었다. 대용량 데이터와 트래픽을 다루지 않는 사용 사례에서도 분산 데이터 시스템이 향후 기본 아키텍쳐로 자리잡을 가능성이 있다. 어떻게 가능할지 책에서 설명하겠다.

확장성을 갖춘 아키텍쳐들은 특정 애플리케이션에 특화되어 있지만, 이런 아키텍처는 보통 익숙한 패턴으로 나열된 범용적인 구성 요소로 구축한다. 이 범용적인 구성 요소들 역시 책에서 설명한다.

유지보수성

유지보수 중 고통을 최소화하고 레거시 소프트웨어를 직접 만들지 않게끔 소프트웨어를 설계해야하고, 할 수있다. 그를 위해 주의를 기울여야할 소프트웨어 시스템 설계 원칙은 다음 세 가지다.

  • 운용성: 운영팀이 시스템을 원활하게 운영할 수 있게 만들라
  • 단순성: 시스템에서 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들어라(사용자 인터페이스의 단순성과는 다르다!)
  • 발전성: 엔지니어가 이후에 시스템을 쉽게 변경할 수 있게 하라. 요구사항 변경 같은 예기치 않은 사용 사례를 적용하기가 쉽도록.(= 유연성, 수정 가능성, 적응성) 

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

좋은 소프트웨어라도 나쁘게 운영할 경우 작동을 신뢰할 수 없다. 운영팀은 일반적으로 다음과 같은 작업 등을 책임진다.

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

좋은 운영성이란 동일하게 반복되는 태스크를 쉽게 수행하게끔 만들어 운영팀이 고부가가치 활동에 노력을 집중한다는 의미이다. 데이터 시스템은 동일 반복 태스크를 쉽게 하기 위해 아래 항목 등을 포함해 다양한 일을 할 수 있다.

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

단순성: 복잡도 관리

복잡도 수렁에 빠진 소프트웨어 프로젝트를 때론 커다란 진흙 덩어리(big ball of mud)로 묘사한다. 복잡도는 다양한 증상으로 나타난다.

  • 상태 공간의 급증
  • 모듈간 강한 커플링
  • 복잡한 의존성
  • 일관성 없는 명명과 용어
  • 성능 문제 해결을 목표로한 해킹
  • 임시방편으로 문제를 해결한 특수 사례

복잡도를 줄이면 소프트웨어 유지보수성이 크게 향상된다. 따라서, 단순성이 구축하려는 시스템의 핵심 목표여야한다. 시스템을 단순하게 만드는 일이 반드시 기능을 줄인다는 의미는 아니다. 우발적 복잡도(accidental complexity)를 줄인다는 뜻일 수도 있다.
최상의 도구는 추상화다. 좋은 추상화는 깔끔하고 직관적인 외관 아래로 많은 세부 구현을 숨길 수 있다. 또한, 다른 다양한 애플리케이션에서도 사용 가능하다. 이러한 재사용은 비슷한 기능을 여러 번 재구현하는 것보다 더 효율적일 뿐만 아니라 고품질 소프트웨어로 이어진다. 추상화된 구성 요소의 품질 향상이 이를 사용하는 모든 애플리케이션에 도움이 되기 때문이다.

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

애자일(agile) 작업 패턴은 변화에 적응하기 위한 프레임워크를 제공한다. 이 책에서는 다양한 애플리케이션이나 다른 특성을 가진 서비스로 구성된 대규모 데이터 시스템 수준에서 애자일 기법을 사용하는 방법을 찾는다. 이 책에서는 민첩성 대신 발전성이라는 단어를 사용한다.