백엔드/분산 시스템

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

박지환 2022. 12. 26. 17:51
Part 1. 데이터 시스템의 기초

 

오늘날 많은 애플리케이션은 compute-intensive(계산 중심)과는 다르게 data-intensive(데이터 중심)적이다.

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

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

하지만 이러한 요소들을 요구사항에 맞도록 구성해야 한다

이를 위해 데이터 시스템의 원리를 파악하여 해당 알고리즘이 가진 트레이드오프를 이해하고, 이를 통해 기술들을 올바르고 정확하게 사용할 수 있어야 한다.

데이터 시스템에 대한 생각

데이터 시스템이라는 포괄적인 개념으로 묶여 있는 다양한 기술들의 특징

  1. 다양한 사용 사례(use case)에 최적화되어 있음.
  2. 단일 도구로는 더 이상 광범위한 요구사항을 만족시킬 수 없음.

따라서 다양한 구성 요소를 결합한 데이터 시스템이 필요하다.

다양한 구성 요소를 결합한 데이터 시스템 아키텍처의 예

데이터 시스템 설계에는 다양한 요소들이 영향을 미치지만, 그 중에서도 소프트웨어 시스템에서 중요하게 여기는 세 가지 관심사가 있다.

  • Reliability(신뢰성): 하드웨어/소프트웨어/인적 오류에도 시스템은 지속적으로 올바르게 동작해야 함.
  • Scalability(확장성): 시스템의 데이터 양/트래픽 양/복잡도가 증가하면 이를 처리할 수 있는 적절한 방법이 있어야 함.
  • Maintainability(유지보수성): 시간이 지남에 따라 다양한 사람들이 시스템 상에서 작업할 것이기 때문에, 모든 사용자가 시스템 상에서 생산적으로 작업할 수 있게 해야한다.

신뢰성

소프트웨어의 경우 신뢰성이란 대략 “무언가 잘못되더라도 지속적으로 올바르게 동작함”을 의미한다.

  • fault(결함): 잘못될 수 있는 일 ≠ failure(장애): 시스템 전체의 멈춤 상태
    • 모든 종유의 결함이 아닌, 특정 유형의 결함에 견딜 수 있는 시스템을 만드는 것이 타당함
  • fault-tolerant(내결함성)/resilient(탄력성) 시스템: 결함을 예측하고 대처할 수 있는 시스템
    • Netflix의 Chaos Monkey 등으로 시스템 테스트를 수행/훈련시켜 fault-tolerant를 높히는 등의 방식이 있음

하드웨어 결함

규모가 큰 데이터센터에서는 하드웨어 결함이 늘상 일어난다.

시스템 장애율을 줄이기 위한 방법으로는 하드웨어 구성 요소의 redudancy(중복) 추가이다.

이전에는 단일 장비의 전체 장애는 매우 드물었기 떄문에, 대부분의 애플리케이션은 하드웨어 구성 요소의 중복으로 충분했다.

하지만 데이터 양과 애플리케이션의 계산 요구가 늘어나면서 더 많은 애플리케이션이 많은 수의 장비를 사용하게 되었고, 이에 따라 하드웨어 결함율도 증가했다.

따라서 소프트웨어 fault-tolerant 기술을 사용하거나 하드웨어 redudancy를 추가하여 전체 장비의 손실을 견딜 수 있는 시스템으로 점점 옮겨가고 있다.

소프트웨어 오류

약한 상관관계(독립적인) 하드웨어 결함과는 다르게, 소프트웨어는 노드 간 상관관계 때문에 예상하기 어렵고, 전체 시스템 오류를 더욱 많이 유발할 수 있다.

소프트웨어의 결함을 systematic error(시스템 내 체계적 오류)라 부른다.

이 문제는 신속한 해결책이 없으며, 빈틈없는 테스트, 프로세스 격리, 모니터링 등으로 해결해야 한다.

인적 오류

인적 오류는 다른 오류보다 빈번하다. (운영자의 설정 오류가 중단의 주요 원인)

인적 오류를 고려하여 시스템을 구축하는 다양한 접근 방식

  • 오류의 가능성을 최소화하는 방향으로 API 등의 인터페이스를 구축하라
  • 실제 데이터를 사용해 사용자에게는 영향이 가지 않는 sandbox(샌드박스)를 제공하라
  • 단위 테스트부터 전체 통합 테스트까지 모든 레이어에서 철저하게 테스트하라
  • 인적 오류에서 빠르게 복구할 수 있도록 하라
  • 성능 지표와 오류율 같은 상세하고 명확한 모니터링 대첵을 마련하라
  • 조작 교육과 실습을 시행하라

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

