Search
Duplicate

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

목차

개요

인터넷은 잘 동작하고 있기에 대부분의 사람들은 인공의 무언가라기보다 태평양 같은 천연 자원으로 생각한다. 이런 규모의 기술이 이토록 오류가 없었던 적은 언제가 마지막일까? - 앨런 케이, Dr. Dobbs 저널 인터뷰에서(2012)
1장을 들어가며 맨 처음 나오는 인용문이다. 이렇게 인터넷이 잘 동작한다고 말할 수 있는 근거는 무엇일까? 우선 인터넷의 구조부터가 TCP/IP 통신 프로토콜을 이용해 정보를 주고받는 컴퓨터 네트워크로 전 세계의 모든 통신 네트워크를 하나의 통신망 안에 연결(international Network) 하면서 인터네트워크(internetwork)가 되고 약어인 ineternet이 주로 알려지게 된 것으로 보인다.
이처럼 여러 네트워크의 네트워크가 하나의 통신망에서 연결된 환경이다보니, 특정 통신망이 문제가 생기더라도 그 통신망의 문제이지 인터넷이 문제라고는 구분짓지 않아서 오류가 없었다고 판단하는게 아닐까? 추측해본다.
과거의 애플리케이션들은 하드웨어적인 컴퓨팅 능력(CPU연산 처리 능력)에 제약을 받는 경우가 많았기에 계산 중심( compute-intensive)적이였다.
그렇기에 현재에 이르러 하드웨어적인 컴퓨팅 능력이 기하급수적으로 높아진 지금에 와서는 이런 CPU성능의 제약은 거의 제약이 안되는 상황이 되었다. 오히려 이제는 이런 연산 능력보다는 데이터의 양, 데이터의 복잡도, 데이터의 변화 속도가 중요한 요소가 되었다.
즉, 오늘날의 애플리케이션들은 데이터 중심적(data-intensive)이 되었다.
그리고 이런 데이터 중심 애플리케이션들이 필요로 하는 공통 기능들을 제공하는 표준 구성 요소(standard building block)들로 만드는데 있어서 다음과 같은 요소들을 필요로 한다.
데이터베이스: 구동 및 기타 애플리케이션에서 언제든 다시 데이터를 찾을 수 있게 데이터를 관리
캐시: 읽기 속도 향상을 위해 고비용의 수행 결과를 기억
검색 색인(search index): 키워드를 통한 데이터 검색 및 다양한 필터링 기능 제공
스트림 처리: 비동기 처리를 위한 다른 프로세스 메세지 전송
일괄 처리(batch processing): 주기적으로 대량의 누적된 데이터 분석
이런 요소들을 우리는 데이터 시스템이 성공적으로 추상화되었기에 쉽게 떠올릴 수 있다.
하지만, 애플리케이션마다 다른 요구사항을 해결하기 위해서 이런 추상적인 요소들을 구체화 시키기 위해서는 여러 조건들을 고려해야 한다. 데이터베이스 시스템도 여러 종류가 다른 장/단점을 가지고 있고, 캐싱의 범위나 접근 방식, 다양한 방식의 검색 색인 구축 등 추상적인 요소의 구체화를 위해 고려해야 할 부분들이 많다.
이 책에서는 이런 고민들을 해결하여 다음과 같은 조건들을 충족하는 데이터 시스템을 구축하기 위한 노력을 하는 것으로 책을 시작한다.
신뢰성
확장성
유지보수성

데이터 시스템

데이터베이스와 메세지 큐는 일정 기간 동안 데이터를 저장한다는 공통점이 있지만, 접근 방식이나 성능 특성에서 몹시 다르기에 구현 방식도 다르다. 그렇기에 매우 다른 범주에 속한다고 생각할 수 있다. 하지만 이런 도구들이 데이터 시스템이라는 포괄적으로 묶이는 이유는 무엇일까?
이는 갈수록 많은 애플리케이션의 다양한 요구사항을 단일 도구로 충족시키지 못하게 되면서, 이런 요구사항을 만족시키기 위한 효율적인 태스크(task)로 나누고 이런 다양한 도구를 연결하는 것이다.
다양한 구성 요소를 결합한 데이터 시스템 아키텍처
내부적으로 애플리케이션 도구를 통해 다양한 도구를 핸들링 하고 있는데, 사용자에게는 API를 제공하며 클라이언트가 이런 내부까진 모르도록 구현 세부 사항을 숨긴다.
즉, 개발자는 이제 애플리케이션 개발자일뿐만 아니라 데이터 시스템 설계자이기도 하다는 것이다.
그렇기에 우리는 다음 세 가지 키워드를 중점으로 신뢰할 수 있는 시스템을 구축하는 방법에 대해 알아보고 학습해보도록 하자.
신뢰성(Reliablility)
여러 문제들을 직면하게 되더라도 시스템은 지속적으로 올바르게 동작해야 한다.
확장성(Scalability)
시스템의 데이터 양, 트래픽 양, 복잡도가 증가할 떄 이를 처리할 적절한 방법이 있어야 한다.
유지보수성(Maintainability)
모든 사용자가 시스템 상에서 생산적으로 작업할 수 있게 해야 한다.

