[microservice-upLearning] Ⅴ. 데이터 처리

Ⅴ. 데이터 처리

5.1 독립적인 배포와 데이터 공유

ch4에서 샘뉴먼이 말한 일반적인 마이크로서비스의 제안항목

  • 마이크로서비스는 서로 느슨하게 결합되어 독립적으로 배포 가능해야 함
  • 마이크로서비스 내부의 기능 관련해서 높은 응집력을 가져야 한다

느슨한 결합의 또다른 중요한 측면

  • 느슨한 결합
    • 서비스가 느슨하게 결합 되면 서비스를 변경해도 다른 서비스에 영향을 주지 않음
    • 마이크로서비스 주요 이점 : 규머가 커지더라도 안정성/품질의 조화와 함께 속도↑
    • 마이크로서비스간의 조정을 제거하거나 최소한으로 줄임으로써 달성 가능
  • 또다른 측면 : 독립적인 배포 가능성
    • 시스템의 다른 부분이나 다른 마이크로서비스의 변경/배포 필요 없이 독립적으로 서비스를 변경/배포 가능해야한다
    • 마이크로서비스 배포가 의존성을 가지면? 릴리즈가 복잡하고 취약해짐, 전체 시스템의 속도와 안전성 모두 손상 위험

마이크로서비스 독립적 배포 불가한 이유와 데이터 ?

  • 데이터 맥락에서 가장 일반적인 문제는?
  • 여러 마이크로서비스가 데이터 공간을 공동으로 소유하는 것
  • 데이터를 공동으로 소유하면 느슨한 결합, 독립적인 코드 배포가 어려움

5.2 데이터를 포함하는 마이크로서비스

opening

모놀리스의 문제

  • 데이터 공유
    • 대규모 시스템의 다양한 모듈화된 부분을 통합하닌 기본 패턴
    • 모놀리스 아키텍처에서 주로 사용
    • 기존 레거시보다 더 모듈화된 SOA 또한 데이터 공동 소유가 관행
  • 모놀리스는 모듈화가 아니다? 틀린 사실
    • 사람들은 모놀리스가 모듈화 되지 않고 구성요소로 나눈 큰 덩어리라고 생각
    • 개발자들은 작은 코드 베이스로 나눈 것이 관리와 효율이 좋다는 것을 알고 있음
    • 모놀리스의 문제 : 분리된 모듈을 독립적 배포가 불가능하다는 문제
    • ex)SOA설계도 데이터 결합 때문에 독립적 배포 불가 → 더 큰 규모로 발전하지 못함

문제예시

여러 마이크로서비스가 DB에서 고객 테이블 소유권을 공유하고 있음
(소유권 : 공유된 테이블에서 서로 다른 마이크로서비스가 데이터를 읽고 수정할 수 있음을 의미)

  • 가정: 항공편 검색 마이크로서비스가 공유 테이블의 열중 하나의 필드 유형을 변경해야 함
    • 정수→ 부동소수점등으로 변경한다면?
    • 동일 테이블에 엑세스하고 특정타입에 의존하는 예약이나 항공편 추적 마이크로서비스에 문제 생길 수 있음
    • 문제해결?
      • 항공편 검색 마이크로서비스의 필요로 데이터 모델 변경할 때마다 예약, 항공편 추적마이크로서비스 관련 코드 변경 필요
      • 또한 하나의 변경으로 연관된 모든 마이크로서비스를 다시 배포해야 한다
  • 데이터 계증 변경의 파급 효과
    • 여러 구성요소가 공통으로 데이터 소유시 매우 일반적
    • 다양한 서비스간 결합 유발 → 독립적 배포 가능성 문제 유발

마이크로서비스에서는 독립적 배포 가능성이 핵심 가치

  • 결과적으로 데이터 공유가 금지된다
  • db의 데이터 공간에 대한 공통 책임 허용 X
  • 마이크로서비스는 자체 데이터를 소유(포함)해야 한다
  • db의 데이터셋을 어떤 마이크로서비스가 소유하는지 명확히 알 수 있어야 한다

자체 데이터 포함에 대한 중요한 고려사항

