데이터 중심 애플리케이션 설계 1장

00 머리말

  • 환경에 대한 이야기를 한다.
  • 머리말에서는 최근 사업적으로는 클라우드와 saas 환경, 하드웨어 적으로는 cpu클럭이 더이상 오르지 않고 멀티코어가 표준이 된 환경을 이야기하며 병렬 처리에 대한 환경을 강조한다.
  • 위의 예시를 데이터 중심적 이라고 정의하며 반대로 cpu사이클이 병목인경우를 계산 중심적이라고 정의한다

이 책은 (…) 데이터 시스템의 기초가 되는 다양한 원리와 트레이드오프에 대해 논의한다. 데이터 시스템 아키텍처와 데이터 중심 애플리케이션으로 데이터 시스템을 통합하는 방법을 주로 다룬다.

이 책의 개요

이책은 크게 3부로 구성되어 있다.

  1. 1부에서는 데이터 중심 애플리케이션 설계를 뒷받침하는 금본 개념을 설명한다.
  2. 2부에서는 여러 장비에 데이터를 분산하여 저장하는 부분을 설명하다.
  3. 3부에서는 특정 데이터셋에서 다른 데이터셋을 파생하는 시스템을 설명한다.

01 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션

  • 오늘날 많은 어플리케이션은 계선중심적이 아니라 데이터 중심적이라서, 데이터의 양, 데이터의 복잡도, 데이터의 변화 속도 등이 주요 해결해야할 이슈이다.

  • 데이터 시스템이 어플리케이션의 요구에 따라 제공해야할 핵심 기능은 아래와 같이 추상화 되어 있다.

  1. Database
  2. Cache
  3. Seach Index
  4. Stream processing
  5. Batch processing

데이터 시스템에 대한 생각

일반적으로 db큐, 캐시와 같은 것들은 다른 범주에 속하는 단일 도구들로 생각한다. 하지만 표면적으로 비슷하더라도 각각은 매우 다른 접근 패턴을 가지고 있고, 최근에 만들어졌는데 심지어 특정한 유즈케이스에 특화된 컴포넌트들이기 때문에 전통적인 데이터 시스템 분류로 접근하면 안된다. 그리고 점점 더 많은 애플리케이션들이 단일도구로는 핸들링 할 수 없는 복잡한 요구사항을 가지고 있다. 요구사항을 태스크로 나누고 해당 태스크들에 위에 설명한 각각의 단일 도구들에 맡기고 전체적인 실행 흐름을 애플리케이션 코드로 제어한다. 그래서 위의 각각의 단일 도구들을 애플리케이션 코드로 제어하는 개발자는 데이터 시스템 설계자이기도 하며, 각각의 단일 도구들 역시 데이터 시스템으로 봐야 한다.

그리고, 이러한 데이터시스템의 가장 주요한 관심사는 세가지로 분류된다.

신뢰성 (Reliability)

하드웨어나 소프트웨어 결함, 심지어 인적오류같은 역경에 직면하더라도, 시스템은 지속적으로 올바르게 동작한다.

소프트웨어의 경우 신뢰 할 수 있다라는 언사에 대한 일반적인 기대치는 아래와 같다.

  1. 애플리케이션은 사용자가 기대한 기능을 수행한다.
  2. 시스템은 사용자가 범한 실수나 예상치 못한 소프트웨어 사용법을 허용할 수 있다.
  3. 시스템 성능은 예상된 부하와 데이터 양에서 필수적인 사용 사례를 충분히 만족한다.
  4. 시스템은 허가되지 않은 접근과 오남용을 방지한다.

결함(fault) 에 대해서.

결함장애 그리고 내결함성에 대해서 이해해야 한다. 먼저 결함장애는 동일하지 않다. 결함은 사양에서 벗어난 시스템의 한 구성요소로 정의되지만, 장에는 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 상황을 이야기한다.

그리고 결함은 모든 시스템에 필연적으로 존재한다. 예를들어 지진과 홍수와 같은 자연재해로 인해 서버가 파괴되었을 때 웹 호스팅을 이어나갈 수 없다. 결론적으로는 결함이 장애로 이어지지 않도록 내결함성(fault-tolerent), 탄력성(resilient)를 갖춰야 한다.

