4장까지는 데이터 시스템이 단일 장비이거나 여러 클러스터 장비에 분산됐거나 상관없이 모든 상황에서 적용되는 데이터 시스템의 기초 개념에 대해 알아본다.
오늘날 많은 애플리케이션은 계산 중심(compute-intensive) 이라기 보단 데이터 중심적(data-intensive)이다.
데이터 중심적인 애플리케이션의 성능을 제한하는 것은 CPU 성능보단 데이터의 양, 복잡도, 변화 속도다.
결함이 발생해도 시스템이 올바르게 동작하는 성질
결함(fault) 은 사양에서 벗어난 시스템의 한 구성 요소로 정의된다.
결함을 예측하고 대처할 수 있는 시스템을 내결함성(fault-tolerant) 또는 탄력성(resilient)을 지녔다고 한다.
장애(failure) 는 사용자에게 필요한 서비스를 제공하지 못하고 시스템 전체가 멈춘 상황을 의미한다. 결함과는 다른 의미다.
결함으로 인해 장애가 발생하지 않게끔 내결함성 구조를 설계하는 것이 좋다.
10,000 개의 디스크로 구성된 저장 클러스터는 평균적으로 하루에 한 개의 디스크가 죽는다고 예상된다.
하드웨어 구성 요소에 중복(redundancy)를 추가하는 방법으로 대응한다.
신속한 해결책이 없다.
숙고하기, 빈틈없는 테스트, 프로세스 격리, 죽은 프로세스의 재시작 허용, 시스템 동작의 측정, 모니터링, 분석과 같은 노력이 필요하다.
한 연구에 따르면 인적 오류가 하드웨어 에러보다 더 많이 발생한다고 한다.
어떻게 해결하겠는가?
부하가 증가해도 좋은 성능을 유지하기 위한 전략
확장성은 증가된 부하에 대처하는 시스템 능력을 설명하는 데 사용하는 용어다. 그러나, "X는 확장 가능하다" 와 같은 일차원적인 표식은 의미가 없다. 오히려 "시스템이 특정 방식으로 커지면 대처하기 위한 선택은 무엇인가?", "추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?" 같은 질문을 고려해야 한다.
시스템의 현재 부하를 간결하게 기술해야 한다. 그래야 부하 성장 질문("부하가 두 배로 되면 어떻게 될까?")을 논의할 수 있다.
부하는 부하 매개변수(load parameter) 라 부르는 몇 개의 숫자로 나타낼 수 있다. 가령 다음의 것들이 있다.
평균적인 경우가 중요할 수도 있고, 소수의 극단적인 경우가 병목 현상의 원인일 수 있다.
트위터는 팔로워의 분포가 핵심 부하 매개변수이다.
트위터의 주요 두 가지 동작은 다음과 같다.
트위터의 확장성 문제의 주요 원인은 트윗 양이 아닌 팬 아웃 때문이다. 개별 사용자가 많은 사람을 팔로우하고, 다시 많은 사람들이 개별 사용자를 팔로우한다.
트위터를 구현하기 위한 방법은 크게 두 가지가 있다.
트위터의 첫 번째 버전은 첫 번째 방법을 사용하다가, 홈 타임라인의 질의 부하를 버텨내기 어려워 두 번째 방법으로 전환했다.
평균적으로 트윗 작성보다 홈 타임라인 요청이 수백 배 많기 때문에, 두 번째 방법이 훨씬 더 잘 동작한다.
이런 경우엔 쓰기 시점에 더 많은 일을 하고(트윗 작성시 팔로워들을 찾고 그들의 타임라인 캐시에 새로운 트윗을 삽입하는 일), 읽기 시점에는 적은 일(단순 캐시 읽기)을 하는 것이 바람직 하다.
반면 두 번 째 방법은 트윗 작성이 많은 부가 작업을 필요로 한다는 것이다. 평균적으로 트윗이 약 75명의 팔로워에게 전달되므로, 초당 4.6k 트윗 요청은 홈 타임라인 캐시에 345k 쓰기를 유발한다. 또한, 어떤 사용자는 3천만 명의 팔로워가 있어서, 단일 트윗 작성이 홈 타임라인에 3천만 건 이상의 쓰기 요청을 유발할 수도 있다. 따라서, 적시에 트윗을 전송하는 작업이 (트위터는 5초 이내에 팔로워에게 트윗을 전송하려고 노력한다) 중요한 과제이다.
트위터 사례에서 사용자당 팔로워의 분포는 팬아웃 부하를 결정하기 때문에 확장성을 논의할 때 핵심 부하 매개변수가 된다.
트위터는 최종적으로, 두 가지 방식을 혼합해서 사용한다. 대부분의 사용자의 트윗은 두 번째 방법처럼 팬아웃 캐시 쓰기를 사용한다. 반면, 유명인의 트윗은 팬아웃하지 않고, 타임 라임 읽기 요청이 발생한 경우에 사용자의 홈 타임라인에 합친다.
부하 매개변수를 선택하여 부하를 기술한 뒤, 부하가 증가할 때 어떤 일이 일어나는지 조사할 수 있다.
부하 매개변수를 증가시키면서 목적에 따라 자원(CPU, 메모리, 네트워크 대역폭 등)과 성능 중 하나를 고정시킬 수 있다.
두 방법 모두 성능 수치가 필요하다. 따라서 시스템 성능에 대해 간단히 살펴본다.
처리량(throughput): 초당 처리할 수 있는 레코드 수나 일정 크기의 데이터 집합으로 작업을 수행할 때 걸리는 전체 시간
응답 시간(response time): 클라이언트가 요청을 보내고 응답을 받는 사이의 시간. 매번 다를 수 있으므로, 응답 시간은 측정 가능한 값의 분포로 생각해야 한다.
응답시간은 평균보다는 중앙값(median)과 같은 백분위(percentile)를 사용하는 편이 좋다. 평균은 얼마나 많은 사용자가 실제로 지연을 경험했는지 알려주지 않기 때문이다.
비공유(shared-noting) 아키텍처: 다수의 장비에 부하를 분산하는 아키텍처.
탄력적(elastic) 시스템: 부하 증가를 감지하면 컴퓨팅 자원을 자동으로 추가하는 시스템
탄력적인 시스템은 부하를 예측할 수 없을 만큼 높은 경우 유용하지만, 수동으로 확장하는 시스템이 더 간단하고 운영상 예상치 못한 일이 더 적다.
대개 대규모로 동작하는 시스템의 아키텍처는 해당 시스템을 사용하는 애플리케이션에 특화돼 있다. 범용적이고 모든 상황에 맞는(one-size-fits-all) 마법의 확장 아키텍처(magic scaling source)는 없다.
특정 애플리케이션에 적합한 확장성을 갖춘 아키텍처는 주요 동작이 무엇이고 잘 하지 않는 동작이 무엇인지에 대한 가정을 바탕으로 구축한다. 이 가정은 곧 부하 매개변수가 된다.이 가정이 잘못되면 확장에 대한 헛수고가 되거나 최악의 경우 역효과를 낳는다. 초기 단계나 검증되지 않은 제품의 경우 미래를 가정한 부하에 대비해 확장하기보다는 빠르게 반복해서 제품 기능을 개선하는 작업이 좀 더 중요하다.
소프트웨어의 비용은 대부분 초기 개발보다 유지보수에 들어간다.
유지보수 비용을 줄이기 위한 소프트웨어 시스템 설계 원칙 세 가지
신뢰성, 확장성을 달성하기 위한 쉬운 해결책은 없다. 그보다 유지보수성(운용성, 단순성, 발전성)을 염두에 두고 시스템을 생각하려 노력해야 한다.
좋은 운영성이란 동일하게 반복되는 태스크를 쉽게 수행하게끔 만들어 운영팀이 고부가가치 활동에 노력을 집중한다는 의미.
우발적 복잡도(accidental complexity) 를 제거하기 위한 최상의 도구는 추상화다.
애자일(agile) 작업 패턴은 변화에 적응하기 위한 프레임워크를 제공한다. 애자일 커뮤니티는 테스트 주도 개발(test-driven development, TDD)과 리팩토링 같이 자주 변화하는 환경에서 소프트웨어에를 개발할 때 도움이 되는 기술 도구와 패턴을 개발하고 있다.