12. The Future of Data Systems

지금까지는 현재에 존재하는 것들에 대해서 알아보았다. 이 장에서는 앞으로 어떻게 되어야 할지에 대해서 논해 본다. 이 책의 목적은 애플리케이션과 시스템이 신뢰성 있고, 확장 가능하고, 유지보수 가능하도록 만드는 것이다.

Data Integration

이 책에서 계속해서 등장하는 테마는 주어진 문제에 대해 여러 해법이 있으며 모두 다른 장점, 단점, 트레이드오프가 존재한다는 것이다. 데이터를 저장하고 나중에 찾아보고 싶다는 문제가 있을 때 하나의 정답은 없으며 다른 환경에서 각각 적절한 다른 접근법들이 존재한다. 즉, 소프트웨어 도구의 가장 적절한 선택은 환경에 의존한다. 이런 여러 대안을 봤을 때, 첫 번째 난점은 소프트웨어 제품과 그것이 잘 맞는 환경 사이의 매핑을 알아내는 것이다. 하지만, 심지어 도구들과 그것이 쓰이는 환경간의 매핑을 알아내더라도, 다른 난점이 있다. 복잡한 애플리케이션에서 데이터는 여러 다른 방식으로 쓰일 수 있다.

Combining Specialized Tools by Deriving Data

데이터의 서로 다른 표현식이 증가할수록, 통합 문제는 어려워진다. 데이터베이스와 검색 인덱스 아니더라도, 데이터의 사본을 분석 시스템 내에 보관해야 할 수도 있고, 원본 데이터로부터 파생된 오브젝트의 비표준화된 버전이나 캐시를 보존해야 할 수도 있고, 데이터를 기계 학습, 분류, 랭킹, 추천 시스템으로 전달할 수도 있고, 데이터의 변화에 기반해 알람을 줄 수도 있다. 데이터 통합의 필요성은 데이터 플로우를 전체 조직 내에서 고려하게 될 때 명확해진다.

Reasoning about dataflows

같은 데이터의 사본이 다른 접근 패턴을 만족시키기 위해 여러 저장소 시스템에 유지되어야 한다면, 입력과 출력에 대해 매우 명백해야 한다. 어떤 데이터가 먼저 쓰여지고 어떤 표현식이 어떤 근원으로부터 파생되는가? 데이터를 어떻게 알맞은 포맷으로 알맞은 장소에 넣는가? 애플리케이션이 직접 검색 인덱스나 데이터베이스 모두에 쓰게 하는 것은 두 클라이언트가 동시에 충돌하는 쓰기를 전송하고 두 저장소 시스템이 이를 다른 순서로 처리하는 문제가 발생한다. 모든 사용자 입력을 모든 쓰기의 순서를 매길 수 있는 단일 시스템에 집어넣을 수 있다면 쓰기도 같은 순서로 처리함으로써 데이터의 다른 표현식을 파생해 내는 것도 쉬워질 것이다. 이벤트 로그에 기반한 파생 데이터 시스템을 업데이트하는 것은 결정론적이며 멱등화될 수 있고, 이는 실패로부터 쉽게 복구될 수 있다.

Derived data versus distributed transactions

다른 데이터 시스템을 서로와 일관성 있게 유지할 수 있는 전통적 접근은 분산 트랜잭션을 포함한다. 파생 데이터 시스템에서는 다른 방식으로 비슷한 목표를 이룬다. 가장 큰 차이점은 트랜잭션 시스템은 대개 선형화 가능성을 제공하는데 이는 자신이 쓴 것을 읽는 것과 같은 유용한 보장을 해 준다. 반면, 파생 데이터 시스템은 종종 비동기적으로 업데이트되므로, 같은 타이밍 보장을 기본적으로 제공하지 않는다. 분산형 트랜잭션의 비용을 제공하고자 하는 제한된 환경 속에서 이들은 성공적으로 쓰였지만, 저자는 XA가 실패에 대한 대처 능력과 성능 특성이 좋지 않기 때문에 그 유용성을 크게 제한한다고 생각한다. 좋은 분산형 트랜잭션 프로토콜에 대한 폭넓은 지원의 부재로 인해, 저자는 로그 기반 파생 데이터가 다른 데이터 시스템을 통합하는 데 있어 가장 확실한 접근 방식이라고 믿는다. 뒤에서는 분산형 트랜잭션과 비동기 로그 기반 시스템의 중간 지점에 대해 알아보자.

The limits of total ordering

작은 시스템에서는 완전히 순서화된 이벤트 로그는 가능하다. 하지만 시스템이 커지고 부하가 복잡해질 수록 한계가 드러난다. 대부분의 경우, 완전 순서화된 로그를 만드는 것은 모든 이벤트가 순서를 정하는 단일 리더 노드를 통해야 한다는 것을 필요로 한다. 서버가 지정학적으로 분산된 복수의 데이터 센터들을 통해 퍼져 있다면, 예를 들면 전체 데이터센터가 오프라인이 될 경우 이에 대처하기 위해서는 각 데이터 센터마다 별개의 리더를 둬야 한다. 네트워크 딜레이는 데이터 센터간 동기적 좌표 설정을 비효율적으로 만들기 때문이다. 애플리케이션이 마이크로 서비스에 배포된다면, 흔한 설계는 각 서비스와 그 견고한 상태를 독립적인 유닛으로 두고, 견고하지 않은 상태는 서비스간 공유하도록 한다. 어떤 애플리케이션은 사용자 입력에 대해 즉각적으로 업데이트되는 클라이언트 쪽 상태를 유지하고, 오프라인에서 계속 동작하기도 한다. 공식적으로는, 이벤트의 완전 순서를 결정하는 것은 완전 순서 방송이라 하며, 이는 컨센서스와 동치이다.

Ordering events to capture causality

이벤트간 인과적 연결이 없는 경우에는 전체 순서가 없는 것은 큰 문제가 아니다. 동시적 이벤트는 임의로 순서화될 수 있기 때문이다. 하지만, 인과적 의존성이 미묘하게 발생하는 경우에는 문제가 된다. 연결성 상태를 한 장소에 저장하고 메시지를 다른 장소에 저장하는 시스템에 대해서는 비연결 이벤트와 메시지 전송 이벤트간 순서화 의존성이 유실될 수 있다. 이 경우, 알림은 메시지와 친구 리스트간 효과적인 연결이 되어 이전에 다뤘던 연결의 타이밍 문제들과 연관되어야 한다.

  • 로컬 타임스탬프는 좌표 지정 없이 전체 순서를 정할 수 있으므로, 전체 순서 방송이 가능하지 않은 많은 경우에 도움이 된다.
  • 이벤트 로그를 이용해 사용자가 결정을 내리기 전 시스템의 상태를 기록한 뒤 그 이벤트에 유일한 식별자를 준다면, 그 이후의 이벤트는 인과적 의존성을 기록하기 위해 그 이벤트 식별자를 참조할 수 있다.
  • 충돌 해결 알고리즘은 예상치 못한 순서로 전달된 이벤트를 처리하는 데 도움이 된다.

아마도 시간에 따라, 애플리케이션 개발의 패턴은 인과적 의존성이 효과적으로 포착되도록 하며, 전체 순서 방송의 병목을 통해 모든 이벤트가 지나가는 일 없이도 파생 상태가 정확히 유지되도록 할 것이다.

Batch and Stream Processing

저자는 데이터 통합의 목적은 데이터가 모든 올바른 곳에서 모든 올바른 형태로 존재하게 하는 것이라 한다. 배치와 스트림 처리의 출력은 검색 인덱스, 물질화된 뷰, 사용자에게 보여 줄 추천, 통합 지표 등의 파생 데이터 셋이다. 배치와 스트림 처리는 많은 원리를 공유하지만, 그 근본적 차이는 스트림 프로세서는 한계가 없는 데이터 셋에 작동하며 배치 처리 입력은 알려진 유한 크기라는 것이다. Spark는 스트림을 마이크로배치로 쪼개 배치 처리 엔진 위에서 스트림 처리를 한다. Apache Flink는 배치 처리를 스트림 처리 엔진 위에서 한다.