5.2.1 데이터 포함으로 DB 클러스터 수가 급증해서는 안된다

  • 복잡한 어플리케이션 구축 : 다양한 종류의 DB 사용
  • DB의 데이터셋(RDBMS의 TABLE등의)은 여러 마이크로서비스가 공동 소유해선 안됨
  • 데이터 독립성은 복잡한 DB를 별도로 구축해야 함을 의미하지는 않는다
    • ‘마이크로서비스는 데이터를 소유한다’의 개념을 명확하게 이해가 필요
    • 마이크로서비스는 물리적 db 클러스터를 공유할 수 있다
    • 여러 마이크로 서비스가 동일한 논리적인 테이블 공간과 동일 데이터를 수정하지 않는다면?
      물리적 클러스터를 공유하는 것은 실제로 문제가 되지 않는다
  • 데이터관리의 독립성은 스트림을 넘지 않는 것
    • 필요한 경우 마이크로서비스를 가져와 다른 DB 설치와 함께 배포 가능해야 한다
  • 비용을 들여 다른 DB를 사용해서 각 서비스를 배포할 필요는 없음
    • 비용은 중요 고려 대상이다
    • 단순성 또한 중요 고려대상
    • 여러 마이크로서비스가 동일한 데이터 공간에 접근하고 수정하지 않으면 데이터 독립성 조건 만족

5.2.2 데이터 포함 및 데이터 위임 패턴

모놀리식을 작은 모듈화를 하면 마이크로 서비스인가? 절대 아님

  • 모놀리식의 N계층 아키텍처도 ‘마이크로’수준의 작은 모듈로 나눌 수 있으며 각 모듈은 네트워크 서비스로 배포될 수 있음
  • 마이크로서비스는 구성요소가 조정제거를 목표로 모듈화 되고 느슨하게 결합되며 느슨하게 배포가 가능해야 한다
  • 임의로 분할되고 느슨하게 결합되지 않은 시스템은 마이크로서비스 아키텍처가 될 수 없음
  • 앞에서 봤던 이미지를 다시 살펴보자
  • 문제점
    • 마이크로서비스의 데이터 포함 원칙을 떠올려보자
    • 이러한 데이터 설계는 3개의 서비스가 동일 데이터 공간을 공유
    • 이는 독립적인 배포 가능성을 손상 시킴 → MSA의 큰 문제
  • 해결책 : 위임 서비스 뒤에 공유 데이터를 숨기는 간단한 기술
  • 항공편 인벤토리서비스 : 항공편 정보와 관련된 모든 것을 처리할 권한을 가진 서비스 선언
    • 항공편에 대한 정보가 필요하거나 업데이트해야하는 모든 서비스는
    • 항공편 인벤토리서비스에서 적절한 엔드포인트를 호출해야 한다
    • 유연한 조회 API를 구현했다면 이전의 항공편 검색 서비스는 항공편 인벤토리의 기능중 일부가 된다
    • 중요 포인트 : 예약항공편 추적서비스가 직접 항공편 테이블 접근하는 문제를 해결
    • 앞으로 항공편에 대한 모든 정보는 항공편 인벤토리서비스를 통해서 얻어야 한다
    • 예시1: 예약 서비스가 항공편에 충분한 좌석이 남아있는지 알아야 할 경우
      • DB의 항공편 테이블을 직접 쿼리하는 대신 해당 쿼리를 항공편 인벤토리 서비스에 전달하여 조회 가능
    • 예시2: 항공편 추적서비스가 비행중인 비행기의 위치를 알고 업데이트 해야할 경우
      • DB 테이블에 직접 접근 하는 대신 항공 인벤토리 서비스를 통해 작업 수행
    • 항공편 인벤토리서비스가 데이터를 숨기고 캡슐화하여 데이터를 감싸는 대리인 역할 가능
    • 여러 서비스가 동일한 데이터 테이블을 공유하지 않음
  • 반드시 하나의 서비스를 대리인으로 변환할 필요는 없다
    • 이러한 패턴에서 꼭 그럴 필요는 없으며 다른 방법도 가능
    • 위에서는 항공편 검색 서비스를 항공편 인벤토리서비스로 전환 하고 항공편 테이블을 캡슐화
    • 항공편 인벤토리 서비스를 새로 개발 후 항공편 검색서비스가 이를 참조하게 할 수도 있음