신뢰성

여러 문제들을 직면하게 되더라도 시스템은 지속적으로 올바르게 동작해야 한다.
소프트웨어는 다음과 같은 여러 문제들을 겪게 된다.
사용자의 실수
의도와 다른 사용법
허가되지 않은 접근과 오남용
정상적인 사용이나 예상되는 범위의 트래픽에서 올바르게 동작하는것은 어렵지 않지만, 위와 같은 여러 문제들에서도 올바르게 동작할 경우 우리는 무언가 잘못되더라도 지속적으로 올바르게 동작하는 소프트웨어에 신뢰를 가질 수 있을 것이다.
즉, 결함(fault)를 예측하고 대처할 수 있는 내결함성(fault_tolerant) 또는 탄력성(resilient)을 지닌 소프트웨어라 할 수 있다.
장애(failure)와 결함(fault)는 동일하지 않다.
결함은 사양에서 벗어난 시스템의 한 구성 요소를 의미하지만, 장애는 사용자에게 필요한 서비스를 제공하지 못하고 서비스가 멈추는 경우를 말한다.
우리는 결함을 완전히 없애지는 못하더라도 최소화하기위해 다음과 같은 여러 노력을 할 수 있다.
고의적으로 결함을 발생시켜 내결함성 시스템의 보완 하는 방식으로 넷플릭스(Netflix)의 카오스 몽키(Chaos Monkey)가 이런 방식을 사용.
소프트웨어의 신뢰성을 낮추는 요소는 무엇이 있고, 어떻게 대응할 수 있을까?

하드웨어 결함

하드디스크의 평균 장애 시간(mean time to failure, MTTF)은 약 10 ~ 50년으로 보고됐다.
즉, 10,000개의 디스크로 구성된 저장 클러스터는 디스크가 하루에 하나정도는 죽는다고 예상 할 수 있다. 그래서 이렇게 하드웨어적인 결함이 빈번하게 발생할 때 대응하기 위하여 각 하드웨어 구성 요소에 중복(redundancy)을 추가할 수 있고, 일반적으로 많이 사용된다. 그래서 하나의 디스크가 죽는다고 해도 중복된 다른 디스크를 사용하여 내결함성을 확보할 수 있다.
그런데 데이터 양과 애플리케이션의 계산 요구가 늘어나면서 애플리케이션에 사용되는 장비의 수가 늘어나고 이에 비례하여 하드웨어 결함율도 높아지게 되었다.
우리가 일반적으로 많이 사용하는 아마존 웹 서비스(Amazon Web Service, AWS)와 같은 클라우드 플랫폼 역시 단일 장비 신뢰성보다 유연성(flexibility)과 탄력성(elasticity)을 우선으로 처리하게 설계되었기 때문에 이런 결함에서 자유로울 수 없다.
따라서 소프트웨어 내결함성 기술을 사용하거나 하드웨어 중복성을 추가하여 전체 장비의 손실을 견딜 수 있는 시스템으로 옮겨가는 추세다.

소프트웨어 오류

소프트웨어에서 발생하는 결함은 하드웨어 결함보다 예상하기가 더 어렵고, 각 노드간의 상관관계로 인해 하나의 결함이 다수의 결함으로 확장되는 경향이 있다. 예를 들면 다음과 같다.
잘못된 입력으로 모든 애플리케이션 서버 인스턴스가 죽는 문제
공유 자원(CPU, 메모리, 디스크 공간, 네트워크 대역폭)을 과도하게 사용하는 일부 프로세스
시스템의 속도가 느려져 반응이 없거나 잘못된 응답을 반환하는 서비스
한 구성 요소의 작은 결함이 다른 구성 요소의 결함을 야기하고 연쇄적으로 결함을 유발하는 연쇄 장애(cascading failure)
심지어 이런 문제들은 특정 조건이 충족되기전에는 길게는 몇 년 이상 발생하지 않아서, 발생 시점에 서비스에 치명적인 타격을 줄 수 있고, 디버깅까지 많은 시간이 소요되어 문제가 될 수 있다.
이런 소프트웨어의 체계적 오류 문제는 신속한 해결책이 없다.
그렇기에 최대한 테스트를 철저하게 진행하고, 프로세스를 격리하여 연쇄를 막고, 프로세스가 죽더라도 재시작 할 수 있도록 처리한 뒤 지속적인 모니터링 및 분석 리팩토링으로 소프트웨어 결함이 발생하지 않도록 예방해야 한다.