Maintaining derived stte

배치 처리는 매우 강한 기능적 특성을 갖고 있다. 이는 결정론적 성질을 유도한다는 것인데 잘 정의된 입력과 출력을 갖는 결정론적 함수의 원리는 실패에 대한 대처에만 좋은 것이 아니라 조직 내 데이터 플로우의 원인 파악도 단순화한다. 이론적으로, 파생 데이터 시스템은 동기적으로 유지될 수 있다. 마치 관계형 데이터베이스가 이차 인덱스를 같은 트랜잭션 내에서 동기적으로 업데이트하면서 테이블에 대한 쓰기가 인덱스되는 것처럼. 이차 인덱스는 종종 파티션 경계를 가로지르기도 한다.

Reprocessing data for application evolution

파생 데이터를 유지할 때, 배치와 스트림 처리는 모두 유용하다. 구체적으로, 존재하는 데이터를 재처리하는 것은 시스템을 유지하는 데 있어 좋은 메커니즘을 제공하며, 새 특성과 변경된 요구 사항들을 지원하도록 한다. 파생된 뷰는 완만한 진화를 허용한다. 이러한 완만한 이주의 유용함은 모든 과정의 단계가 어떤 것이 잘못된다면 쉽게 되돌려질 수 있다는 것이다.

The lambda architecture

배치 프로세싱이 역사적 데이터를 재처리하기 위해 사용되고, 스트림 처리가 최근의 업데이트를 처리하는 데 쓰인다면, 이들을 어떻게 처리할까? 이 때 쓰이는 람다 구조는 들어오는 데이터가 변경 불가능한 이벤트를 항상 성장하는 데이터셋에 덧붙여짐으로써 기록되어야 한다는 것이다. 이는 이벤트 근원과 비슷하다. 람다 접근법에서는, 스트림 프로세서는 이벤트를 소모해 뷰에 대한 근사적 업데이트를 빠르게 제공한다. 람다 아키텍쳐는 더 나은 데이터 시스템 설계를 고안하는 데 영향을 준 아이디어이다. 구체적으로는 파생 뷰의 원리에 변경 불능한 이벤트의 스트림과 필요할 때 재처리되는 이벤트를 적극적으로 적용함으로써. 하지만, 저자가 생각하기에는 람다 아키텍쳐도 몇 가지 문제가 있다: 배치와 스트림 처리 프레임워크 모두에서 같은 로직이 동작하도록 유지하는 것은 적지 않은 추가적인 노력을 필요로 한다. 스트림 파이프라인과 배치 파이프라인이 별개의 출력을 제공하므로, 사용자 요청에 대응하기 위해서는 병합되어야 한다. 전체 역사적 데이터셋을 재처리할 능력이 있더라도, 이를 빈번하게 하는 건 큰 데이터셋에서 비싼 연산이다.

Unifying batch and stream processing

더 최근의 진전들은 람다 구조의 이득을 손해 없이 누릴 수 있도록 했다. 배치 연산과 스트림 연산이 같은 시스템에서 구현되게 함으로써. 배치와 스트림 처리를 한 시스템에 통합하는 것은 다음의 특성을 요구로 하는데, 이는 점차 폭넓게 사용 가능해지고 있다: 최근의 이벤트의 스트림을 다루는 동일한 처리 엔진을 따라 역사적 이벤트를 재실행하도록 하는 것. 스트림 프로세서에 대한 정확히 한 번 시맨틱 – 즉, 실패가 실제로 일어났어도 출력은 실패가 없었던 것처럼 나오게 하는 것. 구간 설정을 처리 시간이 아닌 이벤트 시간에 따라 하는 것 – 처리 시간은 역사적 이벤트를 재처리할 때는 무의미하므로.

Unbundling Databases

가장 추상적인 수준에서, 데이터베이스, Hadoop, 그리고 운영 체제는 동일한 기능을 한다. 하지만 물론 실질적인 차이점들이 존재한다. Unix와 관계형 데이터베이스는 정보 관리 문제를 매우 다른 철학을 통해 접근하였다. 어떤 접근법이 더 나은가? 이 철학들간 논쟁은 수십년 간 이어져 왔으며 아직도 해결되지 않았다. 이 절에서는 저자는 두 철학의 장점만 취해서 조화시키도록 시도한다.

Composing Data Storage Technologies

이 책은 데이터베이스와 그 동작 원리에 대해 설명하였다. 이는 필드의 값에 기반해 기록을 효율적으로 탐색할 수 있도록 하는 이차 인덱스, 쿼리 결과의 일종의 미리 계산된 캐시인 물질화된 뷰, 다른 노드들의 데이터의 사본을 업데이트된 상태로 유지시키는 복제 로그, 텍스트 내 키워드 탐색을 허용케 하고 어떤 관계형 데이터베이스들에는 내장된 완전 텍스트 검색 인덱스. 10-11장에서도 비슷한 테마들이 등장하였다. 사람들이 배치와 스트림 프로세서를 만들 때 쓰는 데이터베이스의 내장 특성과 파생 데이터 시스템 내장 특성들 사이에는 비슷한 점이 있는 것으로 보인다.

Creating an index

관계형 데이터베이스에 CREATE INDEX로 새 인덱스를 만들 때 어떤 일이 일어나는지를 생각해 보자. 이 과정은 새 팔로워 복제를 셋업하는 것과 눈여겨볼만 하게 닮았다. 또한 스트리밍 시스템에서 변경 데이터 포착을 붓스트래핑하는 것과도 매우 비슷하다. CREATE INDEX를 수행할 때, 데이터베이스는 존재하는 데이터셋을 필수적으로 재처리하고, 인덱스를 존재하는 데이터에 대한 새 뷰로 파생시킨다.

The meta-database of everything

저자는 전체 조직을 통한 데이터 플로우는 하나의 거대한 데이터베이스처럼 시작된다고 본다. 이처럼 보였을 때, 배치와 스트림 프로세서는 트리거, 저장된 프로시져, 물질화된 뷰 유지보수 루틴의 꼼꼼한 구현과도 같다. 다른 저장소와 처리 도구가 그럼에도 불구하고 응집된 시스템으로 조합될 수 있는 두 가지의 방법이 있다:

  • 연합 데이터베이스: 통합된 읽기. 연합 데이터베이스나 폴리스토어라는 접근법을 통해서 넓은 범위의 기반 저장소 엔진이나 처리 방법에 대한 통합된 쿼리 인터페이스를 제공할 수 있다. 연합 쿼리 인터페이스는 고수준 쿼리 언어와 우아한 시맨틱을 가진 단일 통합된 시스템의 관계적 전통을 따르지만, 구현은 더 복잡하다.
  • 분산 데이터베이스: 통합된 쓰기. 연합이 여러 다른 시스템들에 대해 일기 전용 쿼리를 수행한다면, 이 시스템간 쓰기를 동기화하는 데에는 좋은 해답을 주지 못한다. 이 때는 분산된 접근법을 쓰는데 이것은 작은 도구는 한 작업을 잘 하고 균일한 저수준 API를 통해 통신하고 고수준 언어를 통해 조합될 수 있는 Unix 전통을 따른 것이다.

Making unbundling work

연합과 분산은 동전의 양면과도 같다. 쓰기를 동기화하는 전통적 접근법은 비균일한 저장소 시스템 위에서의 분산 트랜잭션을 필요로 하는데, 저자는 이것이 틀렸다고 생각한다. 단일 저장소나 스트림 처리 시스템 내에서의 트랜잭션은 가능하지만, 데이터가 다른 기술간 경계를 가로지른다면 저자는 멱등 쓰기를 하는 비동기적 이벤트 로그가 더 강건하고 실현 가능한 접근법이라 믿는다. 로그 기반 통합의 큰 이득은 여러 컴포넌트간 느슨한 연결인데 이는 그 스스로를 두 가지 방법으로 드러낸다: 시스템 수준에서 비동기 이벤트 스트림은 시스템을 컴포넌트 각각의 성능 저하나 고장에 대해 훨씬 더 강건해지게 한다. 사람 수준에서 분산 데이터 시스템은 다른 소프트웨어 컴포넌트와 서비스가 각각 다른 팀들에 의해 독립적으로 개발, 개선, 유지될 수 있도록 한다.

