본문 바로가기
스터디/데이터 중심 애플리케이션 설계

[데이터 중심 애플리케이션 설계] 12. 데이터 시스템의 미래

by 디토20 2023. 2. 15.
반응형

 

 

 

 

[데이터 중심 애플리케이션 설계] 12. 데이터 시스템의 미래

  • 데이터 시스템은 미래에 어떻게 되어야 할까?
  • 신뢰할 수 있고 확장 가능하며 유지보수하기 쉽게 만드는 데이터 시스템의 미래를 고찰해보자.

1. 데이터 통합

1.1 파생 데이터에 특화된 도구의 결합

1.1.1 데이터플로에 대한 추론

  • 같은 데이터의 사본을 여러 저장소 시스템에 유지해야 할 때 입력과 출력을 분명히 해야한다.
  • 어디서 데이터를 처음으로 기록하는지, 어떤 표현형이 어떤 원본에서 파생되는지, 데이터를 모두 올바른 장소로 올바른 형식으로 어떻게 넣는지 등에 대해 충분히 고려해야 한다.
  • 파생 데이터 시스템은 이벤트 로그를 기반으로 갱신하면 결정적이고 멱등성을 지녀 결함에서 복구하기가 상당히 쉬워진다.

1.1.2 파생 데이터 대 분산 트랜잭션

  • 파생 데이터와 분산 트랜잭션의 큰 차이점
    • 트랜잭션 시스템은 일반적으로 선형성을 지원한다. (자신이 쓴 내용 읽기 같은 유용한 기능을 보장)
    • 파생 데이터 시스템은 대개 비동기로 갱신되기 때문에 기본적으로 동시간 갱신 보장을 하지 않는다.

1.1.3 전체 순서화의 제약

  • 작은 시스템에서 이벤트 로그의 순서 전체를 보장하는 것은 가능하나 규모가 커지면 한계가 생긴다.
  • 이벤트 전체 순서를 결정하는 것은 전체 순서 브로드캐스트 라고 하고, 이것은 합의와 동등하다.

1.2 일괄 처리와 스트림 처리

  • 데이터 통합의 목표는 데이터를 올바른 장소에 올바른 형태로 두는 것
  • 이것을 위해 입력을 소비해 적절한 출력으로 기록해야 한다.

1.2.1 파생 상태 유지

  • 일괄 처리는 결정적이고 출력이 입력에만 의존하며, 명시적 출력 외에는 다른 부수 효과가 없는 순수 함수를 장려한다.
  • 입력과 출력을 잘 정의한 결정적 함수의 원리는 내결함성에 도움이 될 뿐만 아니라 조직 내의 데이터플로 추론을 단순화한다.
  • 비동기 방식을 사용하면 이벤트 로그 기반 시스템을 훨씬 견고하게 만들 수 있다.
  • 색인을 비동기 방식으로 유지한다면 파티션 간 통신에서 더욱 신뢰성 있고 확장성이 좋아진다.

1.2.2 애플리케이션 발전을 위한 데이터 재처리

  • 파생 데이터를 유지할 때 일괄 처리와 스트림 처리는 모두 유용하다.
    • 스트림 처리를 이용하면 입력의 변화를 빠르게 파생 뷰에 반영할 수 있다.
    • 일괄 처리 시스템을 사용하면 누적된 상당한 양의 과거 데이터를 재처리해 기존 데이터셋을 반영한 새 파생 뷰를 만들 수 있다.
  • 기존 데이터를 재처리하는 것은 시스템을 유지보수하기 위한 좋은 메커니즘으로 새로운 기능 추가와 변경된 요구사항에 대응할 수 있다.
    • 재처리 없이 스키마를 변경하는 작업은 새 필드를 추가하는 것과 같은 간단한 것으로 제한된다.
    • 반면 재처리를 이용하면 완전히 다른 모델로 데이터셋을 재구축할 수 있다.
  • 파생 뷰를 사용하면 점진적 발전이 가능하다.
    • 이전 스키마와 새 스키마를 함께 유지해 같은 데이터를 기반으로 두 개의 독립적인 파생 뷰를 만들 수 있으며 점진적으로 새 뷰를 접근하는 비율을 늘려 기존 뷰를 내릴 수 있다.
    • 점진적 이전의 장점은 처리의 모든 단계에서 뭔가 잘못됐을 때 쉽게 이전으로 되돌릴 수 있다는 점이다.
  •  