신뢰성은 원자력 발전소 소프트웨어만을 위한 것이 아님. 일상적인 애플리케이션의 장애 또한 매출 손실, 생산성 저하 등의 문제를 야기하기 때문에, 안정적으로 작동해야 한다.

확장성

시스템은 부하 증가로 인해 성능 저하가 발생할 수 있다.

시스템의 Scalability(확장성)을 고려한다는 것은 “추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?”, “시스템이 특정 방식으로 커지면 이에 대처하기 위한 선택은 무엇인가?” 등을 고려하는 것이다.

부하 기술하기

시스템의 현재 부하를 기술할 때는 가장 적합한 load parameter(부하 매개변수)를 선택하는 것이 중요하다. (ex: 웹 서버의 초당 요청 수, 데이터베이스의 읽기/쓰기 비율, 트위터의 fan-out 등)

Twitter(트위터)의 부하 예시

트위터의 주요 두 가지 동작은 다음과 같다

  • 트윗(tweet) 작성: 사용자는 팔로워에게 새로운 메시지를 게시할 수 있다(평균 초당 4.6k 요청, 피크일 때 초당 12k 요청 이상)
  • 홈 타임라인(timeline): 사용자는 팔로우한 사람이 작성한 트윗을 볼 수 있다(초당 300k 요청)

초당 12,000건의 쓰기 처리를 상당히 쉽다. 하지만 트위터의 확장성 문제는 트윗 양이 아닌 팬 아웃(fan-out) 떄문이다. 개별 사용자들은 많은 사람들을 팔로우하기 때문이다. 이 두 가지 동작을 구현하는 방법은 크게 두 가지다.

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. 각 수신 사용자용 트윗 우편함처럼 개별 사용자의 홈 타인라인 캐시를 유지한다. 사용자가 트윗을 작성하면 해당 사용자를 팔로우하는 사람을 모두 찾고 팔로워 각자의 홈 타임라인 캐시에 새로운 트윗을 삽입한다. 그러면 홈 타임라인의 읽기 요청은 요청 결과를 미리 계산했기 때문에 비용이 저렴하다.

트위터 홈 타임라인 구현을 위한 간략한 관계형 스키마
2012년 11월 부하 매개변수와 함께 팔로워에게 트윗을 전송하기 위한 트위터의 데이터 파이프라인

평균적으로 트윗 게시 요청량이 홈 타임라인 읽기 요청량에 비해 수백 배 적기 떄문에 접근 방식 2가 훨씬 잘 동작한다. 그래서 이 경우에는 쓰기 시점에 더 많은 일을 하고, 읽기 시점에 적은 일을 하는 것이 바람직하다.

하지만 접근 방식 2의 불리한 점은 트윗 작성이 많은 부가 작업을 필요로 한다는 점이다. 평균적으로 트윗이 약 75명의 팔로워에게 전달되므로 초당 4.6k 트윗은 홈 타임라인 캐시에 초당 345k건의 쓰기가 된다. 그러나 평균값은 사용자마다 팔로워 수가 매우 다르다는 사실을 가린다. (일부 사용자는 팔로워가 3만명이 넘는다) 이는 단일 트윗이 홈 타임라인에 3천만 건 이상의 쓰기 요청이 될지도 모른다는 의미다. 이는 트위터가 5초 이내에 팔로워에게 트윗을 전송하려는 목표를 유지하기 위한 중요한 도전 과제다.

트위터의 사례에서 사용자당 팔로워의 분표가 fan-out 부하를 결정하기 떄문에 확장성을 논의할 때 핵심 부하 매개변수가 된다.

트위터는 최종적으로 두 접근 방식의 혼합형(hybrid)을 취하고 있다. 대부분 사용자의 트윗은 계속해서 사람들이 작성할 때 홈 타임라인에 펼쳐지지만 팔로워 수가 매우 많은 소수 사용자(유명인)은 fan-out에서 제외되고, 사용자가 팔로우한 유명인의 트윗은 별도로 가져와 접근 방식 1처럼 읽는 시점에 사용자의 홈 타임라인에 합친다.

성능 기술하기

load parameter를 통해 부하가 증가할 때 어떤 일이 일어나는지 조사할 수 있다. 아래 2가지 방법으로 가능하다.

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