Unbundled versus integrated systems

분산이 사실 미래의 길이라면 그것은 현재 형태의 데이터베이스를 대체하지는 못할 것이다. 이들은 지금껏 그래왔듯 필요해질 것이다. 인프라의 여러 다른 부분을 동작시키는 복잡도는 문제가 될 수 있다. 분산의 목표는 특정한 부하에 대해 성능 관점에서 각 데이터베이스와 경쟁하고자 하는 것이 아니다. 그러므로, 필요한 모든 것을 해 주는 단일 기술이 있다면 저수준 컴포넌트로부터 그것을 직접 구현하는 것을 시도하기보다는 단순히 그 제품을 사용하는 것이 낫다.

What’s missing?

데이터 시스템을 조합하는 도구는 더 나아지고 있지만 저자는 하나의 큰 부분이 부족하다고 생각한다. Unix shell에 대응되는 분산 데이터베이스의 대응품이 존재하지 않는다. 이것이 나온다면 캐시를 미리 계산하고 업데이트하기 훨씬 쉬워질 것이다.

Designing Applications Around Dataflow

특수화된 저장소와 처리 시스템을 애플리케이션 코드와 조합시키는 분산 데이터베이스 접근은 데이터베이스 안팎의 접근법이라 불린다. 이는 저자가 생각하기에 모두가 배워야 한다고 생각하는 다른 사람들의 아이디어를 결합한 것이다. 스프레드시트들조차도 메인스트림 프로그래밍 언어보다는 훨씬 앞선 데이터 플로우 프로그래밍 능력을 갖고 있다. 저자는 대부분의 데이터 시스템이 이런 것들이 갖고 있는 특성으로부터 배워야 한다고 생각한다. 이 절에서 저자는 이 아이디어를 확장해 분산된 데이터베이스와 데이터 플로우의 아이디어에 기반해 애플리케이션을 만드는 방법들을 탐색해 본다.

Application code as a derivation function

한 데이터셋이 다른 데이터셋으로부터 파생될 때 이는 어떤 종류의 변환 함수를 한다. 예를 들면 이차 인덱스는 간단한 변환 함수를 통한 파생 데이터셋이라 볼 수 있다. 완전 텍스트 검색 인덱스는 언어 감지, 단어 의미화, 가지치기와 표제어 결정, 동의어 식별 뒤에 효율적인 탐색을 위한 자료 구조를 만드는 것 등 여러 자연어 처리 함수를 적용해 만든 것이라 볼 수 있다. 기계 학습 시스템에서 모델은 여러 특성 추출과 통계적 분석 함수를 데이터에 적용해 파생된 것이라 할 수 있다. 캐시는 사용자 인터페이스에 표시될 형태의 데이터 집합을 종종 포함한다. 이차 인덱스를 위한 파생 함수는 데이터베이스의 핵심 특성으로 내장되도록 매우 흔히 요구된다. 그래서 CREATE INDEX로 발동시킬 수 있다. 파생 데이터셋을 만드는 함수가 이차 인덱스를 만드는 것과 같은 간단한 표준 함수가 아니라면 애플리케이션 특정 관점을 다루기 위해 커스텀 코드가 필요하다.

Separation of application code and state

이론적으로 데이터베이스는 운영체제처럼 임의의 애플리케이션 코드에 대한 배포 환경이다. 반면에, Mesos, YARN, Docker, Kubernetes와 같은 배포와 클러스터 관리 툴들은 애플리케이션 코드를 동작하는 목적 하에 특별히 설계되었다. 저자는 시스템의 일부가 견고한 데이터 저장소를 특수화하는 부분을 갖고, 애플리케이션 코드를 작동하는 데 있어 특수화하는 부분을 갖는 것이 합당하다고 생각한다. 대다수의 웹 애플리케이션은 오늘날 상태 없는 서비스로 배포되는데, 여기서 사용자 요청이 임의의 애플리케이션 서버로 라우팅되고 서버는 요청에 대한 응답을 보내고 나면 그 요청에 대한 모든 것을 잊어버린다. 이런 웹 애플리케이션 모델에서는, 데이터베이스는 네트워크를 통해 동기적으로 접근되는 변경 가능한 공유 변수처럼 동작한다고 볼 수 있다. 하지만 대부분의 프로그래밍 언어에서 변경 가능한 변수에서의 변경 사항을 추적할 수는 없고, 주기적으로 읽을 수만 있을 뿐이다. 데이터베이스는 이런 수동적인 접근법을 변경 가능한 데이터에 상속하였다.

Dataflow: interplay between state changes and application code

애플리케이션을 데이터플로우에 대해 생각하는 것은 애플리케이션 코드와 상태 관리의 관계를 재협상하는 것을 의미한다. 여기에 대해서 데이터베이스의 변경 로그들을 구독할 수 있는 이벤트의 스트림으로 다룰 수 있음을 알아보았다. 데이터가 변경되거나, 이차 인덱스가 인덱스되는 테이블의 변경을 반영하기 위해 업데이트됨으로써 트리거가 발생하면 비슷한 일들이 데이터베이스 내에 일어난다. 염두해야 할 중요한 것은 파생 데이터를 유지하는 것은 메시징 시스템이 전통적으로 설계된 비동기 작업 실행과는 다르다는 점이다. 파생 데이터를 유지할 때는 상태 변화의 순서가 종종 중요하다. 실패에 대한 대처는 파생 데이터에 있어 필수적이다. 안정적인 메시지 순서화와 실패에 대처 가능한 메시지 처리는 매우 엄중한 ㅇ구이지만, 분산 트랜잭션보다는 훨씬 덜 비싸고 기능적으로 더 강건하다. 애플리케이션 코드는 데이터베이스에 내장된 파생 함수들이 일반적으로 제공하지 않는 임의의 처리를 할 수 있다.

Stream processors and services

애플리케이션 개발의 현재 트렌디한 스타일은 기능을 RES API와 같은 동기적 네트워크 요청을 통해 통신하는 서비스 집합들로 분해하는 것을 포함한다. 스트림 연산자를 데이터플로우 시스템으로 조합하는 것은 마이크로서비스 접근법과 유사성을 많이 갖는다. 데이터플로우 시스템은 더 좋은 실패에 대한 대처 말고도 더 좋은 성능을 얻는다. 마이크로서비스 접근법에서, 구입을 수행하는 코드는 교환율 서비스나 데이터베이스를 쿼리해서 특정 통화의 현재 환율을 알아볼 수 있다. 데이터플로우 접근법에서, 구입을 처리하는 코드는 미리 환율 업데이트의 스트림을 구독해서 그것이 변경될 때마다 로컬 데이터베이스에 현재 환율을 기록해둘 수 있다. 두 번째 접근법은 다른 서비스로의 동기적 네트워크 요청을 로컬 데이터베이스에 대한 쿼리로 대체하였다. 이 때의 결합은 시간 의존적이다. 필요할 때 현재 상태를 쿼리하는 대신 변경 스트림을 구독함으로써, 이는 스프레드시트 같은 계산 모델에 더 가까워진다.

Observing Derived State

추상적인 레벨에서, 직전 절에서 논의된 데이터플로우 시스템은 파생 데이터셋을 만드는 프로세스를 만들고 이들을 업데이트되게 유지시키는 과정을 제공해 준다. 하지만 왜 처음부터 파생 데이터셋을 만들어야 하는가? 쓰기 경로와 읽기 경로는 데이터에 대해 그것이 수집된 시점부터 그것이 소비된 시점까지의 전체 여정을 아우를 수 있다. 파생 데이터셋은 쓰기 경로와 읽기 경로가 만나는 지점이다.

Materialized views and caching