2 데이터베이스 언번들링

2.1 데이터 저장소 기술 구성하기

  • 데이터베이스에 내장된 기능과 일괄 처리와 스트림 처리로 구축하는 파생 데이터 시스템 사이에는 유사점이 있다.

2.1.1 색인 생성하기

  • CREATE INDEX를 실행할 때마다 데이터베이스는 근본적으로 기존 데이터셋을 재처리해서 기존 데이터를 반영하는 새로운 뷰로서 색인을 파생하는데, 이것은 스트림 시스템에서 변경 데이터 캡처의 예비 과정과도 상당히 유사하다.

2.1.2 모든 것의 메타데이터베이스

  • 일종의 전체 조직의 데이터플로가 거대한 데이터베이스처럼 볼 수 있다.
  • 일괄 처리와 스트림 처리자는 트리거와 스토어드 프로시저 그리고 구체화 뷰 유지 루틴을 정교하게 구현한 것과 같다.
  •  

2.2 데이터플로 주변 애플리케이션 설계

  • 현대 데이터 시스템은 내결함성과 확장성이 있어야 하고 지속성 있게 데이터를 저장해야 한다.
  • 또한 데이터 시스템은 시간이 흐름에 따라 다른 그룹의 사람들이 개발한 이종 기술과도 통합이 가능해야 할 뿐 아니라 이미 존재하는 라이브러리와 서비스를 재사용 가능해야 한다.

2.2.1 파생 함수로서의 애플리케이션 코드

  • 데이터셋이 다른 데이터셋으로부터 파생될 때는 변환 함수 몇가지를 거친다.
  • 보조 색인용 파생 함수는 아주 일반적인 요구사항이라서 많은 데이터베이스에 핵심 기능으로 내장돼 있다.
  • 파생 데이터셋을 생성하는 함수가 보조 색인 생성 함수와 같은 비슷한 표준 함수가 아니라면 사용자 정의 코드를 써서 애플리케이션에 특화된 측면을 다뤄야 한다.

2.2.2 애플리케이션 코드와 상태의 분리

  • 이론상 데이터베이스가 운영체제와 같이 임의의 애플리케이션 코드를 배포하는 환경이 될 수 있으나 이는 현실성이 부족하다.
  • 메소스, 얀, 도커, 쿠버네티스 등과 같은 배포와 클러스터 관리 도구는 애플리케이션 코드를 수행하는 목적으로 특별히 설계되었다.
  • 오늘날 대부분의 웹 애플리케이션은 상태 비저장 서비스로 배포된다.
    • 상태 비저장 서비스 내에서 사용자 요청은 어떤 애플리케이션 서버로도 라우트될 수 있다.
    • 상태 관리(데이터베이스)와 상태 비저장 애플리케이션 로직을 분리한다.
  • 전형적인 웹 애플리케이션 데이터베이스는 네트워크를 통해 동기식으로 접근할 수 있는 변경 가능한 공유 변수와 같이 동작한다.
    • 애플리케이션은 이 변수를 읽고 갱신할 수 있으며 데이터베이스는 이 변수를 지속성 있게 만들고 동시성 제어와 내결함성을 지원한다.
  • 대부분의 프로그래밍 언어에서 변경 가능한 변수의 변경을 구독할 수 없다.
  • 데이터베이스의 내용이 변경됐는지 확인하고 싶다면 폴링, 즉 주기적으로 질의를 반복하는 게 유일한 방법이다.
  •  