위의 두 질문 모두 성능 수치가 필요하다.

  • throughput(처리량): 초당 처리할 수 있는 일정 크기의 데이터 집합으로 작업을 수행할 떄 걸리는 전체 시간 (Hadoop과 같은 일괄 처리 시스템에서 유용)
  • response time(응답 시간): 클라이언트가 요청을 보내고 응답을 받는 사이의 시간 (온라인 시스템에 유용) ≠ latency(지연 시간): 클라이언트의 관점에서 본 시간. 네트워크 지연과 큐 지연도 포함. 요청이 처리되길 기다리는 시간.

응답 시간은 요청마다 매번 달라지기 때문에, 응답 시간은 값의 분포로 생각해야 한다.

평균과 백분위 예시: 서비스에 대한 100건의 샘플 요청에 대한 응답 시간

올바른 응답시간을 알고 싶다면 arithmetic mean(산술 평균)보다 percentile(백분위)median(중앙값, p50)를 사용하는 편이 좋다.

outlier(특이 값)을 알기 위해서는 상위 백분위인 p95/p99/p999를 보는 것이 일반적이다.

tail latency(꼬리 지연 시간) = 상위 백분위 응답 시간 (p98~p99) 또한 사용자 경험에 직접적인 영향을 주기 떄문에 중요하다.

percentile은 SLO(Service Level Objective, 서비스 수준 목표), SLA(Service Level Agreement, 서비스 수준 협악서)와 같은 서비스 가용성을 정의하는 계약서에 자주 등장한다. 이런 지표를 통해 서비스 클라이언트와의 기대치를 설정한다.

 

head-of-line blocking(선두 차단): queueing deploy(큐 대기 지연)과 같은 소수의 느린 요청 처리로 후속 요청 처리가 지연되는 현상 → 이 때문에 클라이언트는 전체적인 응답 시간이 느리다고 생각할 것 → 이런 문제 때문에 클라이언트 쪽 응답 시간 측정이 중요하다.

요청을 처리하기 위해 여러 번 백엔드 호출이 필요한 상황에서 단 하나의 느린 백엔드 요청이 전체 최종 사용자 요청을 느리게 한다.

시스템 부하 테스트를 위한 부하 생성 클라이언트는 테스트 평가의 왜곡을 피하기 위해, 응답 시간과 독립적으로 요청을 지속적으로 보내야 한다.

부하 대응 접근 방식

확장 방법

  • scaling up/vertical scaling(수직 확장): 좀 더 강력한 장비로 이동
  • scaling out/horizontal scaling(수평 확장): 다수의 낮은 사양 장비에 부하를 분산 → shared-nothing(비공유) 아키텍처

좋은 아키텍처는 실용적인 접근 방식의 조합이 필요하다. 예를 틀어 적절한 사양의 장비 몇 대가 다량의 낮은 사양 가상 장비보다 여전히 훨씬 간단하고 저렴하다.

 

elastic(탄력적) 시스템: 부하 증가를 감지하면 컴퓨팅 자원을 자동으로 추가 ↔ 수동 확장

부하를 예측할 수 없을 만큼 높은 경우 탄력적 시스템이 유용하지만, 수동으로 확장하는 시스템이 더 간단하고 운영상 예상치 못한 일이 더 적다.

 

stateful(상태 유지) 데이터 시스템을 분산 설치하는 일은 많은 복잡도가 발생하기 때문에, 데이터베이스까지 분산으로 만들어야 하는 고가용성 요구가 있을 때까지 데이터베이스를 유지하는 것이 통념이였다.

하지만 최근에는 분산 시스템을 위한 도구와 추상화가 좋아지면서 일부 애플리케이션에 대해서는 달라졌다.

 

대규모 시스템의 아키텍처는 해당 시스템을 사용하는 애플리케이션에 특화되어 있다. 범용적이고 모든 상황에 맞는 (one-size-fits-all) 아키텍처는 없다.

특정 애플리케이션에 적합한 확장성을 갖춘 아키텍처는 부하 매개변수를 기반으로 구축한다.

유지보수성

소프트웨어 비용의 대부분은 유지보수에 들어간다.

레거시 등의 소프트웨어의 원활한 유지보수를 위해 소프트웨어를 설계해야 한다.