완전 텍스트 탐색 인덱스는 좋은 예이다. 인덱스가 없다면 검색 쿼리는 모든 문서를 탐색해야 하는데 이는 문서의 수가 많다면 매우 비싼 연산이 된다. 반면, 가능한 모든 쿼리에 대한 검색 결과를 미리 계산해두는 것도 상상해볼 수 있다. 다른 선택지는 가장 흔한 쿼리들의 고정된 집합에 대한 검색 결과만 미리 계산해 둠으로써 인덱스를 거칠 필요 없이 빠르게 제공될 수 있도록 하는 것이다. 인덱스는 쓰기 경로와 읽기 경로간 가능한 유일한 경계가 아니다. 쓰기 경로와 읽기 경로에서 이뤄지는 일의 경계가 대상에 따라 바뀌는 것도 가능하다.

Stateful, offline-capable clients

저자는 쓰기와 읽기 경로간 경계의 아이디어가 흥미롭다고 생각한다. 그 경계를 움직이는 것을 논하고 그 움직임이 실제로 무엇을 의미하는지를 탐색해볼 수 있기 때문이다. 최근 20년동안 웹 애플리케이션의 큰 부분은 애플리케이션 개발에 되해 쉽게 가정될 수 있는 특정한 가정들을 불러왔다. 전통적으로, 웹 브라우저들은 인터넷 연결이 되어 있을 때만 유용한 일들을 할 수 있는 상태 없는 클라이언트들이었다. 그러나 이들은 변화해 왔는데 이러한 변화하는 기능들은 오프라인 우선 애플리케이션에 대해 새로워진 관심을 불러일으켰다. 이는 인터넷 연결을 요구하지 않고 네트워크 연결이 가능해지면 백그라운드의 원격 서버와 동기화되어 같은 기기에서의 국소적 데이터베이스를 사용하는 것이다. 중앙 데이터베이스를 이야기할 때 무상태 클라이언트의 가정으로부터 최종 사용자 기기에서 유지되는 상태로의 이동을 한다면, 새 기회의 세상이 열린다.

Pushing state changes to clients

특정 웹 페이지에서, 웹 브라우저의 페이지를 로드하고 데이터가 서버에서 그 뒤에 변경된다면, 브라우저는 페이지가 다시 로드되기 전까지 변경점을 찾지 못한다. 더 최근 프로토콜은 HTTP의 기본 요청/응답 패턴을 너머 이동하였다. 우리의 쓰기 경로와 읽기 경로의 모델에 대해서, 상태 변화를 활발하게 클라이언트 경로에게 완전히 전달하는 것은 최종 사용자에게 쓰기 경로를 완전히 확장하는 것을 말한다. 기기들은 어떤 때는 오프라인일 수도 있고 그 동안에는 서버로부터의 상태 변화에 대한 알림을 받지 못할 수도 있지만 이 문제는 이미 해결된다.

End-to-end event streams

Elm 언어나 React, Flux, Redux같은 유상태 클라이언트와 사용자 인터페이스를 개발하는 최근의 도구들은 내부 클라이언트 쪽 상태를 서버로부터의 반응이나 사용자 입력을 표현하는 이벤트 스트림을 구독함으로써 이미 관리하고 있다. 이는 이벤트 근원과 비슷하게 구조화된다. 이 프로그래밍 모델을 서버로 확장해 상태 변화 이벤트를 이 클라이언트 쪽 이벤트 파이프라인으로 전달하는 것은 매우 자연스럽다. 즉석 메시징이나 온라인 게임같은 어떤 애플리케이션드은 이런 리얼타임 구조를 갖고 있다. 하지만 이를 확장할 때의 문제는 무상태 클라이언트와 요청/응답 상호작용에 대한 가정이 데이터베이스, 라이브러리, 프레임워크, 프로토콜에 매우 깊이 배어들어 있다는 점이다. 쓰기 경로를 완전히 최종 사용자에게 확장하기 위해서는 근본적으로 이러한 시스템들을 만드는 방식을 다시 생각해봐야 한다.

Reads are events too

스트림 프로세서가 파생 데이터를 저장소에 쓰고 사용자가 그 저장소에 쿼리 요청을 할 때 저장소는 쓰기 경로와 읽기 경로간 경계의 역할을 한다는 것을 알아보았다. 많은 경우, 데이터 경로는 스트리밍 시스템과 별개이다. 이 아이디어를 확장할 수 있다. 쓰기와 읽기가 모두 이벤트로 표현되고 다뤄지기 위해 같은 스트림 연산자로 라우팅되면, 읽기 쿼리의 스트림과 데이터베이스간 스트림-테이블 연결을 실제로 수행하는 것이라 볼 수 있다. 요청을 수행하는 것과 연결을 수행하는 것 사이의 이 대응 관계는 매우 근본적이다. 읽기 이벤트의 로그를 기록하는 것은 시스템을 통한 인과적 의존성과 데이터 근원을 추적하는 것에 대해서 이점을 갖고 있다. 견고한 저장소에 읽기 이벤트를 쓰는 것은 인과적 의존성을 더 잘 추적할 수 있게 하지만, 추가적인 저장소와 입출력 비용을 야기한다.

Multi-partition data processing

단일 파티션에만 접촉하는 쿼리에 대해서는, 스트림을 통해 쿼리를 전송하고 반응의 스트림을 모으는 것은 필요 이상일 것이다.하지만, 이 발상은 여러 파티션으로부터 데이터를 조합하는 복잡한 쿼리의 분산된 실행의 가능성을 열어 두며, 스트림 프로세서들에 의해 이미 제공되는 메시지 라우팅, 파티셔닝, 연결의 이득을 취한다. 이 패턴의 다른 예는 사기 방지에서 일어난다. MPP 데이터베이스의 내부 쿼리 실행 그래프도 비슷한 특성을 갖는다.

Aiming for Correctness

데이터를 읽기만 하는 무상태 서비스에서는 무언가가 잘못되어도 큰 일이 아니다. 하지만 데이터베이스처럼 유상태 시스템은 그리 간단하지 않다. 이 때는 신뢰성 있고 올바른 애플리케이션을 만들어야 한다. 이 경우 트랜잭션은 완전히 버려지고 더 좋은 성능과 확장 가능성을 제공하는 모델로 대체되지만 시맨틱은 훨씬 지저분해진다. 특정 애플리케이션을 특정 트랜잭션 분리 레벨이나 복제 설정에 대해서 안전하게 실행할 수 있는지를 판정하는 것은 매우 어렵다. 애플리케이션이 데이터가 가끔 예상 불가능한 방식으로 오염되거나 유실되는 것에 대해 대처할 수 있다면 일은 매우 간단해진다. 하지만, 정확성에 대해 더 강한 보장이 필요하다면 직렬화 가능성과 원자적 수행은 보장된 접근법이지만 이들에는 비용이 따른다. 전통적 트랜잭션 접근법은 사라지지 않지만 그것이 애플리케이션을 정확히 동작시키고 실패에 대해 견고하게 만드는 마지막 방법은 아니라고 저자는 생각한다.

The End-to-End Argument for Databases

애플리케이션이 사용하는 데이터 시스템이 직렬화 가능한 트랜잭션 같은 비교적 강한 안전성 특성을 제공한다고 해서, 애플리케이션이 데이터 유실이나 오염으로부터 완전히 자유로워지는 것이 보장되는 것은 아니다. 애플리케이션 버그는 발생하고 사람들은 실수를 한다. 변경 불가능성은 유용하지만, 그 자체로 만병통치약인 것은 아니다.

Exactly-once execution of an operation

이전에 정확히 한 번 시맨틱의 아이디어를 알아보았다. 두 번 처리하는 것은 데이터 오염의 형태이다. 이를 위한 가장 효율적인 접근법은 연산을 멱등으로 만드는 것이다.

Duplicate suppression

