[데이터중심 애플리케이션 설계] 정리
1장. 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션
오늘날 많은 애플리케이션은 계산 중심에서 데이터 중심으로 변화하고 있다.
CPU 성능보다는 데이터의 양, 복잡도, 변화 속도가 중요해지고 있다.
우린 데이터와 관련된 기능을 제공하는 컴포넌트들을 많이 사용하고 있는데, 이들은 성공적으로 추상화 되어 좋은 도구로 활용하고 있다
애플리케이션도 저마다 요구사항과 역할이 다른 것처럼, 이런 데이터 시스템들도 각자 다양한 특성과 경우에 따라 적합한 특성을 갖고 있다.
따라서 어떤 도구와 어떤 접근 방식이 가장 적합한지 생각해야 한다
데이터 시스템에 대한 생각
개발자는 애플리케이션 개발자일 뿐 아니라 데이터 시스템 설계자 이기도 하다
데이터 시스템이든 서비스든 설계할 때 까다로운 문제가 많이 생긴다.
문제가 있더라도 데이터를 정확하고 완전하게 유지하려면 어떻게 해야할까?
일부 성능 저하가 있더라도 일관되게 좋은 성능을 어떻게 제공할까?
부하 증가를 다루기 위해 어떻게 규모를 확장할까?
이런 질문들에 대해 대부분 소프트웨어에서 중요하게 여기는 세가지 관심사에 중점을 둔다
신뢰성
소프트웨어 신뢰성의 경우 일반적인 기대치는 다음과 같다
- 애플리케이션은 사용자가 기대한 기능을 수행한다
- 시스템은 사용자가 범한 실수나 예상치 못한 소프트웨어 사용법을 허용할 수 있다
- 시스템 성능은 예상된 부하와 데이터 양에서 필수적인 사용 사례를 충분히 만족한다
- 시스템은 허가되지 않은 접근과 오남용을 방지한다
잘못될 수 있는 일을 결함이라 하고, 이를 예측하고 대처할 수 있는 시스템을 결함성 또는 탄력성을 지녔다고 한다.
모든 종류의 결함을 견딜 수 있는 시슽메을 만드는건 불가능하다. 특정 유형에 결함 내성에 대해서 이야기 하는것이 타당하다.
결함은 장애와 동일하지 않다. 결함은 사양에서 벗어난 시스템의 한 구성 요소로 정의되지만, 장애는 사용자에게 서비스를 제공하지 못하고 전체가 멈춘 경우다.
결함 확률을 0으로 줄이는 것은 불가능 하므로, 결함이 장애로 이어지지 않도록 내결함성 구조를 설계하는 것이 좋다
결함의 유형들을 알아보자
하드웨어 결함
장비에 문제가 있는 것은 늘상 일어나는 일이다.
하드디스크의 평균 장애시간은 약 10~50년으로 10,000개의 디스크로 구성된 클러스터는 평균적으로 하루에 한 개의 디스크가 죽는다고 예상해야 한다.
최근에는 데이터 양과 애플리케이션 계산 요구가 늘어나며 더 많은 수의 장비를 사용하고 있고, 클라우드 플랫폼 같은 경우 가상 장비가 별도의 경고 없이 사용할수 없게 되는 상황이 일반적이다.
따라서 소프트웨어 내결함성 기술을 이용하거나, 하드웨어 중복을 추가해 전체 장비의 손실을 견디는 시스템으로 옮겨가고 있다
소프트웨어 오류
예상하기 어렵고 하드웨어 결함보다 시스템 오류를 더 많이 유발하는 경향이 있다.
특정 상황에 발생하기 전까지 오랜기간 나타나지 않을 수도 있다.
이런 문제는 신속한 해결책이 없다.
시스템 가정과 상호작용에 대해 주의 깊게 생각하기, 빈틈없는 테스트, 프로세스 격리, 죽은 프로세스의 재시작 허용,
프로덕션 환경에서 시스템 동작의 측정, 모니터링, 분석과 같은 많은 일들이 필요하다
인적 오류
소프트웨어 시스템을 설계하고 구축하며 운영하는건 사람이다.
아무리 최선의 의도를 갖고 있더라도 실수의 여지가 있다.
한 연구에 따르면 운영자의 설정오류가 중단의 주요 원인이었다고 한다
사람이 미덥지 못한데 어떻게 신뢰성 있게 만들수 있을까?
- 오류의 가능성을 최소화하는 방향으로 시스템을 설계하라
- 사람이 가장 많이 실수하는 부분에서 사람의 실수로 장애가 발생할 수 있는 부분을 분리하라
- 단위 테스트부터 통합 테스트까지 모든 수준에서 철저히 테스트하라
- 장애 발생의 영향을 최소화 하기 위해 인적 오류를 빠르고 쉽게 복구할수 있게 하라
- 성능 지표와 오류율 같은 상세하고 명확한 모니터링 대책을 마련하라
- 조작 교육과 실습을 시행하라
확장성
현재 시스템이 안정적으로 동작한다고 해서 미래에도 안정적일 것이란 보장은 없다.
성능 저하를 유발하는 흔한 이유 중 하나는 부하 증가다.
확장성은 증가한 부하에 대처하는 일차원적인 표식은 아니다.
확장성은 "시스템이 특정 방식으로 커지면 이에 대처하기 위한 선택은 무엇인가?", "추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?" 같은 질문을 고려 한다는 의미다
부하 기술하기
확장성을 논의하기 이전에, 시스템의 현재 부하를 간결하게 기술해야 한다.
부하 매개변수라는 몇 개의 숫자로 나타낼 수 있는데, 적합한 부하 매개변수는 시스템 설계마다 다르다.
주로 웹서버 초당 요청수, 데이터베이스 읽기 대 쓰기 비율, 동시 활성 사용자수, 캐시 적중률 등이 될 수 있다.
평균적인 경우가 중요할 수도 있고, 소수의 극단적인 경우가 병목의 원인일 수도 있다
성능 기술하기
시스템 부하를 기술하면 부하가 증가할 때 어떤 일이 일어나는지 조사할 수 있다.
- 시스템 자원은 그대로 두고, 부하 매개변수를 증가시켜 시스템 성능 영향을 관찰
- 부하 매개변수를 증가시켰을때 성능이 유지되길 원한다면 얼마나 많은 자원을 투입해야 하는가?
일괄 처리 시스템은 처리량, 온라인 시스템은 응답 시간을 주로 본다
응답 시간은 단일 숫자가 아닌 측정 가능한 값의 분포로 생각해야 한다
평균 응답 시간을 보는건 일반적이다.
하지만 "전형적인" 응답 시간을 알고 싶다면 평균은 좋은 지표가 아니다.
일반적으로 평균보다는 백분위를 사용하는 것이 좋다.
중앙값 p50 : 보통 얼마나 오랫동안 기다려야 하는지
상위 백분위 p95, p99, p999 (상위 95%, 99%, 99.9%) : 특이 값이 얼마나 좋지 않은지
보통 응답 시간이 가장 느린 요청이 가장 많은 데이터를 갖고 있을 가능성이 있고, 이를 빠르게 해주는게 서비스상 중요하다
시스템의 확장성을 테스트 하려고 인위적인 부하를 생성할때는 응답시간과 독립적으로 요청을 보내야 한다.
부하 대응 접근 방식
부하 매개변수가 증가하더라도 좋은 성능을 유지하려면 어떻게 해야할까?
급성장하는 서비스라면 부하 규모의 자릿수가 바뀔때마다 아키텍처를 재검토 해야 할지 모른다.
확장성과 관련해 주로 용량 확장(scale up), 규모 확장(scale out) 을 이야기 한다
특정 애플리케이션에 적합한 확장성을 갖춘 아키텍처에 대해 잘 알아야 한다.
이에 대한 이해가 없다면 확장에 대한 노력은 헛수고가 되고 최악의 경우 역효과를 낳는다
유지보수성
소프트웨어 비용의 대부분은 초기 개발이 아닌 지속적인 유지보수에 들어간다.
많은 사람은 소위 레거시시스템 유지보수 작업을 좋아하지 않는다.
유지보수 고통을 최소화 하기 위한 설계를 해야 한다. 그러기 위한 설계 원칙은 다음 세가지가 있다.
운용성
시스템이 지속해서 원할히 동작하려면 운영팀이 필수다.
좋은 운영팀은 일반적으로 다음 작업 등을 책임진다
- 시스템 상태를 모니터링하고 좋지 않다면 빠른 복원
- 시스템 장애, 성능 저하 등 문제 원인 추적
- 보안 패치 포함 소프트웨어를 최신 상태로 유지
- 다른 시스템 영향도 파악하고 문제 생길수 있는 변경 사항을 손상 입히기 전에 차단
- 발생 가능한 문제를 예측해 문제 발생 이전에 해결
- 배포, 설정 관리 등을 위한 도구 마련
- 설정 변경으로 생기는 시스템 유지보수
- 예측 가능한 운영과 안정적인 서비스 환경을 유지하기 위한 절차 정의
- 시스템에 대한 조직의 지식 보존
좋은 운용성은 동일하게 반복되는 작업을 쉽게 수행하게끔 만들어 운영팀이 고부가가치 활동에 집중할수 있게 하는 것이다.
- 모니터링으로 런타임 동작과 시스템 내부 가시성 제공
- 표준 도구 이용해 자동화와 통합 지원
- 개별 장치 의존성 회피, 장비를 내리더라도 전체 시스템에 영향이 없어야 함
- 좋은 문서와 이해하기 쉬운 운영 모델
- 만족할만한 기본 동작 제공
- 적절한 자기 회복, 필요에 따라 관리자가 시스템 상태 수동 제어 가능하게 함
- 예측 가능한 동작, 예기치 않은 상황 최소화
단순성
프로젝트가 커짐에 따라 시스템은 복잡하고 이해하기 어려워진다.
복잡도는 작업 진행을 느리게 하고 유지보수 비용이 증가하게 한다.
복잡도는 다양한 증상으로 나타난다.
상태 공간의 급증, 모듈간 강한 커플링, 복잡한 의존성, 일관성없는 명명과 용어, 성능 문제 해결을 위한 해킹, 임시방편 문제 해결 등..
복잡한 소프트웨어는 변경이 있을 때 버그가 생길 위험이 크다.
시스템을 이해하고 추론하기 어려워지면 숨겨진 가정과 의도치 않은 결과 및 상호작용을 간과하기 쉽다.
시스템을 단순하게 만드는 일이 기능을 줄인다는 의미는 아니다. 우발적 복잡도를 줄인다는 뜻이다.
이를 위한 최상의 도구는 추상화이다.
좋은 추상화는 깔끔하고 직관적인 외관 아래로 세부 구현을 숨길 수 있다.
발전성
시스템 요구사항은 끊임없이 변할 가능성이 크다.
변경을 쉽게 하고 변화된 요구사항에 맞추기 쉽도록 설계 해야 한다.
애플리케이션을 신뢰할수 있고 확장 가능하며 유지보수하기 쉽게 만들어주는 간단한 해결책은 없다
2장. 데이터 모델과 질의 언어
데이터 모델은 해결하려는 문제를 어떻게 생각해야 하는지에 많은 영향을 끼친다.
소프트웨어의 각 계층은 명확한 데이터 모델을 제공해 하위 계층의 복잡성을 숨긴다.
데이터 모델은 소프트웨어가 할 수 있는 일과 할 수 없는 일에 지대한 영향을 주므로 적합한 데이터 모델을 선택하는 일은 상당히 중요하다.
애플리케이션도 저마다 요구사항이 다르므로, 어떤 사례에 맞는 최적의 데이터 모델은 다른 경우엔 다를 수 있다.
가까운 미래에는 여러 저장소를 함께 사용하는 경우가 생길 것이고, 이를 다중 저장소 지속성 이라 한다
관계형 모델과 문서 모델
현재 가장 잘 알려진 데이터 모델은 관계형 모델을 기반으로 한 SQL 이다.
데이터는 관계(relation)으로 구성되고 각 관계는 튜플 모음이다.
관계형 모델은 비즈니스 데이터 처리라는 본래 영역을 넘어 다양한 사용 사례에 보편적으로 나타난다.
NoSQL 의 탄생
대규모 데이터셋이나 매우 높은 쓰기 처리량이 관계형 데이터베이스에 비해 장점이다
관계형 모델에서 지원하지 않는 특수 질의나, 더 동적이고 표현력이 풍부하다.
객체 관계형 불일치
대부분 애플리케이션은 객체지향 언어로 개발하는데, SQL 데이터 모델을 향한 비판을 불러온다.
데이터를 관계형 테이블에 저장하려면 애플리케이션 코드와 데이터베이스 모델 객체 사이 전환 계층이 필요하다.
이런 모델 사이 분리를 임피던스 불일치라 부른다.
하이버네이트 같은 객체 관계형 맵핑(ORM) 프레임워크는 보일러플레이트 코드를 줄여주지만, 두 모델 간 차이는 완벽히 숨길 수 없다.
어떤 데이터 모델이 애플리케이션 코드를 더 간단하게 할까?
데이터가 문서와 비슷한 구조라면 문서 모델을 사용하는 것이 좋다.
문서와 비슷한 구조를 여러 테이블로 나누는 관계형 기법은 다루기 힘든 스키마와 복잡한 코드를 발생시킨다.
하지만 애플리케이션에서 다대다 관계를 사용한다면 문서 모델은 매력이 떨어진다.
일반적으로 어떤 데이터 모델이 코드를 더 간단하게 만드는지 말할 수 없다. 데이터 항목 간 존재하는 관계 유형에 따라 다르다
문서 모델에서의 스키마 유연성
대부분 문서 데이터베이스는 문서의 데이터에 스키마를 강요하지 않는다.
문서 데이터베이스는 종종 스키마리스로 불리지만 이는 오해의 소지가 있다.
데이터를 읽는 코드는 보통 구조의 유형을 어느정도 가정한다.
정적 타입 확인과 유사한 쓰기 스키마, 동적 타입 확인과 유사한 읽기 스키마로 나눌 수 있는데,
NoSQL 은 읽기 스키마 접근 방식이라고 봐야 한다.
이는 컬렉션 안의 항목이 모두 동일한 구조가 아닐때 유리하다.
질의를 위한 데이터 지역성
애플리케이션이 자주 전체 문서에 접근해야 할 때 저장소 지역성을 활용하면 성능 이점이 있다.
지역성의 이점은 한 번에 해당 문서의 많은 부분을 필요로 하는 경우에 적용된다.
문서 데이터베이스와 관계형 데이터베이스의 통합
관계형 데이터베이스와 문서 데이터베이스는 시간이 지남에 따라 점점 더 비슷해지고 있다.
각 데이터모델이 서로 부족한 부분을 보완해 가고 있기 때문이다.
데이터를 위한 질의 언어
SQL이나 관계 대수 같은 선언형 질의에서는 목표를 다성하기 위한 방법이 아니라 알고자 하는 데이터의 패턴에 관심을 둔다.
어떤 색인과 어떤 조인 함수를 사용할지, 질의를 어떤 순서로 실행할지는 데이터베이스 시스템이 할 일이다.
선언형 질의 언어는 명령형 API보다 간결하고 쉽게 작업할수 있어 매력적이다.
데이터베이스 엔진의 상세 구현이 숨겨져 있어 질의를 변경하지 않고도 데이터베이스 시스템 성능 향상을 시킬수 있따.
선언형 언어는 병렬 실행에 더 적합하다.
명령형 코드는 특정 순서로 수행하게끔 지정하기 때문에 다중 코어나 다중 장비에서 병렬 처리가 어렵다.
선언형 언어는 결과의 패턴만 지정하기 때문에 병렬 실행으로 더 빨라질 가능성이 크다
맵리듀스 질의
많은 컴퓨터에서 대량의 데이터를 처리하기 위한 모델이다.
맵리듀스는 선언형 질의와 명령형 질의 중간 정도에 있다.
함수형 프로그래밍 언어에 있는 map(collect) reduce(fold, inject) 함수를 기반으로 한다
그래프형 데이터 모델
다대다 관계가 복잡해지면 관계형 모델보다 그래프로 데이터를 모델링 하는게 자연스럽다.
그래프는 정점(vertex), 간선(edge) 두 유형의 객체로 이루어진다.
그래프 데이터 모델은 속성 그래프모델 과 트리플 저장소 모델로 나눠진다
속성 그래프
속성 그래프 모델에서 각 정점은 다음과 같은 요소로 구성된다
- 고유한 식별자
- 유출(outgoing) 간선 집합
- 유입(incoming) 간선 집합
- 속성 컬렉션(키-값 쌍)
각 간선은 다음과 같은 요소로 구성된다
- 고유한 식별자
- 간선이 시작하는 정점(꼬리 정점)
- 간선이 끝나는 정점(머리 정점)
- 두 정점 간 관계 유형을 설명하는 레이블
- 속성 컬렉션(키-값 쌍)
이 모델은 다음과 같은 특징을 갖는다.
1. 정점은 다른 정점과 간선으로 연결된다. 특정 유형과 관련 여부를 제한하는 스키마는 없다.
2. 정점이 주어지면 정점의 유입과 유출 간선을 효율적으로 찾을 수 있고 그래프를 순회할 수 있다. 일련의 정점을 따라 앞뒤 방향으로 순회한다.
3. 다른 유형의 관계에 대해 서로 다른 레이블을 사용하면 단일 그래프에 다른 유형의 정보를 저장하면서도 데이터 모델을 깔끔하게 유지할 수 있다.
그래프는 발전성이 좋아서 애플리케이션에 기능을 추가하는 경우 애플리케이션의 구조 변경을 수용하게끔 그래프를 쉽게 확장할 수 있다.
사이퍼 질의 언어
속성 그래프를 위한 선언형 질의 언얼, Neo4j 그래프 데이터베이스용으로 만들어 졌다.
트리플 저장소
속성 그래프 모델과 거의 동등하다.
트리플 저장소는 모든 정보를 주어, 서술어, 목적어 처럼 간단한 세 부분 구문 형식으로 저장한다
세가지 모델은 모두 현재 널리 사용하고 각자 영역에서 훌륭하다.
한 모델을 다른 모델로 흉내 낼 수 있지만 그 결과는 대부분 엉망이다.
각기 목적에 맞는 다양한 시스템을 보유해야 하는 이유다.