소프트웨어 시스템 설계 3원칙

  • Operability(운용성): 운영팀이 시스템을 원활하게 운영하 수 있게 쉽게 만들어라.
  • Simplicity(단순성): 시스템의 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들어라
  • Evolvaility(발전성): 엔지니어가 이후에 시스템을 쉽게 변경할 수 있게 하라.

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

시스템이 지속해서 원활하게 작동하려면 운영팀이 필수이다. 운영팀은 모니터링, 문제 추적, 보안 패치, 배포/설정 도구 마련, 운영 절차 정의 등의 작업을 수행한다.

좋은 운영성이란 반복되는 태스크를 쉽게 수행할 수 있도록 자동화, 자기 회복, 표준 도구 사용 등을 지원하는 것이 좋다.

단순성: 복잡도 관리

대규모 프로젝트에서의 복잡도의 증가는 프로젝트 진행을 매우 더디게 하고, 유지보수 비용이 많이 들게 한다.

이를 해결하기 위해 단순성을 시스템의 핵심 목표로 가져가야 한다.

Accidental complexity(우발적 복잡도), 즉 구현에서 발생하는 복잡도를 제거하기 위한 최고의 도구는 추상화이다. (ex: 고수준 프로그래밍 언어는 기계 언어, CPU 레지스터, 시스템 호출 등을 숨긴 추상화다)

추상화를 통해 세부 구현을 숨기고, 깔끔한 인터페이스를 제공하여 쉽게 사용할 수 있으면서 재사용까지 가능하게 할 수 있다.

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

시스템의 요구사항은 항상 바뀐다. 그리고 이러한 변화에 대응하기 위한 조직 프로세스 프레임워크 측면에서 agile(애자일) 작업 패턴을 사용한다.

다만 애자일은 작은 규모에서의 초점을 맞추고 있으므로, 대규모 시스템을 위한 방법은 추후에 기술한다.

변화에 잘 대응할 수 있는 방법은 시스템의 단순성과 밀접한 관련이 있다.

정리

데이터 중심 애플리케이션을 생각하는 기본적인 방법을 살펴보았다.

애플리케이션이 유용하려면 다양한 요구사항을 충족시켜야 한다. 요구사항은 다음과 같은 종류로 나뉜다.

  • 기능적 요구사항(여러 방법으로 데이터를 저장하고 조회하고 검색하고 처리하는 작업)
  • 비기능적 요구사항(보안, 신뢰성, 법규 준수, 확장성, 호환성, 유지보수성 등의 속성)

 

이 중에서 신뢰성, 확장성, 유지보수성을 자세히 살펴봤다.

  • Reliability(신뢰성): 결함이 발생해도 시스템이 올바르게 동작하게 만들어야 한다.
    • 결함에는 하드웨어/소프트웨어/인적 결함이 있다.
    • 내결함성 기술은 최종 사용자에게 특정 유형의 결함을 숨길 수 있게 해준다.
  • Scalability(확장성): 부하가 증가해도 좋은 성능을 유지하기 위한 전략.
    • 확장성을 설명하기 위해서는 먼저 양적으로 부하와 성능을 설명하는 방법이 필요하다 (ex: 응답 시간 백분위).
    • 확장 가능한 시스템에서는 부하가 높은 상태에서 신뢰성을 유지하기 위해 처리 용량을 추가할 수 있다.
  • Maintainability(유지보수성): 유지보수성의 본질은 시스템에서 작업하는 엔지니어와 운영 팀의 삶을 개선하는 데 있다.
    • 좋은 추상화는 복잡도를 줄이고 쉽게 시스템을 변경할 수 있게 하며 새로운 사용 사례에 적용하는 데 도움이 된다.
    • 좋은 운용성이란 시스템의 건강 상태를 잘 관찰할 수 있고 시스템을 효율적으로 관리하는 방법을 보유한다는 의미다.

 

애플리케이션을 신뢰할 수 있고 확장 가능하며 유지보수하기 쉽게 만들어주는 간단한 해결책은 없지만, 여러 애플리케이션에서 반복되는 특정 패턴과 기술을 통해 이를 달성할 수 있다.

 

Reference: http://www.yes24.com/Product/Goods/59566585

 

데이터 중심 애플리케이션 설계 - YES24

데이터는 오늘날 시스템을 설계할 때 마주치는 많은 도전 과제 중에서도 가장 중심에 있다. 확장성, 일관성, 신뢰성, 효율성, 유지보수성과 같은 해결하기 어려운 문제를 파악해야 할 뿐 아니라

www.yes24.com