인적 오류

연구에 따르면 대규모 인터넷 서비스가 문제가 발생해 중단되는 원인은 하드웨어 결함이 10~25%인 반면, 대다수의 원인으로 운영자의 설정 오류인것으로 나타났다.
그렇기에, 다음과 같은 다양한 방법을 사용해 인적 오류의 발생율을 낮출 필요가 있다.
오류가 최소화 될 수 도록 설계한다. (ex: 추상화, API, 관리 인터페이스 등)
비 프로덕션 샌드박스(sandbox)를 제공해 테스트를 한다.
단위 테스트, 통합 테스트, 수동 테스트 등 철저한 테스트로 검사하라.
성능지표나 오류율 등을 모니터링하라.
소프트웨어 사용법을 교육하라.

확장성

서비스 오픈 초기에는 알려지지 않아 동시 사용자 수가 100명이 안될 수 있다.
하지만, 이 서비스가 시간이 흐르면서 동시에 10만명에서 1,000만명까지 사용자 수가 늘어날 수 있다. 이렇게 부하 증가가 발생하면 필연적으로 성능 저하가 발생할 수 밖에 없고, 처음에 안정적으로 동작하는 기능이 계속 안정적으로 동작하지 않을 확률이 높다.
여기서 확장성은 증가한 부하에 대해 대처하는 시스템 능력을 설명하는데 사용하는 용어로, 시스템이 특정 방식으로 커질 때 이에 대처하기 위한 선택과 추가 부하를 다루기 위한 계산 자원의 투입 방식이다.

부하 기술하기