이제 모든 문제가 끝?

  • 위임을 활용한 방식은 우아하며 다양한 경우에 활용 가능
  • 불행히도 모든 데이터 공유를 이러한 방법으로 해결할 수 없음
  • 위임 패턴이 모든 시나리오에 적용된다고 믿는 것은 매우 순진한 생각
  • 분석, 데이터 감사, 머신러닝과 같은 시스템의 경우 마이크로서비스 경계를 넘어 데이터 접근/수정 필요
  • 전통적인 데이터 트랜잭션 또한 공유 데이터 Lock 필요

데이터 중복을 사용하여 독립 문제 해결

분석, 데이터 감사, 머신러닝과 같은 시스템

  • 문제점

    • 데이터 수정없이 분산된 데이터에 대한 읽기 전용 접근이 필요
    • 마이크로서비스 경계를 넘어선 접근에 해당
  • 일반적인 해결책

    • 모든 마이크로서비스의 데이터 세트를 공유된 공간으로 복사
    • Data Lake : 공유된 공간을 일반적으로 일컽는 말
    • 데이터는 이동이 아니라 복사된다는 점에 유의
    • DataLake는 데이터 쿼리가 가능한 읽기 전용 공간
    • 마이크로서비스는 여전히 해당 데이터셋 권한 가지며 기본 소유자 역할
    • 마이크로서비스가 관련된 데이터를 데이터 스트리밍하면 데이터가 축적되어 쿼리 준비
    • DataLake와 같은 집계 인덱스에는 이러한 데이터를 운영적으로 업데이트하지 않는 것이 중요
      (→ 데이터의 무결성, 명확성을 위해서)
    • DataLake는 기록용 DB로 취급되면 안되며, 단순히 참조된 데이터 스토어다

SOR(System of Record)

  • 마이크로서비스 같은 SOR 데이터 저장소에서 데이터가 스트리밍되면?
    → 집계 데이터는 쿼리에 최적화된 방식으로 인덱싱 된다

신뢰할 수 있는 메시징 인프라

SOR에서 DataLake로 스트리밍→ 일반적으로 신뢰할 수 있는 메시징 인프라 사용

  • IBM MQ, RabbitMQ 등
  • Kafka : 가장 인기 있는 솔루션
  • Apache Pulsar : 빠르게 성장하고 있는 신규 솔루션

DataLake와 공유 데이터 인덱스 → 다양한 읽기 전용 사용 사례 해결

  • 하지만 분산 데이터가 읽기 전용이 아니라면?

5.2.4 분산 트랜잭션과 실패 처리

가정 케이스(항공편 예약)

  • 지불 프로세스 시작시에 좌석 예약이 가능했으나 중간에 다른 사람이 예약 완료 한 경우
  • 현업의 경우 언제든 이러한 문제 발생 가능 - 전체 프로세스 롤백 처리
  • 신용카드 결제는 취소까지 시간이 걸리더라도 최소 마일리지 포인트를 사용한 경우 마일리지 포인트도 환불되어야 함

모놀로식 어플리케이션 : ACID특성의 DB 트랜잭션으로 안전하게 수행

  • 위의 케이스 경우 DB 트랜잭션으로 안전하게 관리됨
  • 장애가 생겨도 ACID 특성의 DB 트랜잭션으로 안전하게 수행
  • ACID
    • Atomicity(원자성)
      • All or nothing
      • 트랜잭션 단계는 모두 실행되거나, 모두 실행되지 않아야 한다
    • Consistency(일관성)
      • 트랜잭션은 시스템을 하나의 유효한 상태에서 다른 유효한 상태로 전환해야 한다
    • Isolation(격리성)
      • 트랜잭션의 병렬 실행은 각 트랜잭션을 순차적으로 실행한 것과 동일한 결과야 함
    • Durability(지속성)
      • 트랜잭션이 커밋(완전히 실행)되면 장애가 발생해도 데이터 손실이 없어야 한다

