데이터 중심 애플리케이션 설계의 기본 원칙

Alan·2023년 2월 26일
0

'CH.01 신뢰할 수 있고(신뢰성) 확장 가능하며(확장성) 유지보수하기 쉬운(유지보수성) 애플리케이션'은 이 책이 다루는 주제인 '데이터 중심 애플리케이션 설계'의 전반적인 원칙에 대해 다루는 장이다.

데이터 시스템 애플리케이션의 표준 구성요소

자, 그럼 하나씩 각 요소들을 살펴보기 전 데이터 중심 애플리케이션을 이루는 표준 구성 요소에 대해 살펴보자.

  1. 구동 애플리케이션에서 데이터를 찾을 수 있게 저장(DB)
  2. 읽기 속도 향상을 위해 수행 결과를 저장(캐시)
  3. 사용자가 데이터를 검색하거나 필터링 할수 있게 제공(색인)
  4. 비동기 처리를 위해 다른 프로세스로 메시지 보내기(스트림 처리)
  5. 주기적으로 대량의 누적된 데이터를 분석(일괄/배치 처리)

이러한 용어들이 뻔한 말처럼 들린다면, 데이터 시스템이 그만큼 성공적으로 추상화됐음을 의미한다. 즉, 데이터엔지니어는 애플리케이션의 요구사항을 충족하기 위해 여러 추상화된 도구들을 결합하여 사용할 수 있어야 한다.

데이터 시스템이나 서비스를 설계할 때는, 주로 세 가지 관점을 중요하게 여긴다.

1. 신뢰성(Reliability)

  • 하드웨어나 소프트웨어 결함, 인적오류 같은 fault에 직면하더라도 시스템은 지속적으로 올바르게 동작해야한다.(fault tolerance)
  • 신뢰성에서 다루는 fault는 해결책이 있는 fault에 한정한다. ex) 보안의 경우에는 공격자가 민감한 데이터에 접근권한을 가지는 순간, 이를 되돌릴 방법은 없다.

  • 하드웨어 결함
    하드웨어의 평균 장애 시간은 약 10~50년이다. 따라서 10,000개의 디스크로 구성된 클러스터는 평균적으로 하루에 한 개의 디스크가 죽을 수 있다.
    이를 위한 해결 방법으로는 디스크를 RAID 구성으로 설치할 수 있고, 서버는 이중 전원 디바이스와 핫 스왑이 가능한 CPU로 구성, 데이터 센터는 예비 전원용 발전기를 갖출 수 있다.

  • 소프트웨어 오류
    보통 하드웨어 결함은 무작위적이고 서로 독립적이지만, 소프트웨어 결함(시스템 내 체계적 오류)은 예상하기가 더 어렵고, 노드 간 상관관계때문에 전방위적으로 발생할 여지가 있다.
    이러한 체계적 오류는 신속한 해결책이 없다. 빈틈없는 테스트, 프로세스 격리, 죽은 프로세스의 재시작 허용, 시스템 동작의 측정, 모니터링, 분석하기와 같은 형식들이 문제해결에 도움을 줄 수 있다.

  • 인적 오류
    한 연구에 따르면 서비스의 중단 원인 중 75 ~ 90%가 인적 오류에 의해 만들어진다. 이를 해결하기 위해선 다양한 접근 방식을 결합한다.

    1. 오류의 가능성을 최소화하는 방향으로 시스템을 설계한다.(추상화, API, 관리 인터페이스)
    2. 실수할 수 있는 부분을 분리한다.
    3. 철저하게 테스트한다.
    4. 인적 오류를 빠르고 쉽게 복구할 수 있도록한다.
    5. 성능지표와 명확한 모니터링을 한다.

2. 확장성(Scalability)

  • 시스템의 데이터 양, 트래픽 양, 복잡도가 증가하더라도 이를 처리할 수 있는 적절한 방법이 있어야 한다.
  • 시스템이 현재 안정적으로 동작한다고 해서 미래에도 안정적으로 동작한다고 보장할 수 없다. 가장 흔한 원인은 부하증가다. 하지만, 확장성을 논한다는 것은 단순히 확장 가능하다, 확장성이 없다와 같은 일차원적인 의미가 아니다. "시스템이 특정 방식이 커지면 이에 대처하기 위한 선택은 무엇인가?"(기술 스택을 의미하는 것으로 보임)와 "추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?"와 같은 질문을 고려해야 한다.

  • 부하 기술하기
    시스템 확장에서 고려해야할 것은 부하를 올바르고 간결하게 기술해야 한다는 것이다. 부하는 부하 매개변수라고 부르는 몇 개의 숫자로 나타낼 수 있다. ex) 웹 서버의 초당 요청 수, 데이터베이스의 읽기 대 쓰기 비율, 대화방의 동시 활성 사용자, 캐시 적중률 등

  • 트위터의 예시
    트위터의 주요 부하 원인은 '팬 아웃'때문이다. 즉, 개별 사용자는 많은 사람을 팔로우 하고 많은 사람이 개별 사용자를 팔로우 한다.이 동작을 구현하는 방식은 크게 두가지로 분류할 수 있다.

  1. 트윗 작성은 간단히 트윗 전역 컬렉션에 삽입한다. 사용자가 홈 타임라인을 요청하면 팔로우하는 모든 사람을 찾고 이 사람들의 모든 트윗을 찾아 시간순으로 정렬해 합친다. 이를 SQL로 나타내면 다음과 같다.