그 이유는 모든 결함을 예방할 수 없기 때문인데, 그래서 보통은 모든것을 예방한다기 보단 내결함성을 갖는것을 추구한다. (물론 한번의 결함으로 인해 복구가 불가능한 피해를 입을 수 있는 보안같은 것들은 예방이 중요하다.)

하드웨어 결함

하드웨어 평균 장애시간(mean time to faillure, MTTF)는 평균적으로 10~50년으로 보고됐다. 따라서 10,000개의 디스크로 구성된 저장 클러스터는 평균적으로 하루에 한개의 디스크가 죽는다고 예상해야한다.

  • 첫 번째 대응으로 하드웨어 컴포넌트에 중복을 추가하는 방법이 일반적이다.

  • 전원 컴포넌트라면 발전기와 건전지가 있고, 네트워크 컴포넌트라면 다중화된 네트워크 인터페이스가 있다.

  • 당연히 비용이 엄청나게 들어가는 일이라, 고가용성을 위한 비용을 지불할 수 있는 소프트웨어에 사용됐었다.

  • 하지만 클라우드가 보편화 되었고, AWS와 같은 클라우드 서비스들은 단일 장비의 신뢰성보다 유연성과 탄력성을 주요 관심사로 삼는다.

  • 즉 AWS의 단일 인스턴스는 쉽게 죽지만, 다른 인스턴스로 쉽게 대체할 수 있다.

소프트웨어 오류

하드웨어 결함은 보통 무작위적이고, 서로 독립적이다. 즉 특정 한 장비의 디스크에 장애가 있다고 해서 다른 장비의 디스크에 장애가 발생하지 않는다는 뜻이다. 반면 소프트웨어 결함은 보통 시스템 전체에 영향을 미친다.

  • 소프트웨어 결함은 보통 무작위적이지 않다. 즉, 특정한 조건에서만 발생한다.

  • 소프트웨어 결함은 보통 신속한 해결책이 없다. (주의깊게 생각하기, 빈틈없는 테스트, 프로세스 격리와 죽은 프로세스의 재시작 허용, 모니터링 …)

인적 오류

장비의 결함으로 인한 장애율은 보통 10~25% 선이다. 반면 사람은 최선의 의도를 갖고 있어도 미덥지 않다.

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

“원자력 발전소”, “항공 관제 시스템”, “몇만명의 몇년치 추억을 보관한 저장소 서비스” 만큼…

확장성

시스템이 현재 안정적으로 동작한다고 해서 미래에도 안정적으로 동작한다는 보장은 없다. 확장성은 증가한 부하에 대처하는 시스템 능력을 설명하는 데 사용하는 용어지만, 시스템에 부여하는 일차원적인 표식이 아니다.

“X는 확장 가능하다”, “Y는 확장성이 없다” - (X)

“시스템이 특정 방식으로 커지면 이에 대처하기 위한 선택은 무엇인가” - (O) “추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?” - (O)

부하 기술하기

먼저 부하를 정의하고 파악하기 위해서 load parameter를 기술해야 한다.

대표적인 예시로, 웹 서버의 초당 요청 수, 데이터베이스의 읽기 대 쓰기 비율, 대화방의 동시 활성 사용자, 캐시 적중률 등이 될 수 있다.

병목이 있다면 특정 하나의 파라미터가, 그게 아니라면 해당 값들의 평균이 중요 할 수 있다.

부하 기술하기 tweeter 예제

# 트위터의 주요 두가지 동작

트윗 작성 - 평균 초당 4.6k, 피크 초당 12k
홈 타임라인 - 초당 300k

트윗 작성은 핸들링 하기 쉽지만, 팬아웃이 문제였다고 한다.

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번 방식 - 개별 사용자의 홈 타임라인 캐시를 유지하는 방식. (사용자가 트윗을 작성하면 해당 사용자를 팔로우하는 모든 사람을 찾고 각자의 홈 타임라인 캐시에 새 트윗 삽입)

쓰기 시점에 작업을 부가적으로 하면서 조회 부담을 극단적으로 줄이는 방식!