ACID 트랜잭션은 기존 데이터 관리의 대표적 방법

  • 복잡한 시스템에서 실패는 항상 존재한다
  • 오류를 완전히 피하는 것은 불가능한 일
  • 실패를 고려해서 자동으로 복구할 수 있는 방법이 최선
  • ACID 구현 시스템→ 실패 항상 발생한다고 가정 → 장배 복구 가능 방식으로 설계

ACID트랜잭션의 한계 : 분산시스템에 적합한 솔루션이 아님

  • ACID 트랜잭션은 배타적 잠금(exclusive lock)에 의존
  • 마이크로서비스는 데이터를 소유, 다른 마이크로서비스의 데이터 수정을 허용X
  • 위 사항을 고려하면 ACID 잠금은 마이크로서비스 시스템에서 구현이 어려움(혹은 높은 비용)
  • 분산 시스템에는 더 적합한 패턴이 필요

Saga를 사용한 분산 트랜잭션

Saga

사가를 사용하면?

  • ACID 트랜잭션과 직접적으로 동일하지 않음
    • 사가는 롤백시 시스템이 초기 상태로 반드시 돌아감을 보장하지 않음
    • 오히려 시스템이 부분 완료된 트랜잭션의 실행취소를 수용할수 있는 수준을 반영하는 ‘합리적인 상태’에 도달해야함
  • 트랜잭션의 모든 단계: 각 단계에 요청된 작업 수행 + 롤백시 실행해야 하는 보상 작업 정의
  • 보상 작업 : routing slip에 등록되며 다음 단계로 넘어감
  • 만약 이후 단계 실패시?
    • routing sliop에 정의된 모든 보상 작업 실행 → 수정사항 되돌림, 시스템을 ‘합리적으로 보상된 상태’로 만듬
합리적인 상태

케이스) 좌석예약

  • 예약서비스 예약 실패시?
    • 이전의 알림결제의 보상 작업도 같이 호출된다
    • 결제의 보상 조치 : 고객에게 돈을 환불 하는 작업 → 결제 유형에 따라 즉시 환불이 아닐 수도 있음
    • 따라서 결제 시스템은 즉시 초기 단계로 돌아가지 않을 수 있다
    • 하지만 결국에 고객은 돈을 돌려받을 수 있다
    • ACID에선 고객이 트랜잭션 롤백 여부를 알 수 없지만 사가에서는 결제가 진행되고 취소됨을 인지 가능하다
  • 알림 서비스 실패시?
    • 더 복잡해질 수 있음
    • email, sms
      • 회수가 불가능 → 보상 트랜잭션으로 회수 설정 불가
      • 보상 트랜잭션 : 고객에게 예약 실패를 알리고 이전 메시지를 무시해야함을 전달하는 새로운 메시지 보내야함
      • 역시 상황에 따라 합리적이지만 시스템을 초기상태로 되돌리지 않음
      • 고객은 아무 메시지도 받지 않는 상태로 돌아가는게 아니라 2개의 메시지를 더 보게 됨
사가에서 이벤트의 순서는 유의미
  • 사가의 이벤트 순서는 중요하며 신중히 구성되어야 한다
  • 일반적으로 보상하기 어려운 단계를 트랜잭션 마지막 단계로 이동하는 것이 좋다
  • 위 예시에서 (비지니스 규칙이 허용하는 경우)알림을 맨 끝으로 이동하면 수정 메시지를 크게 줄일 수 있음
  • 이렇게 하면 트랜잭션이 알림을 전달하는 경우 이전 단계들이 성공했음을 알 수 있음

또?

위임 서비스, 데이터 레이크 사가

  • 모두 강력한 패턴
  • MSA의 많은 데이터 격리 문제 해결 가능
  • 이 3가지 패턴이 해결할수 없는 문제들은 어떻게??

5.3 이벤트 소싱과 CQRS

관계형 데이터 모델링과 데이터 공유의 관계
  • 여러 해결 방법을 써도 복잡한 시나리오에선 결국 원하는 수준의 데이터 격리와 느슨한 결합 지원이 불가능
  • 예) 마이크로서비스가 소유한 데이터셋에서 ‘조인’을 생성하는 경우
  • 관계형 데이터 모델링은 데이터의 정규화, 재사용, 참조와 같은 기본 원칙에 뿌리
  • 즉 관계형 데이터 모델은 기본적으로 데이터 공유를 선호하는 경향

