데이터 중심 애플리케이션 설계 - 스트림 처리(2)

문지은·2022년 5월 11일
0

저번에 이어서 스트림 처리 관련해서 이야기를 해보자

저번 내용을 까먹으신 분들을 위한 간단 요약

  • 입력 데이터 크기를 한정 짓지 않고 준 실시간 반영을 위해서 스트림 등장
  • 메시징 시스템 사용하여 새로운 이벤트를 소비자에게 알려줌 : 직접 전달, 메시지 브로커
  • 로그 기반 메시지 저장소 : 데이터베이스의 지속성 + 메시징 시스템의 짧은 지연 시간
  • 이종 데이터 시스템에서의 동기화 이슈 : 경쟁 조건 발생, 내결함성
  • 이벤트 스트림의 아이디어를 데이터 베이스에 적용하여 문제 해결하는 방법을 살펴보자

이종 데이터 시스템에 스트림 적용하기

변경 데이터 캡쳐(CDC)

  • 데이터베이스에 기록하는 모든 데이터의 변화를 관찰해 다른 시스템으로 데이터를 복제하도록 추출
  • 변경 내용을 스트림으로 제공하면 유용
  • 데이터베이스 하나를 리더로 하고 나머지는 팔로워
    • 트리거 방식 : 변화 관찰하는 트리거 등록. 고장, 오버헤드
    • 복제 로그 파싱 방식 : 스키마 변경 대응 이슈 존재하지만 견고
  • 비동기로 동작 -> 복제 지연과 동일한 이슈 발생
  • 모든 로그를 저장하면 디스크 공간 부족, 로그 재생 작업도 오래 걸림
    • 스냅숏 저장
    • 로그 컴팩션 : 주기적으로 같은 키의 로그 레코드를 찾아 중복 제거, 툼스톤 제거
    • 파생 데이터 시스템을 재구축할 때 새로운 소비자는 컴팩션된 로그 토픽의 오프셋 0부터 순차적으로 데이터베이스의 모든 키를 스캔하면 됨

이벤트 소싱

  • 변경 데이터 캡쳐와 유사하지만 추상화 레벨이 다름
    • 로그를 데이터베이스에서 저수준으로 추출하므로 쓰기 순서가 보장되고 경쟁 조건 일어나지 않음
    • 애플리케이션 로직은 이벤트 로그에 기록된 불변 이벤트를 기반으로 구축
    • 저수준에서 상태 변경하지 않고 애플리케이션 수준에서 발생한 일 반영
  • 디버깅이 쉽고 애플리케이션 버그 방지
  • 로그 컴팩션을 CDC와 다르게 처리해야 함
    • 이벤트의 결과가 아닌 사용자의 행동 의도 표현
    • 최신 키 값을 갱신하지 않고 전체 히스토리 저장 -> 로그 컴팩션 불가능
    • 스냅숏 이용

불변 이벤트와 스트림

  • 이벤트와 명령을 구분하는 데 주의
    • 명령의 무결성이 검증되면 불변 이벤트가 됨
    • 이벤트는 생성 시점에 사실이 됨
  • 불변성 원리가 이벤트 소싱, CDC를 매우 강력하게 만듦
    • 변경 로그를 지속성 있게 저장한다면 상태를 간단히 재생성할 수 있다
    • 데이터를 덮어 쓰지 않고 추가만 하기 때문에 버그가 있을 경우 문제 상황 진단, 복구가 훨씬 쉽다
    • 고객의 히스토리를 저장할 수 있으므로 훨씬 많은 정보를 포함

동일한 이벤트 로그로 여러가지 뷰 만들기

  • 불변 이벤트 로그에서 가변 상태를 분리하면 동일한 이벤트 로그로 다른 여러 읽기 전용 뷰를 만들 수 있다
  • 데이터 쓰는 형식 / 읽는 형식 분리
    • 데이터 저장의 유연성 얻음
    • 명령과 질의 책임의 분리(CQRS, command query responsibility segregation)

동시성 제어

  • 이벤트 소싱, CDC의 가장 큰 단점은 이벤트 로그의 소비가 비동기로 이루어진다는 것
  • 사용자가 쓰고 바로 읽으면 반영 안 되어 있을 수 있다
  • 읽기 뷰의 생신과 로그에 이벤트 추가 작업을 동기식으로 수행하여 해결
  • 이벤트 로그로 현재 상태를 만들면 동시성 제어 측면이 단순해진다
    • 다중 객체 트랜잭션은 단일 사용자 동작이 여러 다른 장소의 데이터를 변경해야 할 때 필요
    • 사용자 동작은 한 장소에서 한 번 쓰기만 필요
    • 즉 이벤트를 로그에 추가만 하면 되며 원자적으로 만들기 쉽다