결론적으로 트위터는 타임라인 조회 건수가 압도적으로 많기 때문에, 후자가 맞는 설계였다 문제는 2번 방식은 작성에 부하가 매우 커진다는 것 이었는데, 평균값은 75명 정도의 팔로워가 있어 4.6k의 쓰기가 345k 건의 쓰기로 이어지는데다가 진짜 문제는 분포였다. 특정 사용자의 경우 3천만이 넘는 팔로워를 가지고 있었다.

트위터 결론 1번과 2번의 혼합 방식을 사용한다. 다만 2번방식에서 유명한 사용자의 트윗은 팬아웃에서 빠지고, 1번 방식으로 데이터를 로드하는 시점에 가져온다고 한다.

성능 기술하기

부하 매게변수를 잘 기술해두면 아래와 같은 테스트들이 가능하다.

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

두 질문 모두 성능 수치가 필요한데, 하둡과 같은 일괄 처리 시스템은 처리량이 관심사일것이고, 온라인 시스템의 중요한 사항은 응답 시간일 것이다. 책에서는 응답 시간에 대한 이야기를 위주로 한다.

먼저 응답시간은 측정 가능한 값의 분포로 생각해야 한다.

응답 시간에 대한 이 책의 주요한 관점

  1. 보통 평균 시간을 참고하는건 좋은 생각이 아니다.
  2. 그보다는 중앙값과 같은 백분위값을 이용하는게 좋다. (예를 들어 가장 중간값의 요청이 200ms 가 걸렸다면, 절반의 사용자는 200ms이내에 응답을 받았고, 반대도 마찬가지이다.)
  3. 상위 하위 백분위값으로 주요 지표를 추려갈 수 있다.
  4. 꼬리 지연시간 (tail latency)는 생각보다 중요한 지표이다. 가장 비싼 요청을 보낸 사용자는 가장 주요한 고객일 확률이 높다.
  5. 그렇다고 최상위의 백분위를 참고하는것은 최적화에 너무 큰 비용이 들거나 임의 이벤트의 영향을 받을 수 있기에 참고하기에는 너무 지엽적인 지표이다.
  6. 큐 대기 지연은 느리게 응답받은 요청건의 상당수를 차지한다. (오래걸리는 요청이 먼저 온 케이스)
  7. 6번과 같은 이유로 클라이언트사이드의 응답시간 측정이 주요하다.

부하 대응 접근 방식

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

특정 애플리케이션에 적합한 확장성을 갖춘 아키텍처는 주요 동작이 무없이고 잘 하지 않는 동작이 무엇인지에 대한 가정을 바탕으로 구축한다. 이 가정이 잘못되면 확장에 대한 엔지니어링 노력은 헛수고가 되고 최악의 경우 역효과를 낳는다.

유지보수성

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

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

좋은 운영성이란 동일하게 반복되는 태스크를 쉽게 수행하게끔 만들어 운영팀이 고부가가치 활동에 노력을 집중한다는 의미이다.

이해하기 어려운 내용은 없고 기술적인 면이 있는 챕터라 책의 디테일한 부분을 발췌하지는 않았다.

단순성: 복잡도 관리

최상의 도구는 추상화다. 깔끔하고 직관적인 외관 아래로 많은 세부 구현을 숨길수 있다. 예를 들어, 고수준 프로그래밍 언어는 기계언어, cpu 레지스터, 시스템콜을 숨긴 추상화다. SQL은 디스크에 기록하고 메모리에 저장한 복잡한 데이터 구조와 다른 클라이언트의 동시 요청과 고장 후 불일치를 숨긴 추상화다.

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

애자일과 TDD와 같은 좋은 이야기들..

정리

  1. 데이터 시스템을 컴포넌트로 나누고 그 흐름을 어플리케이션의 흐름으로 제어하기 때문에 어플리케이션 설계는 데이터 시스템 설계와 같다 가 주요한 내용인것 같은데 아직 구체적인 이야기는 나오지 않았다.
  2. 확장성을 설명하는 접근 방식이 좋았다. 주요 지표를 설정하고 그 지표에 따라서 확장에 대응하는 실제 예시를 통해 실전적인 고민을 하는 부분이 좋았다.