2.2.3 스트림 처리자와 서비스

  • 단일 일체식 애플리케이션에 비해 서비스 지향 아키텍처의 가장 큰 장점은 느슨한 연결을 통한 조직적 확장성이다.
  • 서로 다른 팀은 다른 서비스를 운영하기에, 서비스가 독립적으로 배포되고 갱신될 수 있다.
  • 스트림 연산자로 데이터플로 시스템을 구성하는 것은 마이크로서비스 접근법과 유사한 특징이 상당히 많지만 기반이 되는 통신 메커니즘은 매우 다르다.
    • 마이크로서비스는 동기식 요청/응답
    • 스트림 연산자 시스템은 단방향 비동기식 메시지 스트림

2.3 파생 상태 관찰하기

  • 데이터플로 시스템은 검색 색인이나 구체화 뷰 또는 예측 모델과 같은 파생 데이터셋을 생성하고 최신 상태로 유지하는 과정에 사용할 수 있다.
  • 시스템에 정보를 기록할 때마다 일괄 처리와 스트림 처리의 여러 단계를 거친 다음 결과적으로 기록된 데이터를 모든 파생 데이터셋에 통합해 갱신한다.
  • 파생 데이터를 생성하는 이유는 이후에 같은 질의를 할 가능성이 크기 때문이다.
  • 파생 데이터셋은 쓰기와 읽기가 만나는 장소로, 쓰기 시간에 필요한 작업의 양과 읽기 시간에 필요한 작업의 양 간에 트레이드 오프를 나타낸다.

2.3.1 구체화 뷰와 캐싱

  • 전문 검색 색인은 좋은 예제로, 키워드에서 모든 단어를 포함(AND)하거나 어떤것이든 포함(OR)하는 등의 논리를 적용해야 한다.
  • 색인이 존재하지 않는다면 검색 질의는 모든 문서를 스캔해야 한다.
  • 색인이 없으면 쓰기 경로의 작업량은 줄지만 읽기 경로의 작업이 상당히 늘어난다.
  • 고정된 가장 공통적인 질의 집합의 검색 결과를 미리 계산 해 색인까지 가지 않고 빠르게 처리 할 수 있다. 이를 일반적으로 공통 질의 캐시라고 한다.

2.3.2 오프라인 대응 가능한 상태 저장 클라이언트

  • 오프라인 우선 애플리케이션은 인터넷 연결 요구 없이 같은 장치의 로컬 데이터베이스를 이용해 최대한 많은 일을 하고, 네트워크 연결이 가능할 때 백그라운드에서 원격 서버와 동기화 한다.
  • 장치 상 상태를 서버 상 상태의 캐시로 생각할 수 있다.

2.3.3 상태 변경을 클라이언트에게 푸시하기

  • 명시적으로 변경 사항을 폴링하지 않으면 장치의 상태는 갱신되지 않은 신선도가 떨어지는 캐시다.
  • 많은 최신 프로토콜이 HTTP의 기본적인 요청/응답 패턴을 벗어나고 있다.
    • 서버 전송 이벤트(이벤트 소스, Event Source API)와 웹소켓(WebSocket) 은 웹브라우저가 서버와 TCP 접속을 유지하면서 연결이 유지되는 동안 서버가 주도적으로 메시지를 브라우저에 보내는 방식의 통신 채널을 제공한다.
    • 이 방식을 통해 서버의 로컬에 저장된 상태가 변경되었을 때 서버에서 주도적으로 사용자에게 알려줘, 클라이언트 측 상태의 신선도가 떨어지는 것을 줄여준다.
  • 로그 기반 메시지 브로커를 통해도 오프라인 장치에 대한 알림을 날려줄 수 있다.