SELECT tweet.*, 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
  1. 각 사용자별 홈 타임라인 캐시를 유지한다. 사용자가 트윗을 작성하면 해당 사용자를 팔로우하는 사람을 찾고 팔로워 각자의 홈 타임라인 캐시에 새로운 트윗을 삽입한다.(홈 타임라인의 읽기 요청은 요청결과를 미리 계산했기 때문에 비용이 저렴하다.)

트위터는 1의 방식에서 2의 방식으로 전환했다. 평균적으로 트윗 게시 요청량이 홈 타임라인 읽기 요청량에 비해 수백 배 적기 때문이다. 즉, 쓰기 시점에 더 많은 일을 하고, 읽기 시점에 적은 일을 하는 것이 바람직하다.

이후에는 1과 2를 선택적으로 적용하는 혼합형으로 바뀌었다. 팔로워 수가 매우 많은 소수 사용자(유명인)은 팬 아웃에서 제외하고 1의 방식을 적용한다. 왜나하면 특정 유저는 팔로워가 3천만 명이 넘는 경우가 있다. 이 경우, 한 번의 트윗 요청이 3천만 건 이상의 쓰기 요청이 된다는 것을 의미하기 때문에 1의 방식이 더 알맞다.

즉, 트위터의 사례에서는 사용자당 팔로워의 분포가 확장성을 논의할 때 핵심 부하 매개변수가 될 수 있다.

  • 성능 기술하기
    일단 시스템 부하를 기술하면 부하가 증가할 때 어떤 일이 일어나는지 조사할 수 있다.
    부하 매개변수를 증가시키고 시스템 자원을 유지하면 시스템 성능은 어떤 영향을 받는가?
    부하 매개변수를 증가시킬 때 성능이 변하지 않고 유지되길 원한다면 자원을 얼마나 많이 늘려야 하나?
    • 처리량(throughput)
    • 응답시간(response time != 지연시간)
      응답시간의 경우, 단일 숫자가 아니라 측정 값의 분포로 생각해야 한다. 평균보다는 중앙값이 일반적으로 더 좋은 선택이며, 특이값(outlier)가 없을수록 좋은 시스템 환경하에서는 95분위, 99분위, 99.9분위를 사용하는 것이 일반적이다.
      대부분의 응답에는 정상적으로 응답하지만, 몇개의 outlier가 나타나는 현상을 꼬리지연시간(tail latency)라고 한다.
      큐 대기 지연, 서버는 병렬로 소수의 작업만 처리할 수 있기 때문에 소수의 느린 요청으로 후속 요청 처리가 지연될 수 있다. 이러한 현상을 선두차단(head-of-line blocking)이라 한다.
  • 부하 대응 방식
    • scale up : 좀 더 강력한 장비로 이동
    • scale out : 다수의 낮은 사양 장비에 부하 분산
    • elastic : 부하 증가를 감지하면 컴퓨팅 자원을 자동으로 추가
      일반적으로 상태 유지(stateful) 데이터 시스템을 분산 설치하는 것은 복잡도가 추가적으로 발생하기 때문에 고가용성 요구가 있을 때까지는 단일 노드에 데이터베이스를 유지하는 것(용량 확장)이 최근까지의 통념이다.
      아키텍처를 결정하는 요소는 읽기의 양, 쓰기의 양, 저장할 데이터의 양, 데이터의 복잡도, 응답 시간 요구사항, 접근 패턴 등이 있다.

3. 유지보수성(Maintainability)

  • 시간이 지나더라도 모든 사용자가 시스템 상에서 생산적으로 작업할 수 있게 해야 한다.

유지보수에는 버그 수정, 시스템 운영 유지, 장애 조사, 새로운 플랫폼 적응, 새 사용 사례를 위한 변경, 기술 채무 상환, 새로운 기능 추가 등이 있다.
유지보수를 편하게 하기 위해 소프트웨어 설계시 고려해야 할 점은 다음과 같다.

  • 운용성 : 운영팀이 시스템을 원할하게 운영할 수 있게 쉽게 만든다.

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

    • 복잡도는 다양한 증상으로 나타난다. 상태 공간의 급증, 모듈 간 강한 커플링(tight coupling), 복잡한 의존성, 일관성 없는 명명(naming)과 용어, 성능 문제 해결을 목표로 한 해킹, 임시방편으로 문제를 해결한 특수 사례(special-casing) 등이다.
      이는 기능을 줄인다는 뜻이 아닌, 우발적 복잡도(accidental complexity)를 줄인다는 의미이다. 즉, 우발적 복잡도란 소프트웨어가 풀어야 할 문제에 내재하고 있지 않고 구현에서만 발생하는 것이다.
      우발적 복잡도를 제거하기 위한 최상의 도구는 추상화다. 책 전반에 걸쳐 좋은 추상화란 무엇인지 살펴본다.
  • 발전성 : 엔지니어가 이후에 시스템을 쉽게 변경할 수 있게 만든다.

    • 데이터 시스템 변경을 쉽게 하고 변화된 요구사항에 시스템을 맞추는 방법은 시스템의 간단함과 추상화와 밀접한 관련이 있다. 간단하고 이해하기 쉬운 시스템은 대개 복잡한 시스템보다 수정하기 쉽다.

0개의 댓글