12장 데이터 시스템의 미래
- 이 책의 목적은 애플리케이션과 시스템을 신뢰할 수 있고 확장 가능하며 유지보수하기 쉽게 만드는 방법을 탐구하는 것이다.
- 이번 장에서는 이 아이디어들을 기반으로 미래를 고찰한다.
데이터 통합
- 특정 문제에 대해서 트레이드 오프를 고려할 때 적절한 소프트웨어를 선택해야 한다.
- 하지만 도구를 사용하는 환경을 고려하더라도 복잡한 애플리케이션은 데이터를 여러 가지 다른 방법으로 사용한다.
- 여러 데이터를 모든 상황에 적합하게 다루는 소프트웨어가 있을 가능성은 낮다.
- 예를 들어 OLTP 데이터베이스와 키워드를 대상으로 질의하는 전문 검색 색인을 통합하는 요구는 일반적이다.
- 전문 색인 기능이 있는 RDB도 있지만 더 복잡한 검색을 지원하려면 전문적인 탐색 도구가 필요
- 반대로 검색 색인은 지속성 있는 레코드 시스템으로는 적합하지 않다.
파생 데이터에 특화된 도구의 결합
- 데이터를 다른 방식으로 표현하는 수가 늘어날수록 데이터 시스템을 통합하기는 더욱 어렵다.
- ex) 원본에서 파생된 캐시 객체의 비정규화 버전을 유지하기
- 데이터 사본을 여러 저장소에 유지해야 할 때 입출력을 분명히 해야 한다.
- 레코드 데이터베이스에 데이터를 입력하고 CDC로 검색 색인에 반영하는 경우 색인은 전적으로 레코드 시스템에서 파생되므로 일관성이 보장된다.
- 애플리케이션이 검색 색인에 직접 데이터를 기록한다면 쓰기 충돌 등으로 일관성 문제가 발생한다.
- 모든 쓰기 순서를 결정하는 단일 시스템으로 입력을 밀어 넣을 수 있다면 쓰기를 같은 순서로 처리해 데이터 파생이 훨씬 쉬워진다.
- 이벤트 로그 기반의 파생 데이터 시스템은 결정적이고 멱등성을 지닌다.
- 파생 데이터 대 분산 트랜잭션
- 분산 트랜잭션
- 상호 배타적인 잠금을 사용해 쓰기 순서를 결정한다.
- 원자적 커밋을 사용해 변경 효과가 한 번 나타나도록 보장
- 선형성을 보장해 자신이 쓴 내용 읽기 같은 기능을 보장
- 파생 데이터
- CDC와 이벤트 소싱은 순서를 결정하는 데 로그를 사용한다.
- 결정적 재시도와 멱등성을 기반으로 동작한다.
- 대개 비동기로 갱신되기에 동시간 갱신 보장을 하지 않는다.
- 전체 순서화의 제약
- 보통 전체 로그 순서를 결정하려면 단일 리더 노드를 통해야하지만 데이터 처리량이 늘어나면 파티셔닝이 필수적이고 그럼 두 파티션 간 순서는 애매해진다.
- 마이크로서비스의 경우 두 이벤트가 서로 다른 서비스에서 발생했다면 이들 사이엔 정해진 순서가 없다.
- 어떤 애플리케이션은 클라이언트 측 상태를 유지하고 사용자 입력마다 서버 응답을 기다리지 않고 바로 갱신하는데 이 경우 이벤트 순서가 클라이언트와 서버에서 서로 다를 가능성이 높다.
- 결론적으로 단일 노드 처리량을 넘어서는 규모와 지리적으로 분산된 설정에서 동작하는 전체 순서 브로드캐스트(합의) 설계는 아직 해결되지 않은 과제다.
- 인과성 획득을 위한 이벤트 순서화
- 이벤트 간 인과성이 없는 경우 순서가 정해지지 않아도 큰 문제는 아니다.
- 논리적 타임스탬프를 통해 코디네이션 없이 전체 순서화를 지원할 수는 있지만 여전히 수신자가 잘못된 순서로 전달된 이벤트를 처리해야 한다.
- 충돌 해소 알고리즘은 예상치 못한 순서로 전송된 이벤트를 처리하는 데 도움을 주지만 사용자에게 알림을 보내는 등의 외부 부수 효과가 있다면 도움이 되지 않는다.
일괄 처리와 스트림 처리
- 파생 상태 유지
- 일괄 처리는 결정적이고 출력이 입력에만 의존하여 부수 효과가 없는 강력한 함수형 특징을 가진다.
- 결정적 함수는 내결함성에 도움이 되고 데이트 플로 추론을 단순화한다.
- 스트림 처리도 유사하지만 연산자를 확장해 상태를 관리할 수 있고 내결함성을 지니게 한다.
- 이론상 파생 데이터 시스템은 동기식으로 갱신할 수 있지만 비동기 방식을 사용하면 이벤트 로그 기반 시스템을 훨씬 견고하게 만든다.
- 애플리케이션 발전을 위한 데이터 재처리
- 일괄 처리와 스트림 모두 파생 데이터를 유지하는데 유용하다.
- 스트림 처리를 통해 빠르게 파생 뷰에 반영 가능
- 일괄 처리 시스템을 사용하면 상당한 양의 과거 데이터를 재처리해 새 파생뷰 구축이 가능
- 파생뷰를 사용하면 스키마의 점진적 발전이 가능하다.
- 스키마가 변경되어도 이전 스키마와 새 스키마를 함께 유지에 두 개의 독립적인 파생뷰를 만들 수 있다.
- 점진적으로 새 뷰를 접근하는 사용자 비율을 늘려 결국엔 기존 뷰를 내릴 수 있게 된다.
- 람다 아키텍처 - 일괄 처리와 스트림 처리를 결합한 설계 방식
- 입력 데이터를 불변 이벤트로 기록하여 일괄 처리와 스트림 처리가 모두 같은 원본을 사용
- 스트림 처리자는 이벤트를 소비해 근사 갱신을 뷰에 빠르게 반영하고 이후 일괄 처리자가 같은 이벤트를 소비해 정확한 버전의 파생뷰에 반영
- 하지만 실질적인 문제가 몇 가지 있다.
- 일괄 처리와 스트림 처리에서 같은 로직을 유지해야 하는 번거로움이 존재
- 스트림과 일괄 처리 파이프라인은 분리된 출력을 생성하기에 사용자 요청에 대응하려면 두 출력을 병합해야 한다.
- 대규모 데이터 재처리가 자주 발생한다고 하면 비용이 만만치 않다.
- 최근에는 람다 아키텍처의 단점을 극복하기 위해 두 방식을 통합하려는 시도가 있는데 아래 기능들이 필요하다.
- 과거 이벤트를 재생하는 능력
- 정확히 한 번 실행
- 처리 시간 기준이 아닌 이벤트 시간 기준으로 윈도우를 처리하는 도구
- 과거 이벤트를 처리할 땐 처리 시간은 의미가 없기 때문
데이터베이스 언번들링
- 추상화 수준에서 보면 데이터베이스, 하둡, 운영체제는 모두 같은 기능을 수행한다.
- 유닉스와 관계형 데이터베이스는 정보 관리 문제를 각기 매우 다른 철학으로 접근한다.
- 유닉스 - 논리직이지만 저수준인 하드웨어 추상화를 프로그래머에게 제공
- 관계형 데이터베이스 - 자료 구조, 동시성, 장애 복구 등의 복잡성을 감추고 고수준 추상화를 애플리케이션 프로그래머에게 제공
데이터 저장소 기술 구성하기
- 지금까지 데이터베이스가 제공하는 다양한 기능을 설명하고 동작 방식을 설명했다.
- 보조 색인 - 필드 값을 기반으로 효율적인 레코드 검색 지원
- 구체화 뷰 - 질의 결과를 미리 계산한 캐시의 일종
- 복제 로그 - 데이터 복사본을 다른 노드에 최신 상태로 유지하는 기능
- 전문 검색 색인 - 텍스트에서 키워드 검색을 가능하게 하는 기능
- 데이터베이스에 내장된 기능과 일괄처리와 스트림 처리로 구축하는 파생 데이터 시스템 사이에는 유사점이 있다.
- 관계형 DB에서 색인 생성하는 과정도 새 팔로워 복제본을 구축하는 과정과 비슷하다.
- 테이블의 스냅숏을 사용해 스캔하고 색인할 필드 값을 모두 골라 정렬 후 색인에 기록한다.
- 그 다음 스냅숏 이후에 실행된 쓰기의 백로그를 처리해야 한다.
- 색인 생성 후에는 쓰기 트랜잭션마다 꾸준히 색인에 반영해야 한다.
- 전체 조직의 데이터플로가 거대한 데이터베이스로 보일 수도 있는 것이다.
- 일괄 처리, 스트림 처리를 통해 데이터를 다른 저장소에 다른 형태로 바꿔 저장하는 작업은 색인이나 구체화 뷰를 최신으로 유지하는 데이터베이스의 하위 시스템처럼 작동한다.
- 서로 다른 저장소와 처리 도구를 사용하지만 하나의 응집된 시스템으로 구성할 수 있는 길은 두 가지 존재한다.
- 연합 데이터베이스: 읽기를 통합
- 엄청 많은 하단 저장소 엔진과 처리 메서드를 통합해 질의하는 인터페이스를 제공
- ex) PostgreSQL이 제공하는 foreign data wrapper
- 특정 데이터 모델이 필요한 경우엔 하단 저장소에 직접 접근
- 다른 장소의 데이터를 결합하기 원한다면 연합 인터페이스를 통해 처리
- 여러 시스템에 걸친 쓰기를 동기화하기에는 적합하지 않다.
- 언번들링 데이터베이스: 쓰기를 통합
- 여러 시스템으로 구성됐을 때도 모든 데이터가 올바른 장소에 반영되도록 보장해야 한다.
- 저장소 시스템들을 신뢰성 있게 결합하기 쉽게 만드는 것은 언번들링 방식과 유사하다.
- 언번들링 - 기존에 하나의 통합된 시스템으로 제공되던 데이터베이스의 다양한 기능(색인, 캐싱, 질의, 트랜잭션 등)을 분리하여 독립적으로 관리하거나 최적화하는 접근 방식
- 언번들링이 동작을 위해 쓰기를 동기화하는 경우 쓰기 기반 비동기 이벤트 로그를 사용하는 것이 좋다.
- 비동기 이벤트 스트림을 사용하면 전체 시스템 중 개별 요소에 장애가 발생해도 잘 견딜 수 있다.
- 인적 수준 관점에서도 데이터 시스템을 언번들링하면 각 팀에서 각자 구성 요소를 개발하고 독립적인 유지보수가 가능하다.
- 언번들링 대 통합 시스템
- 언번들링이 사용된다고 해도 현재 형태의 DB를 대체하지는 못할 것이다.
- 스트림 처리자의 상태 유지를 위해 필요, 일괄 처리와 스트림 처리자의 출력에 대한 질의에도 필요
- 여러 인프라에서 수행하는 복잡성도 문제가 된다.
- 각 소프트웨어마다 학습 곡선 그리고 운영상 특성
- 언번들링의 목표는 몇 개의 데이터베이스를 결합해 단일 소프트웨어로 가능한 것보다 더 넓은 범위의 작업부하에 대해 좋은 성능을 달성하기 위함이다.
- 개별 데이터베이스와 특정 작업부하에 대한 성능을 경쟁하는 것이 아님
- 언번들링의 장점은 요구사항을 모두 만족하는 단일 소프트웨어가 없는 상황에서만 드러난다.
데이터플로 주변 애플리케이션 설계
- 파생 함수로서의 애플리케이션 코드
- 보조 색인용 파생 함수는 아주 일반적이라 많은 데이터베이스의 내장되어 있다. (ex.
CREATE INDEX
)
- 파생 데이터셋을 생성하는 함수가 표준 함수가 아닌 사용자 정의 코드를 써야한다면 많이 어렵다.
- 관계형 DB는 DB 내에서 애플리케이션 코드를 실행할 수 있게 해주는 트리거, 스토어드 프로시저, 사용자 정의 함수 등을 지원하긴 한다.
- 애플리케이션 코드와 상태의 분리
- 이론상 DB가 애플리케이션 코드를 배포하는 환경이 될 순 있지만 상당히 적합하지 않다.
- DB 등은 지속성 있는 데이터 저장을 전문으로, 다른 일부는 애플리케이션 코드 실행을 전문으로 하는 게 합리적이다.
- 대부분의 웹 애플리케이션이 상태 비저장 서비스로 배포되기 때문이다.
- 대부분의 프로그래밍 언어는 변경 가능한 변수의 변경을 구독할 수 없고 단지 주기적으로 읽을 수밖에 없다.
- 데이터베이스 내용이 변경됐는지 확인하고 싶다면 주기적으로 질의를 반복할 수밖에 없다.
- 변경 데이터 구독은 이제 막 등장하기 시작한 기능
- 데이트플로: 상태 변경과 애플리케이션 코드 간 상호작용
- 데이터플로 측면에서 데이터베이스는 단순히 애플리케이션에 의해 조작되는 수동적인 시스템이 아니다.
- 데이터베이스 상태와 상태 변경, 상태 처리 코드 간의 상호작용과 협동에 관해 생각해볼 수 있다.
- 데이터베이스의 데이터 변경으로 트리거가 발생해 색인 테이블에 변경을 반영할 때 비슷한 일이 발생한다.
- 데이터베이스를 언번들링한다는 의미는 이 아이디어를 채택해 DB 외부에 파생 데이터 생성 시 적용한다는 뜻
- 최신 스트림 처리자는 애플리케이션 코드를 스트림 연산자로 실행할 수 있어 DB 내에서 지원하지 않는 임의 처리가 가능하다.
- 스트림 처리자와 서비스
- 스트림 연산자로 데이터플로 시스템을 구성하는 것은 마이크로서비스와 상당히 유사하다.
- 최근 유행하는 애플리케이션 개발 스타일은 각 기능을 REST API 기반 동기 요청으로 통신하는 서비스 집합으로 나누는 것이다. (마이크로서비스)
- 느슨한 연결을 통한 조직적 확장성을 장점으로 가진다.
파생 상태 관찰하기
- 데이터플로 시스템은 파생 데이터 셋을 생성해 최신 상태로 유지하는 과정에 사용할 수 있다.
- 일괄 처리와 스트림 처리의 여러 단계를 거쳐 갱신된다.
- 이 과정을 쓰기 경로(write path)라 부른다.
- 사용자 요청 처리 시 먼저 파생 데이터셋을 읽고 결과를 어느 정도 가공한 후 응답을 만든다.
- 이 과정을 읽기 경로(read path)라 부른다.
- 구체화 뷰와 캐싱
- 전문 검색 색인에선 쓰기 경로는 색인을 갱신하고 읽기 경로는 색인을 사용해 키워드를 찾는다.
- 색인이 존재하지 않는다면 쓰기 경로 작업량은 줄지만 읽기 경로 작업이 상당히 늘어난다.
- 고정된 가장 공통적인 질의 집합이 결과를 미리 계산해 두는 방법을 캐시라 부른다.
- 캐시와 색인, 구체화 뷰는 쓰기 경로와 읽기 경로 간 수행 작업의 경계를 옮기는 역할을 한다고 볼 수 있다.
- 오프라인 대응 가능한 상태 저장 클라이언트
- 오프라인 우선(offline-first) 애플리케이션이란
- 인터넷 연결 없이 장치의 로컬 데이터베이스를 이용해 가능한 많은 일을 하고
- 네트워크 연결 가능 시 백그라운드에서 원격 서버와 동기화를 수행한다.
- 장치 상 상태를 서버 상 상태의 캐시, 구체화 뷰라 생각할 수 있다.
- 상태 변경을 클라이언트에게 푸시하기
- 일반적으론 웹 브라우저가 로드된 후 서버 상태가 변경되도 리로드할 때까진 변경 사항을 알 수 없다.
- 웹소켓, SSE 등의 프로토콜을 통해 서버에서 주도적으로 클라이언트 측 상태를 갱신할 수 있다.
- 서버가 클라이언트에 적극적으로 상태를 푸시하면 쓰기 경로가 최종 사용자까지 확장된다.
- 장치가 잠시 오프라인이더라도 로그 기반 메시지 브로커를 통해 재연결 후 받지 못한 메시지를 모두 받을 수 있다.
- 종단 간 이벤트 스트림
- 리액트(React) 같은 상태 저장 클라이언트는 이벤트 스트림이나 서버 응답을 구독하여 클라이언트 측 상태를 관리한다.
- 상태 변경은 종단 간 쓰기 경로를 따라 흘러 파생 데이터 시스템과 여러 장치로 이어진다.
- 문제는 많은 데이터 스토어들이 요청/응답 아키텍처만 지원하고 변경 구독 기능을 지원하지 않는다.
- 쓰기 경로를 최종 사용자까지 확장하려면 근본적으로 발행/구독 데이터플로 방식이 자리잡아야 한다.
- 읽기 요청을 이벤트 스트림으로 표현하고 읽기 이벤트 또한 스트림 처리자로 보내는 방법도 가능하다.
- 읽기 이벤트는 해당 데이터를 가지고 있는 데이터베이스 파티션으로 보내야 한다.
- 읽기 이벤트 로그를 기록하면 잠재적으로 인과적 의존성과 시스템 전체의 데이터 출처를 추적할 수 있다는 이점이 있다.
- 하지만 추가적인 저장소가 필요하고 I/O 비용이 더 발생한다.
- 스트림 처리자가 제공하는 메시지 라우팅, 파티셔닝, 조인용 인프라를 통해 여러 파티션을 통한 복잡한 질의를 분산 실행할 수 있는 가능성을 열어준다.
- 스톰의 분산 RPC이 이러한 분산 질의를 지원한다.
정확성을 목표로
- 애플리케이션 정확성을 구축하기 위해 트랜잭션을 사용했다.
- 완화된 격리 수준을 사용한다면 성능과 확장성을 보장받지만 미묘한 버그가 발생할 수 있다.
- 강력한 정합성을 원한다면 직렬성과 원자적 커밋을 사용할 수 있지만 비용이 따른다.
- 분산 환경에선 이 또한 확장성과 내결함성을 제한한다.
데이터베이스에 관한 종단 간 논증
- 직렬성 트랜잭션을 사용하더라도 데이터 유실과 손상은 발생할 수 있다.
- 트랜잭션 메커니즘에만 의존하면 중복 요청을 막을 수 없다.
- 연산자의 정확히 한 번 실행
- 메시지 처리가 두 번 실행되는 것도 데이터 손상의 한 형태다.
- 가장 효과적인 방법은 연산을 멱등으로 만드는 것이다.
- 다만 본질적으로 멱등이 아닌 연산을 멱등으로 만드는 데는 어려움이 따른다.
- 중복 억제
- 스트림 처리 외에도 동일한 중복 제거 패턴이 발생한다. (ex. TCP 패킷의 순서 보장)
- 하지만 중복 억제는 TCP 문맥에서만 작동하기에 완전하지 않다.
- 연결이 데이터베이스 트랜잭션을 실행, 커밋시킨 뒤 응답을 돌려 보내는 과정에 네트워크가 끊긴다면 클라이언트는성공/실패 여부를 알 수가 없다.
- 클라이언트는 재시도할 수 있지만 TCP의 중복 억제 범위를 벗어나 멱등하지 않은 연산이라면 데이터 손상이 발생한다.
- 2PC 커밋 프로토콜로 코디네이터가 트랜잭션 이상을 파악하더라도 충분하지 않은 경우가 있다.
- 2PC는 분산 시스템 내의 참가자 간 원자성을 보장하지만, 클라이언트가 트랜잭션 성공 여부를 알지 못하고 재시도하는 상황까지 관리하지는 않기 때문
- 위와 같은 문제를 해결하기 위해 연산 식별자를 사용할 수 있다.
- 고유한 식별자를 요청에 부여하고 특정 식별자에 대해 딱 한 번만 연산을 실행하도록 제어할 수 있다.
- 이미 존재하는 ID를 삽입하려 한다면 트랜잭션을 어보트
- 특정 ID에 대해 딱 한 번만 연산을 실행하도록 보장
제약 조건 강제하기
- 분산 설정에서 유일성 제약 조건을 강제하려면 합의가 필요하다.
- 합의를 달성하는 가장 일반적인 방법은 단일 리더에게 모든 결정을 하게끔 책임을 부여하는 것이다.
- 리더에 장애가 발생하지 않다면 잘 작동한다.
- 다만 리더 장애에 대응할 필요가 있다면 다시 합의 문제로 돌아가게 된다.
- 유일성이 필요한 값을 기준으로 파티셔닝하면 확장이 가능하다.
- 같은 유일값의 요청들이 같은 파티션으로 모인다면 유일성을 보장할 수 있다.
- 그러나 비동기 다중 마스터 복제는 유일성을 보장하지 못한다.
- 다른 마스터에서 동시에 충돌되는 쓰기를 받아들여 유일성이 깨질 수 있다.
- 로그 기반 메시징에서 전체 순서 브로드캐스트를 통해 유일값을 보장할 수도 있다.
- 스트림 처리자는 단일 스레드상에서 한 로그 파티션의 메시지를 순차 소비한다.
- 유일성이 필요한 값 기준으로 파티셔닝한다면 스트림 처리자는 충돌을 결정적으로 판단할 수 있다.
- 이 접근법은 유일성 제약 뿐 아니라 다른 많은 제약에서도 사용할 수 있다.
- 다중 파티션 환경에서 원자적 연산이 필요한 경우 파티셔닝된 로그를 사용해도 정확성을 달성할 수 있다.
- 계좌 A에서 계좌 B로 송금하는 예제를 가정해보자.
- 잠재적으로 세 파티션이 존재할 수 있다. (요청 ID를 포함하는 파티션, 계좌 A가 있는 파티션, 계좌 B가 있는 파티션)
- 계좌 A에서 계좌 B로 송금하는 요청은 클라이언트로부터 요청 ID를 발급 받아 특정 로그 파티션에 추가됨
- 스트림 처리자는 요청 로그를 읽고 계좌 A의 출금 지시 메시지와 계좌 B의 입금 지시 메시지를 두 출력 스트림으로 방출된다. (요청 ID를 포함하며)
- 후속 처리자는 각 지시 메시지를 소비해 요청 ID로 중복을 제거한 다음 변경 내용을 각 파티션에서 반영한다.
적시성과 무결성
- 일반적으로 일관성이라는 용어는 두 가지 요구사항이 합쳐졌다고 본다.
- 적시성(Timeliness)
- 사용자가 시스템을 항상 최신 상태로 관측 가능하다는 의미
- 사용자가 뒤쳐진 복사본을 읽는다면 일관성이 없는 상태를 관측할 수 있다.
- 트랜잭션의 선형성은 적시성을 달성하는 가장 강력한 방법이다. (커밋 후 읽기)
- 적시성을 위반해도 일시적이며 재시도하면 결국 해소된다.
- 무결성(Integrity)
- 손상이 없는 상태로 누락된 데이터, 모순된 데이터, 잘못된 데이터가 없다는 뜻
- 무결성을 위반하면 불일치가 영원히 지속된다.
- 대부분의 애플리케이션에서 무결성이 적시성보다 훨씬 중요하다.
- 데이터플로 시스템의 정확성
- ACID 트랜잭션은 대개 적시성과 무결성 모두 보장하지만 데이터플로 시스템은 적시성과 무결성을 분리한다.
- 이벤트 스트림을 비동기로 처리할 때 소비자가 반환하기 전 명시적으로 메시지 도착을 기다리지 않는다면 적시성은 보장되지 않는다.
- 그러나 무결성이 스트림 시스템의 핵심이다.
- 원자적으로 기록하기 쉽도록 쓰기 연산 내용을 단일 메시지로 표현
- 결정적 파생 함수를 사용해 단일 메시지에서 모든 상태 갱신을 파생
- 클라이언트의 요청을 요청 ID를 포함하여 전달하여 멱등성 보장
- 메시지를 불변으로 만들고 필요 시 파생 데이터 재처리
- 유일성 보장을 위해 합의를 사용할 수도 있지만 많은 애플리케이션이 완화된 유일성 개념을 사용한다.
- ex) 두 사람이 동시에 같은 좌석을 예약한다면 한 사람에게 사과 메시지를 보내 다른 좌석을 고르게 한다.
- ex) 재고보다 많은 상품이 주문된다면 소비자에게 사과와 함께 가격을 할인하거나 주문을 취소
- 이러한 접근법을 보상 트랜잭션이라 한다.
- 비즈니스 맥락에서 제약을 일시적으로 위반하고 나중에 사과해 바로잡는 것은 실제로 수용 가능한 방법이다.
- 코디네이션 회피 데이터 시스템
- 데이터플로 시스템은 원자적 커밋과 선형성, 파티션에 걸친 동기 코디네이션 없이도 파생 데이터에 대한 무결성을 보장할 수 있다.
- 엄격한 유일성 제약은 적시성과 코디네이션을 요구하지만 많은 애플리케이션은 느슨한 제약을 사용해도 실제로 괸찮다.
- 이러한 코디네이션 회피 데이터 시스템은 동기식 코디네이션 시스템보다 성능이 좋고 더 나은 내결함성을 지닌다.
- 하지만 너무 많은 불일치나 너무 많은 사과를 할 순 없으니 최적의 장소를 찾는 것이 목표다.
믿어라. 하지만 확인하라.
- 소프트웨어 버그가 발생해도 무결성 유지하기
- 소프트웨어 버그 위험성은 항상 존재한다.
- 저자는 MySQL에서 유일성 제약 조건 유지에 실패한 사례를 목격한 적이 있다고 한다.
- 성숙도가 낮은 소프트웨어는 더 심각할 것
- 애플리케이션 코드에선 더 많은 버그를 각오해야 한다.
- 데이터베이스는 ACID에 의해 일관성을 유지할거라 기대하지만 이는 트랜잭션에 버그가 없을 때만 통한다.
- 약속을 맹목적으로 믿지 마라
- 버그는 피할 수 없기에 버그를 고치고 오류를 추적하기 위해 데이터 손상을 찾는 방법을 마련해야 한다.
- 데이터 무결성을 체크하는 작업을 감사(auditing)라 한다.
- ex) HDFS나 S3 같은 대규모 저장소 시스템은 손상 위험을 줄이기 위해 백그라운드로 지속적으로 파일을 복제본과 비교하고 디스크 사이에서 이동시킨다.
- 검증하는 문화
- 대부분 정확성 보장이 절대적이라 가정하여 감사를 수행하지 않는다.
- ACID 데이터베이스의 트랜잭션이 강력한 것은 사실이라 오랜 기간 감사 메커니즘에 투자한 저장소가 많이 없다.
- 그러나 NoSQL 같은 느슨한 일관성이 표준이 되었기에 감사 메커니즘은 더욱 필요한 시점이다.
- 감사 기능 설계
- 이벤트 기반 시스템은 감사 기능을 제공하는데 적합하다.
- 이벤트 소싱을 사용하면 어떤 상태 갱신 결과를 결정적으로 재현하는 것이 가능하다.
- 결정적이고 잘 정의한 데이터플로를 사용하면 디버깅과 시스템이 왜 그렇게 동작했는지 추적이 쉬워진다.
옳은 일 하기
- 기술 그 자체로 좋거나 나쁜 것이 아니고 중요한 것은 기술을 어떻게 사용하고 사람들에게 영향을 주는가다.
- 소프트웨어 엔지니어도 윤리적 책임을 져야 한다.
예측 분석
- 데이터 시스템은 사용자 행동을 예측하거나 추천을 제공하는 데 사용된다.
- 이러한 시스템은 데이터 기반의 결정을 내리는데 잘못된 모델링은 부정확한 결과를 초래할 수 있다.
- 의사결정 자동화는 아직 책임과 의무에 대한 문제가 해결되지 않았다.
- 알고리즘이 잘못됐을 때 누가 책임을 지는가?
- 의사결정에 데이터를 최우선으로 하는 맹목적 믿음은 위험하다.
- 편견이 강화되는 것을 막고 발생하는 실수를 수정하는 방법을 찾아야 한다.
- 추천 시스템 등에선 피드백 루프에 빠질 수 있다.
- 서비스가 사람들이 보고자 하는 콘텐츠를 예측하는 데 익숙해지면 이미 그들이 공감하는 의견만을 보여줘 고정관념과 오보, 양극화를 불러오게 된다.
- 특히 자기 강화 피드백 루프 때문에 문제가 발생한다.
- ex) 불운으로 경제적 어려움에 처한 사람의 신용 점수는 제 때 청구서 지불을 못하고 구직을 하기 어려워지며 더더욱 악화된다.
사생활과 추적
- 데이터 수집 자체에도 윤리적 문제가 있다.
- 사용자가 명시적으로 입력한 데이터는 상관 없다.
- 사용자 활동을 추적하는 등 부수 효과로서 로그를 남긴다면 불명확해진다.
- 행동 데이터 추적은 온라인 서비스에서 사용자 대면 기능을 위해 중요해지고 있다.
- 사용자에게 더 나은 서비스를 제공할 수 있게 되는 것은 사실이다.
- 다만 회사 사업 모델에 따라 광고주가 실제 고객이고 사용자 이익은 뒷전이 될 수도 있다.
- 동의와 선택의 자유
- 사용자 행동 추적은 약관 동의를 받았기에 정당하다고 보는 주장도 있다.
- 하지만 사용자는 제공하는 데이터가 무엇이고, 어떻게 처리되는지에 대한 지식이 거의 없다.
- 사용자가 이해할 수도 없는 형태의 데이터를 추출하고 정보 제공도 하지 않으니 서비스와 사용자 간 관계는 매우 일방적이다.
- 자산과 권력으로서의 데이터
- 개인의 행동 데이터는 가치 있는 자산으로 취급되어 많은 사람들과 기업들이 데이터를 원한다.
- 이러한 데이터는 악용될 위험이 있다.
- 심지어 데이터가 유출되거나 국가에 넘어갈 수도 있다.
- 법률과 자기 규제
- 데이터 보호법으로 개인의 권리를 보호할 순 있지만 오늘날 인터넷 맥락에 효과적인진 의심스럽다.
- 근본적으로 저자는 데이터에 대한 기술 산업에 문화 전환이 필요하다고 한다.
- 사용자를 최적화할 지표로 삼지 말고 존중, 존업, 대리의 자격이 있는 인간이라는 점을 기억해야 한다.
- 각 개인은 스스로의 사생활을 보호할 수 있어야 한다.
- 정확히 어떻게 이를 달성할 지는 아직 해결되지 않은 문제이긴 하다.