스트림 처리 외에서도 중복을 억제하는 패턴은 여러 다른 곳에서 등장한다. 하지만 이런 중복 억제는 단일 TCP 연결의 맥락 안에서만 동작한다. 클라이언트가 데이터베이스에 재연결해 트랜잭션을 재시도하더라도, TCP 중복 억제의 범위를 벗어나게 된다. 이에 대해 두 단계 수행 프로토콜은 TCP 연결과 트랜잭션 사이의 1:1 매핑을 깬다. 트랜잭션 조정자를 이용해 네트워크가 실패한 이후에 데이터베이스에 재연결한 뒤 의문이 있는 트랜잭션에 대해 그것을 수행할지 중지할지 말해 주기 때문이다. 데이터베이스 클라이언트와 서버간 중복 트랜잭션을 억제하더라도, 최종 사용자 기기와 애플리케이션 서버 사이의 네트워크는 여전히 염려해야 한다. 웹 서버의 관점에서 재시도는 별개의 요청이며, 데이터베이스의 관점에서 그것은 별개의 트랜잭션이다.

Uniquely identifying requests

요청을 여러 단계의 네트워크 연결을 통해서 멱등이 되게 하려면, 데이터베이스로부터 제공되는 트랜젝션 메커니즘에 단순히 의존하는 것으로는 충분치 않다. 요청의 끝 대 끝 플로우를 고려해야 한다. 관계형 데이터베이스는 일반적으로 분리 수준이 약하더라도 고유성 조건을 올바르게 유지한다. 중복된 요청을 억제하는 것 외에도, 예제의 테이블은 이벤트 로그처럼 동작해서 이벤트 근원의 방향을 힌트를 준다.

The end-to-end argument

중복 트랜잭션을 억제하는 이 시나리오는 끝 대 끝 인자라고 불리는 더 일반적인 원리의 한 예일 뿐이다. 이 예에서 의문시되는 함수는 중복 억제였다. 이 끝 대 끝 인자는 데이터의 통합성을 체크하는 데도 적용된다. 비슷한 논거는 암호화에도 적용된다. 저수준 특성들이 그 자체로는 필요한 끝 대 끝 특성을 제공해 주지 않지만, 이들은 여전히 유용하다. 고수준에서의 문제의 확률을 줄여 주기 때문이다.

Applying end-to-end thinking in data systems

애플리케이션이 사용하는 데이터 시스템이 직렬화 가능한 트랜잭션 같은 비교적 강한 안전성 특성을 제공한다고 해서, 애플리케이션이 데이터 유실이나 오염으로부터 완전히 자유로워지는 것이 보장되는 것은 아니다. 이는 좋지 못한데, 실패에 대처하는 메커니즘은 제대로 동작하기 어렵기 때문이다. 트랜잭션은 오랜 기간 동안 좋은 추상화로 보여졌고, 저자는 이것이 유용하다고 믿는다. 하지만 트랜잭션은 비싸다. 특히 불균일한 저장소 기법들을 포함할 때. 이런 이유들에서, 저자는 애플리케이션 특정 양 극단 정확성 특성을 제공하기 쉽게 하면서도 좋은 성능과 좋은 기능적 특성을 대형 분산 환경에서 유지하도록 도와주는 실패에 대처하는 추상화를 찾는 것이 가치있다고 믿는다.

Enforcing Constraints

정확성을 분산 데이터베이스의 발상의 맥락에서 생각해보자. 구체적으로, 고유성 조건에 집중해 보자. 다른 조건들도 매우 비슷하다.

Uniqueness constraints require consensus

분산 세팅에서, 고유성 조건을 강제하는 것은 컨센서스를 필요로 한다. 이 컨센서스를 얻는 가장 흔한 방법은 단일 노드를 리더로 만들고 이에 모든 결정을 일임하는 것이다. 고유성 체크는 고유해야 할 필요가 있는 값에 기반한 파티셔닝으로 확장될 수 있다. 하지만, 비동기 복수 마스터 복제는 선택에서제외된다. 왜냐하면 다른 마스터들이 충돌되는 쓰기를 동시에 받고 값들이 고유하지 않아질 수 있기 때문이다.

Uniqueness in log-based messaging

로그는 모든 소비자가 메시지를 같은 순서로 봄을 보장한다. 이는 완전 순서 방송으로 공식적으로 알려져 있는 보장이며 컨센서스와 동치이다. 스트림 프로세서는 로그 파티션 내 모든 메시지를 단일 스레드로 순차적으로 소모한다. 즉, 로그가 고유해야 할 필요가 있는 값에 기반해 파티션되면, 스트림 프로세서는 명료하고 결정론적으로 여러 충돌하는 연산 중 어느 것이 로그의 첫 번쨰 오는지 결정한다. 이 알고리즘은 전체 순서 방송을 통해 선형화 가능한 저장소를 구현하는 것과 기본적으로 같다. 이 접근법은 유일성 조건 뿐만 아니라 다른 많은 조건에 대해서도 쓰인다.

Multi-partition request processing

조건을 만족하면서 동작이 원자적으로 실행되는 것을 보장하는 것은 여러 파티션이 포함되면 더 흥미로워진다. 데이터베이스에 대한 전통적인 접근법에서, 이 트랜잭션을 실행하는 것은 모든 세 파티션에 대해 원자적 수행을 필요로 하며, 이는 필수적으로 이 파티션들 중 어떤 것에 대해 적용되는 모든 파티션에 대해서도 전체 순서를 강제한다. 하지만 원자적 수행 없이도 동등한 정확성이 분할된 로그를 통해 얻어질 수 있다. 계정간 돈 전송 요청은 클라이언트에 의해 고유 요청 ID가 주어지고 요청 ID에 기반해 로그 파티션에 덧붙여진다. 스트림 프로세서는 요청의 로그를 읽는다. 이후 프로세서들이 신용과 직불 명령어 스트림을 소모하고 요청 ID의 중복을 제거하고 계좌의 변경을 반영한다. 처음 두 단계는 필수적인데 클라이언트가 직접 신용과 직불 명령어를 보낸다면 둘 모두가 일어나거나 둘 모두가 일어나지 않음을 보장하기 위해 두 파티션간 원자적 수행을 필요로 하기 때문이다. 2번쨰 단계에서 스트림 프로세서가 크래시되면 마지막 체크포인트로부터 처리가 재개된다. 지불 계좌가 이 처리로 인해 초과 인출되지 않음을 보장하려면 추가적으로 계좌를 유지하고 트랜잭션을 검증하는 스트림 프로세서를 갖춰야 한다. 다중 파티션 트랜잭션을 2개의 다르게 파티션된 단계로 쪼개고 양 극단 요청 ID를 사용함으로써, 실패가 존재하더라도 원자적 수행 프로토콜을 쓰지 않고서도 똑같은 정확성 특성을 얻는다.

Timeliness and Integrity

트랜잭션의 편리한 특성은 보통 선형화 가능하다는 것이다. 이는 연산을 스트림 프로세서의 복수 단계를 통해 분산시킬 때는 해당되지 않는다. 이 예에서, 고유성의 정확성 체크는 메시지의 전송자가 결과를 기다리는지에 의존하지 않는다. 더 일반적으로, 저자는 일관성이라는 말이 두 개의 구분지어 고려되어야 할 다른 요구 조건들이 합쳐진 것이라 생각한다:

  • 시기적절성. 사용자가 시스템을 업데이트된 상태로 관찰함을 보장하는 것이다. CAP 정리는 일관성을 선형화 가능성의 관점으로 사용하는데 이것은 시기적절성을 얻는 강한 방법이다.
  • 통합성. 이는 오염이 없는 것이다. 통합성이 깨지면 비일관성은 영구적이 된다.

간단히 말하면 시기적절성 위반은 궁극적 일관성이고 통합성에 대한 위반은 계속되는 비일관성이다. 저자는 대다수의 애플리케이션에서 통합성은 시기적절성보다 훨씬 더 중요하다고 가정한다. 시기적절성에 대한 위반은 짜증나고 혼란스러울 수는 있지만 통합성 위반은 끔찍하다.

Correctness of dataflow systems