불변성의 한계

  • 영구적으로 모든 변화의 불변 히스토리를 유지하는 것이 어느 정도까지 가능할까?
    • 빈번히 갱신과 삭제가 일어나는 데이터는 히스토리가 커지고 파편화 문제 발생 가능
    • 컴팩션, 가비지 컬렉션의 성능 문제 발생 가능
    • 개인 정보 이슈로 데이터 삭제해야 할 경우도 존재
    • 적출, 셔닝
    • 데이터 삭제는 매우 어렵다
      • 많은 복제본 존재
      • 저장소 엔진, 파일 시스템, SSD는 데이터 덮어쓰기보다 새로운 장소에 기록
      • 백업은 불변
      • 삭제는 데이터를 찾기 불가능 보다는 찾기 어렵게

스트림으로 할 수 있는 일

  • 데이터 기록/질의
  • 이벤트를 사용자에게 직접 보내기
  • 하나 이상의 입력 스트림으로 하나 이상의 출력 스트림 생산 (이 부분에 대해 설명)

복잡한 이벤트 처리

  • complex event processing(CEP)
  • 이벤트 스트림 분석용
  • 스트림에서 특정 이벤트 패턴 찾는 규칙을 규정
  • 질의와 데이터의 관계가 일반적 데이터베이스와 반대
    • 질의를 오랜 기간 저장
    • 입력 스트림으로부터 들어오는 이벤트는 흘러가며 매칭되는 질의 찾음

스트림 분석

  • 특정한 이벤트 패턴을 찾기 보다 대량의 이벤트를 집계하고 통계적 지표 뽑음
  • 집계 시간 간격 : 윈도우
  • 블룸 필터, 하이퍼로그로그, 백분위 추정 알고리즘을 통해 최적화
  • 아파치 스톰, 스파크 스트리밍, 플링크, 콩코드, 아프카 스트림, 구글 클라우드 데이터플로, 애저 스트림 분석

구체화 뷰 유지하기

  • 데이터베이스 변경에 대한 스트림은 파생 데이터 시스템이 원본 데이터베이스의 최신 내용을 따라잡게 하는데 사용
  • 어떤 데이터셋에 대한 또 다른 뷰를 만들어 효율적으로 질의할 수 있게 함
  • 기반이 되는 데이터가 변경될 때마다 뷰를 갱신
  • 구체화 뷰를 만들려면 임의의 시간 범위 동안 발생한 모든 이벤트 필요

스트림 상에서 검색하기

  • CEP 외에도 전문 검색 질의와 같은 복잡한 기준으로 개별 이벤트 검색
  • 전통적인 검색 엔진은 문서를 색인하고 색인을 통해 질의
  • 스트림 검색은 질의를 먼저 저장한 뒤 문서가 질의를 지나가면서 실행
  • 최적화하기 위해 질의를 색인할 수 있음

메시지 전달과 RPC

  • 유사 RPC 시스템과 스트림 처리 사이에 겹치는 영역 존재
  • 아파치 스톰에서의 분산 RPC(distributed RPC) : 이벤트 스트림을 처리하는 노드 집합에 질의를 맡김

시간에 관한 추론

  • 일괄 처리는 이벤트의 타임 스탬프 이용 -> 같은 입력으로 같은 처리 시 동일 한 결과
  • 윈도우 시간을 결정할 때 처리하는 장비의 시스템 시계 이용 -> 간단하지만 이벤트 생성과 처리 사이의 시간이 길어지면 문제 발생

이벤트 시간 대 처리 시간

  • 이벤트 시간 기준으로 윈도우를 정의하면 발생하는 문제 : 특정 윈도우에서 모든 이벤트가 도착했다거나 아직도 이벤트가 계속 들어오고 있는지를 확신할 수 없다
  • 이벤트가 어딘가에 버퍼링되어 있는 상태에서 윈도우 종류시 낙오자 이벤트가 발생
  • 낙오자 이벤트 처리 방법
    • 무시
    • 수정 값 발행(낙오자 이벤트가 포함된 윈도우를 기준으로 갱신된 값)
  • 낙오자 이벤트에게 어떻게 타임스탬프를 할당할까?
  • 3가지 타임스탬프를 로그로 남긴다
    • 이벤트가 발생한 시간 - 장치 시계
    • 이벤트를 서버로 보낸 시간 - 장치 시계
    • 서버에서 이벤트를 받은 시간 - 서버 시계

윈도우

  • 이벤트 타임스탬프를 어떻게 결정할 지 안다면 다음은 윈도우 기간을 어떻게 정의하는지 이다
  • 텀블링 윈도우 : 고정 길이. 모든 이벤트는 정확히 한 윈도우에 속한다
  • 홉핑 윈도우 : 고정 길이. 결과를 매끄럽게 하기 위해 윈도우를 중첩
  • 슬라이딩 윈도우 : 각 시간 간격 사이에 발생한 모든 이벤트를 포함. 시간 기준으로 정렬한 이벤트를 버퍼에 유지하고 오래된 이벤트가 만료되면 윈도우에서 제거
  • 세션 윈도우 : 고정된 시간이 없다. 같은 사용자가 짧은 시간동안 발생시킨 모든 이벤트를 그룹화. 일정 시간이 지나서 비활성화되면 윈도우 종료