5.3.1 이벤트 소싱

이벤트 소싱
  • 2005년 마틴 파울러에 의해 처음 언급
  • 디자인 패턴 분야의 유명한 Greg Young의 2014년 세미나 발표를 통해 인기시작
  • Greg Young이 말하는 이벤트 소싱
    • 시스템 도메인 객체가 아닌 이벤트를 저장하는 것에 대한 데이터 모델링 접근 방식
  • 이벤트 소싱 : ‘사실’과 ‘상태’(구조적 모델)을 저장하는 것
  • ‘사실’ : 이벤트 발생의 대표적인 값을 의미
    • ex) LA→NewYork의 이코노미 좌석이 20만원 이상되었다
  • ‘상태’ : ‘사실’에서 파생된 값, 일시적

회계외 체스의 이벤트 소싱

회계와 체스를 통해 이벤트 소싱의 개념을 파악해보자

  • 회계일지 (accounting journal)
    • 고전적 이벤트 저장 방식 사례중 하나
    • 회계사는 개별 거래를 기록하고 잔액은 모든 거래를 합산한 결과
    • 회계사는 ‘상태’를 기록하지 않으며 각 거래 후 결과 잔액을 기록
  • 체스
    • 경기 기록시 보드에 있는 모든 말의 위치를 기록하지 않는다
    • 개별적인 이동을 저장
    • 보드의 상태는 현재까지 일어난 모든 이동의 합이다
    • 특정 경기에 대한 모든 이동에 대한 로그가 있다면 게임 상태를 완전히 재현 가능
      → 실생활에서의 이벤트 소싱과 동일

이벤트 소싱 VS 관계형 모델링

  • 기존 시스템(RDBMS, NoSQL, Document DB)의 접근 방식
    • 일반적으로 어떠한 상태(state)를 저장
    • 예) 항공편 이코노미 좌석의 현재 가격 → 어떤 상태를 저장
  • 이벤트 소싱의 접근 방식
    • 상태를 저장하지 않음
    • 데이터 변경에 대한 사실을 저장
    • 시스템의 현재 상태는 일련의 변경에서 계산된 파생값

예약시스템의 예시

  • 기존 시스템의 관계형 데이터 모델
    • 고객 계정 및 결재 방법과 1:N 관계를 맺는 고객 연락처 정보 테이블로 구성
    • 각 고객 계정 레코드는 완료된 여행, 진행 중인 예약, 계정 관련 선호도를 가르킬 수 있음
    • 세부 사항은 다를 수 있으나 대부분의 전통적인 유형과 유사
  • 이벤트 소싱 데이터 모델
    • 이벤트 소싱을 이용하면 위 그림과 같은 이벤트 시퀀스로 동일한 모델 설계 가능
    • 시스템을 이끄는 이벤트를 확인 가능
      • 고객 연락처 정보를 수집 → 개인 계정을 열고 지불 방법을 입력
      • 여러번의 예약과 여행을 마친 고객은 비지니스 계정을 만들기로 결정
      • 고객은 지불정보 입력후 새로운 계정으로 여행을 예약
      • 몇가지 선호도 추가 하면 윗 그림과 동일한 상태가 됨
      • 단순 상태 기반의 표현과 다름
    • 현재 상태로 이어진 ‘사실’의 정확한 순서를 볼 수 있다
      • 이 일련의 이벤트는 관계형 DB의 모델과 동일한 상태를 결과적으로 제공한다
      • 이전과 다르게 보이지만 사실은 동일하며 오히려 더 균일해 보이기 까지 한다
      • 다양한 엔티티 유형과 서로간의 관계에 대해 내릴 수 있는 의사결정이 훨씬 적다
    • 어떤 면에서는 더 간단하다
      • 다양한 비지니스 이벤트가 발생한다
      • 그리고 이 이벤트의 파생물로 현재 상태를 계산할 수 있다
      • 더 간단하고 예측 가능하며 다양한 엔티티 간 참조 관계가 없다
    • 또한 각 유형의 이벤트는 서로 다른 마이크로서비스에서 소유 → 데이터 공유 회피 가능
      • 예) 고객 인구 통계 마이크로서비스 : 입력된 고객정보는 해당 시스템에 속하는 자연스러운 이벤트