2.3.4 종단 간 이벤트 스트림

  • 상태 저장 클라이언트와 사용자 인터페이스 개발용 최신 도구는 이미 내부적으로 사용자 입력을 표현하는 이벤트 스트림이나 서버 응답 스트림을 구독하는 방식을 사용해 클라이언트 측 상태를 관리한다.
  • 상태 변경은 종단 간(end-to-end) 쓰기 경로를 따라 흐를 수 있다.
    • 즉, 상태 변경이 트리거된 한 장치로부터 다른 장치의 상태를 보고 있는 사람의 사용자 인터페이스까지 이어진다.
  • 다만 쓰기 경로를 최종 사용자까지 확장하려면 근본적으로 시스템을 구축하는 방식을 재고할 필요가 있다.
    • 요청/응답 상호작용 방식에서 벗어나 발행/구독 데이터플로 방식으로 변경해야 한다.
    • 데이터 시스템을 설계한다면 현재 상태를 단지 질의하는 방식이 아니라 변경 사항을 구독하는 방식을 염두에 두어야 한다.

2.3.5 읽기도 이벤트다

  • 일회성 읽기 요청은 단지 조인 연산자를 통해 흘러가고 이후 즉시 사라진다.
  • 그러나 읽기 이벤트 로그를 기록하면 잠재적으로 인과적 의존성과 시스템 전체의 데이터 출처를 추적할 수 있다는 장점이 있다.
  • 지속성 있는 저장소에 읽기 이벤트를 기록하면 인과적 의존성을 추적하기가 더 용이하나,추가 저장소가 필요하며 I/O 비용이 발생한다.

3. 정확성을 목표로

3.1 데이터베이스에 관한 종단 간 논증

  • 직렬성 트랜잭션 같은 비교적 강력한 안전성 속성을 지원하는 데이터 시스템을 사용해도 애플리케이션에 데이터 유실과 손상이 없을 것이라는 보장은 없다.

3.1.1 연산자의 정확히 한 번 실행

  • 실패한다면 포기하거나 재시도하며, 재시도는 중복의 위험성이 있다.
  • 정확히 한 번 연산은 재시도시 동일한 결과를 최종적으로 얻기 위해 계산을 조정한다는 뜻이다.
  • 가장 효과적인 접근법 중 하나는 연산은 멱등으로 만드는 것이다.

3.1.2 연산 식별자

  • 고유 ID를 사용해 중복 요청을 억제할 수 있다.
  • 고유 식별자를 만들어 클라이언트 애플리케이션 내 숨은 폼 필드에 포함하거나 유효한 모든 폼 필드의 해시값을 계산해서 연산 ID를 만들 수 있다.

3.2 제약 조건 강제하기

3.2.1 유일성 제약 조건은 합의가 필요하다

  • 유일성 제약 조건을 강제하기 위해서는 합의가 필요하다.
  • 합의를 달성하는 가장 일반적인 방법은 단일 노드를 리더로 만들고 해당 노드가 모든 결정을 하게끔 책임을 부여하는 것이다.
  • 유일성 검사는 유일성이 필요한 값을 기준으로 파티셔닝하면 확장 가능하다.
  • 비동기 마스터 복제는 충돌되는 쓰기를 받아들여 값이 유일하지 않을 수 있기 때문에 쓸 수 없다.
  • 제약 조건을 위반하면 어떤 쓰기도 즉시 거부하기를 원한다면 동기식 코디네이션이 필요하다.

3.2.2 로그 기반 메시징의 유일성

  • 로그는 모든 소비자가 동일한 순서로 메시지를 보도록 보장한다.
  • 공식적으로 이 보장을 전체 순서 브로드캐스트(total order broadcast) 라 부르고 이것은 합의와 동일하다.
  • 스트림 처리자는 단일 스레드 상에서 한 로그 파티션의 모든 메시지를 순차적으로 소비한다.
  • 유일성 기준으로 로그를 파티셔닝하면 스트림 처리자는 충돌이 발생한 연산 중 어떤 것이 처음 들어온 연산인지 판단할 수 있다.
  • 파티션 수를 늘리면 쉽게 확장할 수 있어 대규모 요청을 처리할 수 있다.
  • 근본 원리는 충돌이 발생할 수 있는 쓰기를 모두 같은 파티션으로 라우팅하고 순서대로 처리하는 것으로, 유일성 제약 조건뿐만 아니라 다른 많은 제약 조건에도 사용할 수 있다.