스트림 조인

스트림 상에서 새로운 이벤트가 언제든 나타날 수 있다는 사실은 스트림 상에서 수행하는 조인을 일괄 처리 작업에서의 조인보다 훨씬 어렵게 만든다.

스트림 스트림 조인(윈도우 조인)

  • 웹 사이트의 검색된 URL의 최신 경향을 파악하고 싶을 때
  • 검색과 클릭 사이의 시간이 가변적이므로 조인을 위한 적절한 윈도우 선택이 필요
  • 예를 들면 한시간 이내에 발생한 검색과 클릭 조인
  • 해당 유형의 조인은 스트림 처리자가 상태를 유지해야 함 (이벤트 매칭시 클릭했다 / 안했다)

스트림 테이블 조인(스트림 강화)

  • 사용자 활동 이벤트 집합과 사용자 프로필 데이터베이스를 조인
  • 강화 (enriching)
  • 원격 데이터베이스를 질의할 수도 있지만 느리고 데이터베이스에 과부하를 줄 수 있다
  • 네트워크 왕복없이 질의가 가능하도록 스트림 처리자 내부에 데이터베이스 사본 적재
  • 이 때 데이터베이스를 항상 최신으로 유지하기 위해서 CDC 사용
  • 스트림 스트림 조인과 유사 (스트림 쪽이 무한 윈도우라고 생각하면)

테이블 테이블 조인(구체화 뷰 유지)

  • 트위터에서 사용자가 트윗을 전송/삭제 하면 해당 사용자를 팔로잉하는 모든 사용자의 타임라인에 트윗 추가/삭제
  • 사용자를 팔로우/언팔로우 하면 해당 사용자의 최근 트윗을 타임라인에 추가/삭제
  • 트윗 이벤트 스트림과 팔로우 관계 이벤트 스트림이 필요
  • 트윗, 팔로우의 두 테이블을 조인하는 질의에 대한 구체화 뷰 유지

조인에서의 시간 의존성

  • 시간에 따라 변하는 상태를 조인할 때 어느 시점을 조인에 사용해야 할까?
  • slowly changing dimension(SCD) 문제
  • 조인되는 레코드의 특정 버전을 가리키는 유일한 식별자를 이용해 해결
  • 모든 버전을 보유해야 하므로 로그 컴팩션 불가

내결함성

  • 스트림 처리자가 어떻게 결함에 견딜 수 있는지 알아보자.
  • 일괄 처리에서의 내결함성은 여러번 시도하더라고 작업의 결과가 동일함이 보장
    • exactly-once semantics
    • effectively-once
  • 스트림 처리는 일괄 처리와 달리 입력이 무한하기 때문에 출력을 노출하기 전에 태스크가 완료될 때까지 기다릴 수 없다.

마이크로 일괄 처리와 체크 포인트

  • 스트림을 작은 블록으로 나누고 각 블록을 소형 일괄 처리와 같이 다루는 방법
  • 스파크 스트리밍에서 사용
  • 블록 크기가 작을수록 스케줄링, 코디네이션 비용 증가
  • 블록 크기가 커질수록 결과를 보기까지의 지연 시간 증가
  • exactly-once semantics 보장
  • 하지만 출력이 스트림 처리자를 떠나자마자 스트림 처리 프레임워크는 실패한 일괄 처리 출력을 더 이상 지울 수 없다 -> 실패한 태스크 재시작 시 부수 효과 두번 발생

원자적 커밋 재검토

  • 태스크가 성공했을 때만 출력과 이벤트 처리의 부수효과 발생하게 함
  • 원자적으로 모두 일어나거나 모두 일어나지 않아야 함
  • 스트림 처리 프레임워크 내에서 상태 변화와 메시지를 관리해 트랜잭션을 내부적으로 유지

멱등성

  • 여러번 수행하더라도 오직 한 번 수행한 것과 같은 효과를 내는 연산
  • 메타 데이터 이용해서 구현 가능
    • 데이터베이스에 기록 시 트리거 메시지의 오프셋 함께 저장
  • 멱등성의 가정
    • 실패한 태스크 재시작 시 같은 순서로 메시지 재생
    • 처리는 결정적
    • 어떤 노드도 동시에 같은 값을 갱신하지 않음

실패 후에 상태 재구축하기

  • 실패 후에도 상태 복구됨을 보장
  • 원격 데이터 저장소에 상태 유지하고 복제 -> 개별 메시지를 원격 데이터베이스에 질의하는 것은 느림
  • 스트림 처리자의 로컬에 상태를 유지하고 주기적으로 복제
  • 충분히 작은 크기의 윈도우로 만든 데이터라면 입력 스트림 이용해서 재구축 가능
  • 시스템의 디스크 접근 지연 시간, 네트워크 지연 시간에서 오는 트레이드 오프를 고려하여 결정
profile
백엔드 개발자입니다.

0개의 댓글