ACID 트랜잭션은 대개 시기적절성과 통합성 보장을 모두 제공한다. 반면에 이벤트 기반 데이터플로우 시스템의 흥미로운 특성은 시기적절성과 통합성을 분리한다는 것이다. 정확히 한 번 또는 효과적으로 한 번 시맨틱은 통합성을 보존하는 메커니즘이다. 신뢰성 있는 스트림 처리 시스템은 분산 트랜잭션과 원자적 수행 프로토콜을 요구하지 않고서도 통합성을 보존할 수 있다. 즉, 잠재적으로 비교 가능한 정확성을 훨씬 더 좋은 성능과 기능적 강건함을 갖추고 얻을 수 있다. 다음 방법들이 있다:

  • 쓰기 동작의 내용을 단일 메시지로 표현하고 이를 원자적으로 쓰는 것. 이벤트 근원에 매우 잘 맞는 접근법이다.
  • 결정론적 파생 함수를 사용하는 단일 메시지로부터 다른 모든 상태 업데이트를 파생하는 것. 저장된 프로시져와 비슷하다.
  • 클라이언트로부터 생성된 요청 ID를 모든 수준의 처리로 전달해 양 극단 중복 제거와 멱등을 가능케 하는 것.
  • 메시지를 변경 불가능하게 하고 파생 데이터가 시간에 따라 재처리되게 함으로써 버그로부터 복구되기 쉽게 하는 것.

이런 메커니즘의 조합은 저자에게는 실패에 대처하는 미래 애플리케이션을 만드는 데 있어 매우 유망한 방식이다.

Loosely interpreted constraints

고유성 조건을 보장하는 것은 컨센서스를 요구한다. 이는 대개 모든 이벤트를 단일 노드를 통해 특정한 파티션을 통과하게 하는 것으로 구현된다. 하지만 유념해야 하는 것은 많은 실제 애플리케이션은 더 약한 고유성을 갖고도 이 한계를 우회할 수 있다:

  • 두 사람이 같은 사용자명이나 같은 좌석을 동시에 예약한다면, 한 명에게 사과 메시지를 보내고 그에게 다른 것을 선택하도록 할 수 있다.
  • 소비자들이 창고에 있는 것보다 더 많은 항목을 주문하면, 더 많은 재고를 주문하고, 소비자들에게 지연을 사과하고, 그들에게 할인을 해 줄 수 있다.
  • 비슷하게, 많은 항공사들은 어떤 승객들은 그들의 비행을 놓칠 거라는 예상 하에 예약을 한도 이상으로 받는다. 호텔도 비슷하다.
  • 어떤 사람이 계좌에 있는 것보다 더 많은 돈을 인출하면, 은행은 초과 발행을 부과하고 빚진 만큼을 다시 내도록 요구할 수 있다.

많은 산업에서, 일시적으로 조건을 위반하고 이에 대해 사과함으로써 이를 수정하는 것은 허용 가능하다. 사과의 비용이 허용 가능한지는 산업의 결정이다. 이런 애플리케이션은 통합성을 요구한다.

Coordination-avoiding data systems

이제 두 가지 흥미로운 발견이 있다:

  • 데이터플로우 시스템은 원자적 수행, 선형화 가능성, 동기화된 파티션간 조정 없이도 파생 데이터에 대한 통합성 보장을 할 수 있다.
  • 강한 고유성 조건이 시기적절성과 조정을 요구하더라도, 많은 애플리케이션은 일시적으로 위반될 수 있고 나중에 고쳐질 수 있는 느슨한 조건만으로도 충분하다. 통합성이 그 동안 보장되는 한.

이를 종합하면, 이는 데이터플로우 시스템은 많은 애플리케이션에 대한 데이터 관리 서비스를 조정 없이 제공할 수 있음을 뜻한다. 강한 통합성 보장을 여전히 제공하면서. 이런 조정 회피 데이터 시스템은 많은 장점이 있다. 이 맥락에서, 직렬화 가능한 트랜잭션은 파생 상태를 유지하는 데 있어 여전히 유용하지만, 그들이 잘 동작하는 좁은 범위에서 작동 가능하다. 조정과 조건을 바라보는 다른 방법은, 비일관성으로부터 해야 하는 사과의 수는 줄여 주지만, 시스템의 성능과 가용성을 줄일 수 있으므로, 정전으로 인해 생길 수 있는 사과의 수를 잠재적으로 늘릴 수 있다는 것이다.

Trust, but verify

지금까지 했던 정확성, 통합성, 실패에 대한 대처는 어떤 것들은 잘못될 수 있지만 어떤 것들은 잘못될 수 없다는 것에 가정했다. 이런 가정은 꽤 합리적이고, 대부분 시간 동안 사실이며, 우리가 항상 컴퓨터가 실수를 저지르는 것에 대해 걱정해야 했다면 아무 것도 제대로 하기 힘들었을 것이다. 하지만 데이터가 디스크에서 접근되지 않은 채로 오염될 수도 있고, TCP 체크섬을 회피해서 네트워크에서 데이터 오염이 발생할 수도 있다. 이것이 혹시 우리가 더 많은 주의를 기울여야 한다는 것은 아닐까? 명확히 하자면, 현대적 하드웨어에서 무작위 비트 뒤집힘은 매우 드물다. 다만 고려할 가치는 있다.

Maintaining integrity in the face of software bugs

하드웨어 문제들 외에도, 소프트웨어 버그의 위험성은 항상 존재한다. 이는 저수준 네트워크, 메모리, 파일시스템 체크섬에 잡힐 수 없는 것이다. 주의 깊은 설계, 테스팅, 리뷰에 대한 유의미한 노력을 하더라도, 버그는 여전히 존재할 수 있다. 애플리케이션 코드에 대해서는, 더 많은 버그를 가정해야 한다. 대부분의 애플리케이션은 데이터베이스 코드가 받는 만큼의 리뷰와 테스팅을 받지 않기 때문이다. ACID의 관점에서의 일관성은 데이터베이스는 일관적인 상태에서 시작하고 트랜잭션은 그것을 일관적인 상태에서 다른 일관적인 상태로 전환한다는 발상에 기반한다.

Don’t just blindly trust what they promise

하드웨어와 소프트웨어가 우리가 생각하는 이상향에 항상 들어맞는 건 아니므로, 데이터 오염은 빠르건 늦건 피할 수 없다. 이에 필요한 감사는 금융 애플리케이션에만 적용되는 것이 아니다. 성숙한 시스템은 좀처럼 잘못되지 않는 것들이 잘못될 가능성도 고려하고 그 리스크도 관리한다. 데이터가 여전히 존재함을 보장하고 싶다면 그것을 읽고 체크해야 한다.

A culture of verification

HDFS나 S3 같은 시스템은 디스크가 대부분 시간 동안 올바르게 동작함을 가정해야 한다. 이는 합당한 가정이지만, 그것이 항상 잘 동작한다는 가정과는 다르다. 우리가 신뢰한 기술이 대부분의 시간 동안 잘 동작했기 때문에, 감사 메커니즘은 그렇게 많은 투자를 받지 못했다. 하지만 데이터베이스의 생태계는 변화했다.

Designing for auditability

트랜잭션이 데이터베이스의 여러 오브젝트를 변경시키면 그 트랜잭션이 무엇을 의미했는지를 말하기 힘들다. 반면, 이벤트 기반 시스템은 더 좋은 감사 가능성을 제공한다. 데이터플로우에 대해 명시적이 되는 것은 데이터의 근원을 훨씬 명료하게 함으로써 통합성 체크를 더 가능하게 한다. 결정론적이고 잘 정의된 데이터 플로우는 시스템의 실행에 대한 디버그와 추적을 더 쉽게 해서 왜 이 일이 일어났는지를 판별하기 쉽게 한다.

The end-to-end argument again

우리가 시스템의 각 구성 요소가 오염으로부터 자유롭다는 것을 완전히 믿을 수가 없다면, 즉 모든 하드웨어 부분이 고장으로부터 자유롭고 모든 소프트웨어 부분이 버그로부터 자유롭다는 가정을 할 수 없다면, 우리는 최소한 주기적으로 우리의 데이터의 통합성을 체크해야 한다. 데이터 시스템의 통합성을 체크하는 것은 양 극단으로 되었을 때 가장 좋다. 연속적인 양 극단 통합성 체크를 갖는 것은 시스템의 정확성에 대해 더 나은 확신을 주고 작업의 속도를 높여 준다.

Tools for auditable data systems