이벤트는 어떻게 생겼?

이벤트 데이터 구조의 ‘모양’
  1. 고유한 식별자가 필요 : 예로 분산 시스템에서 전역적 고유함을 보장하는 UUID를 사용 가능
  2. 이벤트 유형 : 다른 이벤트와 착각하지 않도록
  3. 데이터 : 이벤트 유형과 관련된
이벤트 예시
1
2
3
4
5
6
7
8
{
"eventId" : "afb2d89d-2989-451f-857d-80442c8cd9a1",
"eventType" : "priceIncreased",
"data" : {
"amount" : 120.99,
"currency" : "USD"
}
}
기술적 특성에 대한 설계 결정
  • 이벤트와 함께 작업할 경우 상당히 직관적
  • 대부분의 작업: 비지니스 로직을 기반으로 이벤트의 도메인 관련 필드 올바르게 설명하는데 사용
  • 관계형 접근방식의 ‘주관적이고 기술적인 테이블 형태와 관계 형성의 종류’가 훨씬 적다

projection

프로젝션

  • 이벤트 소싱에서 현재와 같은 특정 시점의 상태를 계산하는 작업
  • 이벤트 기반의 상태 제공, 매우 간단
  • 프로젝션을 실행하려면 프로젝션 함수가 필요
  • 프로젝션 함수 : 현재 상태와 새로운 이벤트를 기반으로 새로운 상태를 계산
  • 예시
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 항공권 가격에 대한 프로젝션 함수
    function priceUp(state, event){
    state.increasePrice(event.amount)
    }

    function priceDown(state, event){
    state.decreasePrice(event.amount)
    }


    //특정 시점의 가격 : 관계된 모든 이벤트에 대한 프로젝션 함수 실행
    let price = priceUp(priceUp(priceDown(s,e),e),e);
  • 관계형 모델의 UPDATE prices SET price=.. SQL 쿼리와 동일
  • 현재 상태 : 현시점 까지 발생한 이벤트의 left fold임을 알수 있음[함수형 프로그래밍]

이벤트 소싱을 사용하면?

  • 현재 상태뿐 아니라 특정 시점의 상태를 계산 가능
  • 과거 시점의 상태 값을 확인 가능한 정교한 분석이 가능

5.3.2 롤링 스냅샷으로 성능 향상

프로젝션은 비싸다

  • 예) 은행잔고 같이 상태변화가 빈번히 발생
  • 현재 잔고 확인할 때마다 처음부터 계산?
  • 느리며 리소스 낭비, 또한 현재 상태 조회는 빠른 응답속도 필요
  • 중간 값들을 저장하고 마지막 스냅샷부터 계산하면 ? 빠른 계산 가능

중간값 스냅샷은 일반적

  • 적절한 스냅샷 시점은 어플리케이션 도메인 마다 다름
  • 예) 은행 매월 말일 계정 잔액 스냅샷 → 잔고 확인시 전달 스냅샷부터 프로젝션 계산

롤링 스냅샷

  • 이벤트 소싱에서 저장된 프로젝션
  • 롤링 스냅샷, 프로젝션 구현 세부사항은 어플리케이션 마다 달라질 수 있음
  • 예) 은행은 월,분기, 연말에 다양한 잔액 계산 → 월단위 롤링 스냅샷을 생성하는 것은 매우 적절
  • 도메인 마다 자연스러운 시점을 찾아서 스냅샷을 정렬해야 한다
  • 해당 챕터 뒤에서 CQRS 패턴을 사용해 롤링 스냅샷에서 캐시 상태 이상의 작업 수행 가능

5.3.3 이벤트 스토어

이벤트 스토어

  • 비교적 단순한 시스템으로 다양한 데이터 스토리지 시스템을 사용해서 구현 가능
  • 다양한 스토리지 예시
    • 파일 시스템의 파일
    • 아마존의 S3 버킷
    • DB 스토리지