부하는 웹 서버의 초당 요청 수, DB의 read/write 비율, 동시 활성 사용자(active user), 캐시 적중률과 같은 부하 매개변수(load parameter)로 설명할 수 있다. 여기서 이런 지표들을 볼 때 평균을 보고 판단해도 되는 경우도 있지만, 반대로 소수의 극단적인 경우가 병목 현상의 원인일 수 있으니, 주의해야 한다.
이에 대해 조금 더 구체적으로 설명하기위해 트위터(Twitter)를 예로들어 보자. 트위터의 주요 동작 두 가지는 다음과 같다.
트윗(tweet) 작성
: 사용자는 팔로워에게 새로운 메시지를 게시할 수 있다.(평균 초당 4.6k 요청, 피크일 때 초당 12k 요청 이상)
홈 타임라인(timeline)
: 사용자는 팔로우한 사람이 작성한 트윗을 볼 수 있다.(초당 300k 요청)
여기서 확장성 문제는 주로 팬 아웃(fan-out) 문제로 발생한다.
(fan in, 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
SQL
복사
2.
각 수신 사용자용 트윗 우편함처럼 개별 사용자의 홈 타임라인 캐시를 유지한다.(그림1-3 참고) 사용자가 트윗을 작성하면 해당 사용자를 팔로우하는 사람을 모두 찾고 팔로워 각자의 홈 타임라인 캐시에 새로운 트윗을 삽입한다. 그러면 홈 타임라인의 읽기 요청은 요청결과를 미리 계산했기 때문에 비용이 저렴하다.
트위터는 최초 1번 방식을 사용하였는데, 시스템이 홈 타임라인 질의 부하를 버텨내기 힘들어지면서 2번 방식으로 전환했다. 평균적인 트윗 게시 요청량이 홈 타임라인 읽기 요청량에 비해 훨씬 적기 때문에 훨씬 잘 동작한다. 그래서 이 경우 쓰기 시점에 더 많은 일을 하고, 읽기 시점에 적은 일dmf 하는게 바람직하다.
물론, 이 방식은 반대로 트윗 작성에 필요한 비용이 높아진다는 문제가 있다. 평균적으로 트윗이 75명의 팔로워에게 전달된다고 하면 초당 4.6k의 트윗은 홈 타임라인 캐시에 초당 345k건의 쓰기가 된다. 하지만 평균이 아니라 특정 인플루언서의 경우 팔로워가 3천만 명이 넘을 경우 트윗 한 번 작성에 들어가는 비용이 엄청나게 커진다.
그래서 트위터는 이 두 접근 방식을 혼합해서 쓰는 혼합형(hybrid)으로 접근하고 있다.
일반적인 사용자의 경우 2번 방식을 사용하고, 팔로워 수가 매우 많은 인플루언서들은 팬 아웃에서 제외하고, 사용자가 팔로우한 인플루언서의 트윗을 별도로 가져와 1번 방식처럼 읽는 시점에 사용자의 홈 타임라인에 합치는 방식을 사용한다. 이러한 접근 방식으로 좋은 성능으로 지속적인 전송이 가능하게 되었다. 이 내용은 12장에서 다시 한 번 설명하도록 한다.

성능 기술하기

성능은 부하를 통해 기술할 수 있다.
시스템 부하를 기술하면 부하가 증가할 때 발생하는 일들을 조사하면, 성능 수치가 필요하게 된다.
시스템 자원이 그대로일 때 부하 매개변수가 증가하면 시스템 성능은 어떻게 될 것인가?
부하 매개변수가 증가될 때 시스템 자원을 얼마나 늘려야 하는가?
이 두 가지 질의에 대해 답변하기 위해서는 성능 수치가 필요하다.
일괄 처리 시스템에서는 처리량(throughput)에 관심을 가질 것이고, 온라인 시스템에서는 응답 시간(response time)에 관심을 가질 것이다. 여기서 응답 시간에 대해 좀 더 살펴보자.
클라이언트 측에서 동일한 요청을 여러번 반복해보면 매번 응답 시간이 다른 것을 확인할 수 있다. 그렇기에 응답 시간은 단일 숫자가 아닌 측정 가능한 값의 분포를 확인해야 한다.
그림 1-4을 보면 대부분의 요청은 평균 혹은 평균 이하를 보이지만 가끔 응답 시간이 오래걸리는 특이 값(outlier)이 있다. 이런 특이 값이 발생하는 이유는 다양한 이유일 수 있다.
백그라운드 프로세스의 컨텍스트 스위치(context switch)
네트워크 패킷 손실 & TCP 재전송
가비지 컬렉션 휴지(garbage collection pause)
디스크에서 읽기 강제하는 페이지 폴트(page fault)
서버 랙의 기계적인 진동 등
여기서 평균 응답 시간을 알고 싶다면 산술 평균(arthmetic mean)보다는 백분위(percentile)를 사용하는데, 가장 빠른시간부터 느린 시간까지 정렬한 뒤 중간 지점인 중앙값(median)을 사용한다. 이러한 중앙값은 50분위로 p50이라고 축약할 수 있다. 이는 다음과 같은 의미를 가진다.
사용자 요청의 절반은 중앙값 응답 시간 미만으로 제공된다.
사용자 요청의 절반은 중앙값 응답 시간 초과로 제공된다.
추가적으로 목적에 따라 p95, p99, p999 등을 살펴볼 수도 있다.
이러한 상위 백분위 응답 시간(p99, p999, p9999, …에서 1을 담당하는 응답 시간)을 꼬리 지연 시간(tail latency)이라고 하는데, 이는 서비스의 사용자 경험에 직접 영향을 주기 때문에 중요하다.
예를 들어, 아마존은 내부 서비스의 응답 시간 요구사항을 p999로 기술하는데 보통 응답 시간이 느린 요청을 경험한 고객들은 구매를 많이 하는 고객으로 판단해서이다.
큐 대기 지연(queueing delay)는 높은 백분위에서 응답 시간의 상당 부분을 차지하는데, 서버가 병렬로 처리할 수 있는 작업의 수는 CPU 코어 갯수에 영향을 받기 때문에 소수의 느린 요청 처리만으로 후속 요청들이 아무리 짧더라도 지체되는데, 이러한 현상을 선두 차단(head-of-line blocking)이라 한다.
그렇기 때문에 부하 테스트를 할 때는 클라이언트 측에서 응답 시간과 독립적으로 요청을 보내야 한다. 이전 요청이 완료되길 기다린 다음 요청을 보내면 정상적인 테스트 결과를 보기 어렵다.

꼬리 지연 증폭(tail latency amplification)

그림 1-5와 같이 단일 사용자의 요청이 내부적으로 여러번 호출되는 백엔드 서비스에서 병렬로 호출을 하더라도 가장 느린 호출시간에 맞춰 응답 시간이 결정되기 때문에, N개의 호출에서 N-1개가 모두 100ms미만의 응답 시간이라 할지라도 단 하나가 500ms 이상이라면 이 기능은 500ms이상의 응답 시간을 가지게 되고 이러한 효과를 꼬리 지연 증폭(tail latency amplification)이라 한다.

부하 대응 접근 방식

그럼 이러한 부하 매개변수가 증가하더라도 좋은 성능을 유지하려면 어떻게 해야 할까?
대표적으로 다음 두 가지가 있다.
용량 확장(scaling up), 수직 확장(vertical scaling): 좀 더 강력한 장비로 이동하는 것
규모 확장(scaling out), 수평 확장(horizontal scaling): 다수의 낮은 사양 장비에 부하를 분산하는 것
용량 확장은 비싸고, 규모 확장은 비교적 저렴하지만 비공유 아키텍처(shared-nothing)이다.
상태를 저장하지 않는 서비스는 다수의 장비에 배포하는것도 어려울게 없다. 하지만, 상태를 저장하는(stateful) 데이터 시스템을 분산 설치하는 것은 높은 복잡도를 가지기 대문에, 꼭 분산으로 만들어야 하는 고가용성 요구가 있을 때 까지는 단일 노드에 데이터베이스를 유지하는 것(용량 확장)이 최근까지의 통념이다.
그런데, 분산 시스템을 위한 도구와 추상화가 좋아지면서 일부 애플리케이션에서는 바뀌고 있고, 대용량 데이터나 트래픽을 다루지 않는 상황에도 분산 데이터 시스템을 사용할 가능성이 생겼다.
아키텍처를 결정하는 요소는 읽기/쓰기의 양, 저장할 데이터의 양, 데이터의 복잡도, 응답 시간 요구사항, 접근 패턴등이 있는데, 저장할 데이터의 양이 동일하게 6GB라도 1MB의 크기로 6,144번 요청을 처리하도록 설계한 시스템과 3GB의 크기를 2번 요청하는 것을 처리하기 위해 설계한 시스템은 각기 다를 수 밖에 없다.
그렇기에 애플리케이션이 적합한 확장성을 갖추기 위해서는 아키텍처를 설계할 때 주요 동작이 무엇이고 잘 하지 않는 동작이 무엇인지에 대한 가정을 바탕으로 구축해야 한다.
이런 가정들은 모두 부하 매개변수가 되기 때문에, 이 가정이 잘못되었다면, 부하에 대한 대처가 되지 않을 뿐더러 엔지니어의 구축을 위한 노력은 모두 헛수고가 되거나 최악의 경우 역효과가 날 수도 있다. 그렇기에 스타트업 초기 단계나 검증되지 않은 제품의 경우 과도한 가정으로 확장을 고려한 아키텍처 구축에 시간을 쓰기보다는 빠르게 반복해서 제품 기능을 개선하는 작업이 중요하다.

유지보수성

소프트웨어 비용은 초기 개발보다는 그 이후 지속적으로 이어지는 유지 보수에 들어간다.
무엇을 유지보수하는 것일까?
버그 수정
시스템 운영 유지
장애 조사
새로운 플랫폼 적응
새 사용사례를 위한 변경
기술 채무(technical debt) 상환
새로운 기능 추가
대부분의 개발자는 레거시 시스템을 유지보수하는걸 싫어한다. 내가 아닌 다른 개발자가 작성한 문제가 있는 코드 수정, 이젠 트렌드에서 벗어난 플랫폼에서 작업등 모두 개발자에게 고통을 주는 작업들이다.
그래서 이런 고통을 최소화하기 위해서는 다음 소프트웨어 시스템 설계 원칙을 지킬 필요가 있다.
운용성(operability): 운영팀이 시스템을 원활하게 운영할 수 있게 쉽게 만들어라.
단순성(simplicity): 시스템에 복잡도를 최대한 제거해 새로운 엔지니어가 시스템을 이해하기 쉽게 만들어라.(사용자 인터페이스의 단순성과는 다르다.)
발전성(evolvability): 엔지니어가 쉽게 변경할 수 있게 하라. 그래야 요구사항 변경같은 예기치 않은 사용 사례를 적용하기 쉽다.
유연성(extensiblity)
수정 가능성(modifiability)
적응성(plasticity)

운용성: 편리한 운영

소프트웨어의 제약을 운영을 통해 풀어낼 수도 있다.
이 말은 반대로 좋은 소프트웨어도 운영을 어떻게 하느냐에 따라 신뢰할 수 없는 소프트웨어가 될 수 있다는 말이 된다. 운영의 일부를 자동화 할 수도 있고, 자동화를 해야하는 부분도 있지만, 최초 자동화를 위한 설정이나 동작 확인등은 사람의 몫이다.
좋은 운영팀은 다음과 같은 작업들을 책임져야 한다.
시스템 상태를 모니터링하고 상태가 좋지 않을경우 서비스 복원
시스템 장애, 성능저하 원인 추적
소프트웨어의 플랫폼이나 보안 패치를 최신화
시스템간의 영향도 확인 후 문제 발생 예상 지점 차단 및 해결
배포, 설정 관리 등을 위한 모범 사례와 도구 마련
애플리케이션을 플랫폼 이전하는 등의 복잡한 유지보수 태스크 수행
설정 변경으로 생기는 시스템 보안 유지보수
서비스를 안정적으로 제공및 운영하기 위해 절차 정의
직원의 인사이동이 시스템에 영향이 가지 않도록 히스토리 (지식) 보존

단순성: 복잡도 관리

프로젝트가 커질수록 시스템은 복잡하고 이해하기 어려워진다.
이는 생산성 저하로 이어지고, 이렇게 복잡도 수렁에 빠진 프로젝트를 커다란 진흙 덩어리(big ball of mud)로 묘사하기도 한다. 이러한 복잡도는 다음과 같이 다양한 증상으로 나타난다.
상태 공간의 급증
모듈 간 강한 커플링(tight coupling)
복잡한 의존성
일관성 없는 명명(naming)과 용어
성능 문제 해결을 목표로 한 해킹
임시방편으로 문제를 해결한 특수 사례(special-casing)
이러한 복잡도가 커질수록 유지보수는 힘들어지고 간단한 기능변경에도 필요한 예산이나 일정등이 초과되어 문제가 된다. 그렇기에 복잡도를 줄여 소프트웨어 유지보수성을 높혀야 할 필요가 있다.
시스템을 단순하게 만드는 것이 기능을 줄인다는 의미는 아니다.
우발적 복잡도(accidental complexity)를 줄인다는 의미일수도 있는데, 소프트웨어가 풀어야 할 (사용자에게 보이는) 문제에 내재하고 있지 않고 구현에서만 발생하는 것을 의미한다.
이러한 우발적 복잡도를 줄이는 최상의 도구는 추상화다.
추상화는 깔끔하며 복잡한 내부를 숨길 수 있고, 높은 재사용성을 제공할 수 있다.
우리가 개발에 사용하는 고수즌 프로그래밍 언어인 Java, C, C++등은 기계 언어, CPU 레지스터, 시스템 호출을 숨긴 추상화이고, SQL은 디스크에 기록하고 메모리에 저장한 복잡한 데이터 구조와 다른 클라이언트의 동시 요청과 고장 후 불일치를 숨긴 추상화이다.

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

시스템의 요구사항은 끊임없이 변할 가능성이 크다.
(회사에서 개발한 기능이 서비스 종료까지 영원히 바뀌지 않을 가능성이 얼마나 될까?)
그렇기에 데이터 시스템 변경을 쉽게 하고 변화된 요구사항에 시스템을 맞추기 위해서는 시스템을 간단하게 만들고 추상화를 적극적으로 활용해야 한다.
회사에서는 애자일(agile) 작업 패턴을 사용하고 TDD(Test-Driven Development)와 리팩토링(refactoring)을 이용해 자주 변화하는 소프트웨어 환경에서 사이드 이펙트를 최소화 할 수 있다.

정리

데이터 중심(Data intensive) 애플리케이션이 유용하게 사용되기 위해서는 다양한 요구사항을 충족시켜야 한다.
다양한 요구사항은 다음과 같이 기능적 요구사항과 비기능적 요구사항 두 가지가 있는데, 보통은 기능적 요구사항에만 집중하는 경향이 있다. 하지만, 우리는 비기능적 요구사항에 집중할 필요가 있고, 이번 장에서는 신뢰성, 확장성, 유지보수성에 대해 살펴봤다.
기능적 요구사항
: 여러 방법으로 데이터를 관리(저장,조회,검색,삭제)하는 일
비기능적 요구사항
: 보안, 신뢰성, 법규 준수, 확장성, 호환성, 유지보수성과 같은 일반 속성