현재는 데이터 시스템 중 많은 수가 감사 가능성을 최고 수준 고려 사항으로 놓지는 않는다. 암호화 도구를 사용해 시스템의 통합성을 증명하는 것은 흥미로울 것이다. 이는 시스템이 넓은 범위의 하드웨어와 소프트웨어 문제, 그리고 심지어는 잠재적으로 공격적인 동작으로부터 강건하다는 것을 증명하는 것이다. 데이터 시스템의 관점에서, 암호화폐는 여러 흥미로운 발상을 갖고 있다. 근본적으로 이는 분산형 데이터베이스에 데이터 모델과 트랜잭션 메커니즘이 갖춰진 것으로, 서로 다른 복제가 상호 불신뢰하는 조직들에 의해 호스팅되는 것이다. 암호학적 감사와 통합성 체킹은 종종 메르클 트리에 의존하곤 한다. 이는 기록이 같은 데이터셋에서 등장함을 효과적으로 증명하는 데 쓰이는 해시의 트리이다. 암호화폐의 선전 외에는, 인증 투명성은 메르켈 트리에 의존해 TLS/SSL 인증의 정당성을 체크하는 보안 기술이다.

Doing the Right Thing

이 장에서는 중요하고 근본적인 논의 부분을 해야 한다. 모든 시스템은 목적을 가지고 만들어진다. 데이터는 추상적 존재라 할 수 있지만 많은 데이터셋은 사람들에 대한 것이다. 소프트웨어 개발은 점차 중요한 도덕적 선택을 만드는 것을 포함하고 있다. 기술은 그 자체로는 좋거나 나쁘지 않다. 그것이 어떻게 쓰이고 어떻게 사람들에게 영향을 주는지가 중요하다.

Predictive Analytics

예측 분석은 빅 데이터 선전의 큰 부분이다. 자연스럽게, 결제 네트워크는 거짓 트랜잭션을 막기를 원하고, 은행은 악성 대출을 피하기 원하고, 항공사는 납치를 피하기 원하고, 회사들은 비효율적이고 믿을 수 없는 사람들을 고용하는 것을 피하기 원한다. 하지만, 알고리즘에 기반한 의사 결정이 더 퍼짐에 따라, 어떤 알고리즘에 의해 리스크 있다고 라벨링된 누군가는 그것이 정확하든 정확하지 않든 이러한 많은 부정 결정에 의해 고통받을 수 있다.

Bias and discrimination

알고리즘에 의해 내려진 결정은 사람에 의해 만들어진 결정보다 꼭 더 낫거나 더 나쁜 것은 아니다. 예측 분석 시스템을 개발할 때에는, 우리는 소프트웨어를 언제 예/아니오를 말할지에 대한 규칙을 특정하도록 사용함으로써 단지 사람의 결정들을 따라하지는 않는다. 많은 나라에서, 차별 금지법은 국가, 나이, 성별, 장애, 신념과 같은 특성에 기반해 사람들을 다르게 대하는 것을 막아 왔다. 예측 분석은 그저 과거로부터 내삽해서는 안 된다.

Responsibility and accountability

자동화된 결정은 책임과 의무에 대한 질문을 남긴다. 신용 등급 기관들은 데이터를 모아 사람에 대한 결정을 하는 오래 된 예이다. 신용 점수는 “당신은 과거에 어떤 행동을 했나?” 를 요약하고, 예측 분석은 “누가 당신과 비슷하고, 당신같은 사람들이 과거에 어떤 행동을 했나?” 를 요약한다. 많은 데이터는 내재적으로 통계적이다. 즉, 전체 확률분포가 올바르더라도 개개인에 대한 경우는 틀릴 수 있다. 데이터 지상주의에 대한 맹목적 믿음을 통해 결정을 하는 것은 망상적일 뿐만 아니라 위험하다. 또한, 데이터가 사람을 해치는 데 쓰이는 것을 어떻게 막을지도 밝혀 내고 그 대신 그 긍정적 잠재성을 일깨워야 한다.

Feedback loops

추천 시스템처럼 예측 분석이 사람들에 대해 덜 즉각적인 영향을 갖더라도, 우리가 다뤄야 할 어려운 문제들이 있다. 예측 분석이 사람의 삶에 영향을 준다면, 자가 강화적 피드백 순환으로 인해 치명적 문제가 발생할 수 있다. 이런 피드백 순환이 언제 일어날지를 항상 정확히 예측할 수는 없다.

Privacy and Tracking

예측 분석의 문제 이외에도, 즉 데이터를 사용해 사람에 대한 자동화된 결정을 하는 것 이외에도, 데이터 수집 자체에 대한 도덕적 문제가 있다. 시스템이 사용자가 명시적으로 입력한 데이터만 저장한다면, 그리고 사용자가 시스템이 그것을 저장하고 특정한 방식으로 처리하길 원했다면, 시스템은 사용자에 대한 서비스를 수행하는 것이다. 하지만 행동적 데이터를 수집하는 것은 많은 온라인 서비스에서 사용자 대면 특성에 대해 점점 더 중요해지고 있다. 하지만, 회사의 비즈니스 모델에 의존적이지만, 추적은 종종 거기서 멈추지 않는다. 이제 회사와 데이터가 수집되는 사람과의 관계는 꽤 다르게 보일 것이다.

Surveillance

사고 실험으로서, 데이터를 감시라는 단어로 바꿔본 뒤, 상투적 문구들이 여전히 괜찮아 보이는지를 보라. 이렇게 소프트웨어가 세계를 먹는 시도를 함으로써, 우리는 세계가 그동안 봤던 것 중 가장 최고의 대량 감시 인프라를 만든 것이다. 가장 전체주의적이고 억압적인 권력이라도 그저 모든 방에 마이크로폰을 넣고 모든 이에게 항상 그들의 장소와 이동을 추적할 수 있는 기기를 항상 갖고 있도록 할 것이다. 모든 데이터 수집이 이런 감시인 것은 아니다. 하지만 그렇게 보는 것은 데이터 수집가와 우리의 관계를 이해하는 데 도움을 준다. 우리는 이미 자동차 보험 프리미엄이 차의 추적 도구와 연결되어 있는 것을 보고, 건강 보험 범위가 건강 추적 기기를 장착한 사람에 의존적임을 본다.

Consent and freedom of choice

우리는 사용자가 자발적으로 그들의 활동을 추적하는 서비스를 사용하고, 그들이 약관과 프라이버시 정책에 동의했으므로, 그들이 데이터 수집에 합의했음을 가정할 수 있을는지도 모른다. 사용자는 그들이 우리의 데이터베이스에 어떤 데이터를 주는지, 그것이 어떻게 유지되고 처리되는지에 대해 아는 바가 거의 없다. 그리고 대부분의 개인 정보 정책은 그것을 밝히는 것보다는 숨기려 한다. 게다가, 데이터는 일방적인 과정을 통해 사용자로부터 추출된다. 이것은 완전 호혜적인 관계가 아니며, 공정한 가치 교환이 아니다. 감시에 동의하지 않는 사용자에 대해서는, 실질적인 유일한 대안은 그 서비스를 이용하지 않는 것이다. 사용자 추적으로 인해 서비스를 사용하지 않는 것은 프라이버시 정책을 이해할 시간과 지식이 되는 소수의 사람에게만 가능한 정책이며 이들도 그 서비스를 누림으로서 얻을 수 있었던 사회적 참여나 전문적 기회를 포기해야 한다.

Privacy and use of data