이벤트 스토어의 인터페이스 : 다음 3가지 기본 기능

  1. 새로운 이벤트를 저장하고 올바른 시퀀스를 할당하여 저장된 순서대로 이벤트 검색 가능
  2. 관심있는 이벤트에 대한 프로젝션을 생성한 구독자에게
    알림을 생성하고 경쟁 소비자 패턴(competing-consumers)을 활성화하는 기능
  3. 조정 흐름을 위해 특정 유형의 이벤트 X 이후에 N개의 이벤트를 가져올 수 있는 기능
    (예: 프로젝션이 손실/손상/의심되는 경우 다시 계산)
    → 따라서 본질적으로 이벤트 저장소의 기본 인터페이스는 다음 두가지 기능으로 구성된다
    1
    2
    save(x)
    getNAfterX()

소비자가 이벤트 구독 가능하게 하는 강력한 알림 시스템

  • 강력한의 의미 : 경쟁 소비자 패턴에 대한 적합성을 의미
  • 경쟁 소비자 패턴
    • 어떤 시스템에서 이벤트 프로젝션을 구축하든
    • 중복성/확장성을 위해 이벤트 수신하는 클라이언트 인스턴스가 여러개 필요할 수 있음
    • 따라서 중요한 패턴
  • 알림
    • 데이터 손상으로 이어지는 이벤트 중복 방지 필요
      → 수신자의 단일 인스턴스에 한 번만 전달되어야 한다
  • 두 가지 접근 방법
    1. 카프카와 같은 소비자에게 신뢰성 보장하는 메시지 큐 구현 보장
    2. 소비자가 HTTP endpoint를 콜백으로 등록 가능토록 허용
      → 새로운 이벤트에 대해 콜백 엔드포인트 호출하고 소비자 측의 LB가 작업 분배 처리
  • 어느 쪽도 본질적으로 더 나은 접근 방식이 아님
강력한 프로젝션 위해선? CQRS 보안 패턴이 필요

5.3.4 명령과 쿼리의 분리

고급 이벤트 소싱 시스템에 대한 프로젝션 : 일반적으로 CQRS 패턴 사용해서 빌드

CQRS Pattern

  • 시작 : 쿼리 시스템과 데이터 저장 시스템이 동일할 필요가 없다는 것에서 시작

숨어있는 진실

  • 앞에서 말한 내용 : 이벤트 스토어는 구현이 쉽다
  • 진실
    • save(x)getNAfterX() 함수가 해당 데이터에 대한 정교한 쿼리를 수행 불가능하다
  • 예시: 지난 24시간 동안 승객이 업데이트한 모든 좌석의 예약을 조회하는 쿼리 → 실행 불가능
  • 이벤트 저장소를 단순/집중적으로 유지하기 위해 위와 같은 유형의 쿼리는 이벤트 저장소에 구현되지 않음
  • 이벤트 소싱 : 이벤트 로그를 신뢰할 수 있고 안정적으로 저장하는 문제만 해결해야 한다
  • 고급 쿼리
    • 이벤트 발생할 때마다 이벤트 저장소를 구독하는 다른 시스템에게 알리고
    • 시스템은 필요한 방식으로 데이터를 쿼리하는 데 최적화된 인덱스 작성을 시작

CQRS의 기본 개념
→ 데이터 저장소, 데이터 소유권, 데이터 쿼리 가능성의 문제를 동일 시스템에서 해결 시도 않는 것
→ 이런 문제는 독립적으로 해결해야 함

이벤트 소싱 & CQRS 사용의 장점
  • 가장 큰 장점 : 매우 세분화되고 느슨하게 결합된 구성 요소를 설계 가능
  • 이벤트 소싱 : 한가지 유형의 이벤트 관리/ 단일리포트 실행가능한 아주 작은 마이크로서비스 생성 가능
  • 목표에 잘 맞춰 사용시 MSA에서 한단계 높은 자체 세분화 달성 가능
  • 서비스 경계에 걸쳐 데이터 조인등의 복잡한 경우에 마이크로서비스간 데이터 공유 방지 가능