3.3 적시성과 무결성

  • 트랜잭션의 한 가지 편리한 속성은 대개 선형성이 있다는 점입니다.
    • 즉, 기록자는 트랜잭션이 커밋될 때까지 기다리고 커밋 이후부터 해당 쓰기가 모든 독자에게 보인다.
  • 일반적으로, 일관성이라는 용어는 두 가지 요구사항이 합쳐진 것을 볼 수 있다.
    • 적시성(Timeliness) : 사용자가 시스템을 항상 최신 상태로 관측 가능
    • 무결성(Integrity) : 손상이 없음
  • 적시성 위반은 "최종적 일관성"이고 무결성 위반은 "영구적 불일치" 로 볼 수 있다.
  • 적시성을 위반하면 성가시고 혼란스러울 수 있으나, 무결성을 위반하면 파국을 맞는다.

3.3.1 데이터플로 시스템의 정확성

  • ACID 트랜잭션은 대개 적시성과 무결성 양쪽 모두 보장하므로 적시성과 무결성을 구분하는 것은 중요하지 않다.
  • 반면 이벤트 기반 데이터플로 시스템은 적시성과 무결성을 분리한다. 무결성이 스트림 시스템의 핵심
  • 신뢰성 있는 스트림 처리 시스템은 분산 트랜잭션과 원자적 커밋 프로토콜 없이 무결성을 보존할 수 있다.
  • 무결성은 아래 메커니즘의 결합을 통해 달성할 수 있다.
    • 쓰기 연산을 단일 메시지로 표현하기
    • 결정적 파생 함수를 사용해 해당 단일 메시지에서 모든 상태 갱신을 파생하기
    • 클라이언트가 생성한 요청 ID를 모든 처리 단계를 통해 전달하기
    • 메시지를 불변으로 만들고 필요 시 파생 데이터 재처리하기

3.3.2 느슨하게 해석되는 제약 조건

  • 유일성 제약 조건을 강제하려면 합의가 필요하고 합의는 일반적으로 모든 이벤트를 단일 노드를 통해 특정 파티션으로 보내 처리하는 방식으로 구현된다.
  • 많은 애플리케이션이 훨씬 완화된 유일성 개념을 사용해 이 제한을 피할 수 있다.
    • 두 사람이 동시에 같은 사용자명을 등록하거나 같으 좌석을 예약한다면, 두 사람 중 한 사람에게 사과 메시지를 보내 다른 이름이나 좌석을 고르게끔 부탁할 수 있다. 이러한 종류의 변경 방법을 보상 트랜잭션이라고 한다.
    • 소비자가 재고보다 더 많은 상품을 주문한다면 일단 재고 주문을 더 넣고 소비자에게 배송 지연에 대해 사과하고 가격을 할인해준다.
    • 비슷하게 많은 항공사가 일부 승객이 비행기를 놓칠 것으로 예상하고 초과 예약을 받는다.
    • 누군가 계좌 잔고보다 더 많은 돈을 뺀다면 은행은 그 사람에게 초과 인출 수수료를 부과하고 빚진 돈을 값으라고 요구한다.
  • 많은 비즈니스 맥락에서 제약 조건을 일시적으로 위반하고 나중에 사과해 바로잡는 것은 실제로 수용 가능한 방법이다.
  • 애플리케이션은 무결성을 반드시 요구한다. 그러나 제약 조건을 강제하는 상황에서도 적시성은 반드시 필요한 것은 아니다.

 

 

 

 

 

728x90
반응형

댓글