프라이버시를 가지는 것은 모든 것을 비밀에 부치는 것이 아니다. 그것은 어떤 것들을 누구에게 드러낼지를, 어떤 것을 공적으로 할지, 어떤 것을 비밀로 할지 선택할 자유가 있는 것이다. 데이터가 감시 인프라를 통해 사람들로부터 추출되면, 프라이버시 권리는 그저 풍화되는 것뿐만 아니라 데이터 수집자에게 넘어간다. 회사들은 이런 감시 비밀의 결과를 비밀로 하길 원한다. 이를 드러내는 것은 으스스하고, 그들의 사업 모델을 해치기 때문이다. 특정한 사용자들이 특정 광고에 의해 대상되는 사람 그룹으로부터 개인적으로 재식별될 수 없더라도, 그들은 어떠한 은밀한 정보를 드러내는 것에 대한 권한을 뺏긴 것이다. 많은 회사들은 두려움의 대상이 되지 않는 것을 목표로 한다.그래서 그들의 데이터 수집이 얼마나 공격적인지에 대한 질문을 답하는 것을 피하고, 사용자 인지를 관리하려 한다. 온라인 서비스의 사용자가 다른 사용자가 그들의 어떤 데이터를 볼 수 있는지를 허용하게 하는 프라이버시 세팅은 제어권의 일부를 사용자에게 넘기는 시작점이 될 것이다. 이렇게 프라이버시 권리의 개인으로부터 사업체로의 대형 이동은 역사적으로 유례가 없었다.

Data as assets and power

행동적 데이터는 서비스와 상호작용하는 사용자의 산물이므로, 이는 데이터 배기라고도 불린다. 이는 데이터가 쓸모없는 쓰레기라는 것처럼 들린다. 더 올바른 것은 이를 다르게 보는 것이다. 개인 데이터가 가치 있는 자산이라는 가정은 데이터 브로커의 존재로서 증명이 된다. 그늘진 사업들이 마케팅 목적으로 비밀스럽게 사용자 데이터를 구매하고, 집합하고, 분석하고, 추론하고, 재판매하니까. 데이터는 가치가 있으므로, 많은 사람들이 이를 원한다. 이런 관찰들은 비평가들에게 데이터는 자산일 뿐만 아니라, 독성 자산 내지는 유독 물질임을 말하도록 하였다. 데이터를 모을 때, 우리는 오늘날의 정채적 환경뿐만 아니라 모든 가능한 미래 정부에 대해서도 고려해야 한다. 아는 것은 힘이다.

Remembering the Industrial Revolution

데이터는 정보화 시대를 정의하는 특성이다. 산업 혁명은 여러 문제를 불러일으키기도 했다. 이후에 안전장치가 설립되는 데는 먼 시간이 걸렸다. 환경 보호 규제, 작업장의 안전 규약, 아동 노동 금지, 식품에 대한 건강 조사. 산업 혁명이 관리되어야 할 어두운 면을 가졌듯, 우리의 정보화 시대로의 전환은 우리가 마주하고 풀어야 할 큰 문제도 갖고 있다.

Legislation and self-regulation

데이터 보호법은 개개인의 권리를 보존하는 데는 도움이 될지 모른다. 하지만, 이 규제가 오늘날의 인터넷 맥락에서 효과적일 지는 의문이다. 사람들에 대한 많은 데이터를 모으는 회사들은 규제들을 부담으로 여기고 혁신에 대한 장애물로 여긴다. 우리는 사용자들을 최적화되어야 할 지표로 보는 것을 멈추고, 존중, 위엄, 권리가 있는 사람이라는 것을 기억해야 한다. 각 개인들이 그들의 프라이버시를 유지하도록 허용해야 한다. 즉, 그들이 그들의 데이터를 제어하도록 해야 하며, 감시를 통해 그들로부터 그 제어를 훔치면 안 된다. 어떻게 이를 얻을지는 열린 문제이다.

Summary

이 장에서는 데이터 시스템을 설계하는 새 접근법을 알아보았고, 저자가 미래에 대한 개인적 의견과 추측을 포함하기도 했다. 먼저 모든 가능한 사용처들을 효과적으로 수행할 수 있는 단일 도구는 없다는 관측에서부터 시작하였다. 그러므로 애플리케이션은 목적을 이루기 위해서는 여러 다른 소프트웨어 부분을 조합할 필요가 있다. 이 데이터 통합 문제를 배치 처리와 이벤트 스트림이 데이터 변경이 다른 시스템간 흐르도록 해서 어떻게 풀지 알아보았다.

이 접근법에서, 특정 시스템은 기록의 시스템으로 지정되고, 다른 데이터는 변환을 통해 그들로부터 파생된다. 이 방식에서 우리는 인덱스, 물질화된 뷰, 기계 학습 모델, 통계적 요약 등을 유지할 수 있다. 이 파생과 변환을 비동기적이고 느슨하게 연결되게 만듦으로써, 한 영역의 문제는 시스템의 관계 없는 부분으로 퍼지는 것을 막아서, 시스템이 전체로서 강건함과 실패에 대한 대처를 증가시킨다.

데이터 흐름을 한 데이터셋에서 다른 데이터셋으로의 전환으로 표현하는 것은 애플리케이션 발달에도 도움을 준다. 한 처리 과정을 변경시키면, 예를 들어 인덱스의 구조나 캐시를 변경하면, 전체 입력 ㅔ이터셋에 대해 새 전환 코드를 재실행해서 출력을 재파생하기만 하면 된다. 비슷하게, 무언가가 잘못된다면, 코드를 고치고 데이터를 재처리해 복구하면 된다.

이 과정들은 데이터베이스가 이미 내부에서 하는 것과 매우 비슷하다. 그래서 우리는 데이터 흐름 애플리케이션의 발상을 데이터베이스의 부분들을 분산한 뒤 이렇게 느슨하게 연결된 컴포넌트들을 조합해 애플리케이션을 만드는 것으로 재구성할 수 있다.

파생 상태는 기반하는 데이터의 변경을 관측하는 것으로 업데이트될 수 있다. 게다가, 파생 상태는 그 자체로도 하류 소비자들에 의해 더 관측될 수 있다. 우리는 이 데이터플로우를 완전히 데이터를 표시하는 최종 사용자 기기에 전달할 수 있으며, 그럼으로써 사용자 인터페이스가 동적으로 데이터 변경을 반영해 업데이트할 수 있고 오프라인에서 계속 작업하도록 할 수 있다.

다음으로, 이 모든 처리가 어떻게 실패의 존재 하에서 정확하도록 보장하는지를 알아보았다. 강한 통합성 보장이 비동기 이벤트 처리와 함께 확장성 있게 구현될 수 있음을 보았다. 이는 양 극단 요청 식별자를 사용해 연산들을 멱등으로 만들고 조건들을 비동기적으로 체크한다. 클라이언트들은 체크가 수행될 때까지 대기하거나, 대기하지 않고 진행하지만 조건 위반에 대해 사과하는 리스크를 질 수 있다. 이 접근법은 분산 트랜잭션을 사용하는 전통적 접근법보다 훨씬 더 확장성 있고 강건하며, 많은 실제로 동작하는 사업 과정에 잘 맞는다.

애플리케이션을 데이터 흐름에 따라 구조화하고 조건들을 비동기적으로 체크함에 따라, 우리는 대부분의 조정을 피할 수 있으며 통합성을 유지하지만 성능이 잘 나오는 시스템을 만들 수 있다. 심지어는 지정학적으로 분산된 시나리오와 실패가 존재하는 시나리오에서라도. 감사를 이용하여 데이터의 통합성을 검증하고 오염을 감지하는 것에 대해서도 아랑보았다.

마지막으로, 한 걸음 물러나서 데이터 집중적 애플리케이션을 만드는 데 있어 도덕적 관점을 알아보았다. 우리는 데이터가 좋은 일을 하는 데 쓰일 수 있더라도, 엄청난 해를 끼칠 수 있음을 보았다. 사람들의 삶에 크게 영향을 미치면서 대항하기는 어려운 결정을 내리는 것, 차별과 착취를 낳고, 감시를 평범하게 만들고, 은밀한 정보를 노출하는 것. 우리는 또한 데이터 위반의 리스크를 가지며, 데이터의 올바른 의도상의 사용도 의도치 않은 결과를 불러올 수 있음을 보았다.

소프트웨어와 데이터가 세계에 이렇게 큰 임팩트를 가졌으므로, 우리 엔지니어드릉ㄴ 우리가 살고 싶어하는 세계를 만들어 나가는 작업에 대한 책임감을 질 필요가 있다. 사람들을 인간성과 존중으로 대하는 세계. 우리는 그 목적을 통해 일할 수 있음을 믿는다.