이벤트 소싱 & CQRS 사용의 단점
  • 가장 큰 단점 : 높은 복잡성
    • 이벤트 소싱과 CQRS는 만병 통치약이 아니다
    • 이벤트 소싱과 CQRS를 과도하게 사용하지 않도록 주의
    • 이벤트 소싱과 CQRS는 구현하기 위한 복잡성이 있음 → 필요한 경우에만 사용해야 한다
    • 전체 시스템에 대한 유일한 데이터 모델링 접근 방식으로 사용해서는 안됨
    • 기존의 관계형 모델이 훨씬 간단/활용되는 사례가 여전히 많음

5.4 마이크로서비스 외의 이벤트 소싱과 CQRS 활용

이벤트 소싱과 CQRS

  • 데이터 공유를 방지하고 MSA의 느슨한 결합 구현시 매우 유용하다
  • 하지만 MSA 외에도 다양한 시스템에 활용될 수 있는 강력한 데이터 모델링 도구임

1] CAP 정리 시점으로 바라보기

CAP 정리

  • 에릭 브루어(Eric Brewer)가 2000년 심포지엄에서 발표한 내용
  • https://oreil.ly/hiQMB
  • 내용 요약 : 분산 공유 데이터 시스템은 아래 3가지 특성중 2가지 특성만 가질 수 있다
    1. 일관성(Consistency) : 데이터의 최신 상태에 대한 단일성 보장
    2. 가용성(Availability) : 항상 데이터를 읽거나 업데이트 가능
    3. 분할 내성(partition tolerance) : 네트워크 파티션에도 정확한 데이터를 얻을 수 있음
  • CAP의 모든 조합이 유효하지 않다
    • 시간이 지남에 따라 CAP 모든 조합이 유효하지 않다는 것이 점점 명확해짐(https://oreil.ly/nHBoN)
    • 분산 시스템의 경우
      → 네트워크 파티션 피할 수 없음
      → 분할 내성 고려
      → 일관성/가용성 모두 만족시킬 수 없음
      → 하지만 일관성/가용성이 꼭 필요하다면?!
CQRS가 출동한다면 어떨까?!
  • CAP 정리: 데이터 공유를 사용하는 단일 시스템이 일관성/가용성/분할 내성의 모든 조건 만족이 불가능
  • 하지만 CQRS 활용 → 여러 시스템 사용, 데이터 공유 최소화 → 이러면 이야기가 달라짐
  • 이 경우 이벤트 스토어에서 일관성 우선순위 정하고, 쿼리 인덱스에서 가용성의 우선순위 정할 수 있음
  • 쿼리 인덱스에 사용하는 시스템은 일관성 깨질 수 있음?
    • 신뢰할 수 있는 소스가 아님
    • 필요한 경우 이벤트 스토어에서 다시 인덱싱 가능

2] 감사 가능성(auditability)과 관련하여 바라보기

관계형 데이터 모델을 사용할 경우 in-place 업데이트

  • 예) 고객의 주소/전화번호가 올바르지 않으면? 해당 테이블에 바로 업데이트
  • 나중에 고객이 이러한 기록에 이의를 제기하면?
    • 관계형 모델 → 히스토리가 없음 → 복구할 방법이 없음
    • 이벤트 소싱 → 모든 변경에 대한 기록이 안전하게 보존 → 과거의 값과 업데이트 이력 확인 가능
  • 다른 목소리
    • “Splunk나 ELK같은 시스템을 사용하면 모든 변경 사항을 로깅할 수 있어요~”
  • 로깅와 이벤트 소싱은 다르다. 절대 다르다
    • 아키텍처의 the source of truth(진실의 근원)이 되는 시스템은 무엇인가?
    • 로그와 현재 상태가 일치하지 않으면 어떤 것을 ‘신뢰’할 것인가
      • 이벤트 소싱
        • 상태 : 계산된(computed)값 → 더 정확하다
          -Splunk 로그
        • 일부 버그를 찾기 위해 두번씩 확인해도 진실의 근원이 관계형 모델일 가능성이 높음
  • 신뢰할 수 있는 이벤트 로그가 진실의 근원이라면?
    • 데이터 모델링 접근 방식으로 이벤트 소싱을 사용하는 것
    • 그렇지 않으면 아무리 많은 로그를 생성해도 이벤트 소싱이 아니다

Related POST

공유하기