[데이터 중심 애플리케이션 설계] 11. 스트림 처리

예니·2023년 2월 25일
0
post-thumbnail
  • 일괄 처리는 입력이 고정된 크기로 한정되므로 입력을 읽는 작업이 끝나는 시점을 알 수 있다.
  • 맵리듀스의 정렬 연산은 출력을 생산하기 전에 전체 입력을 다 읽어야하므로, 조기에 출력을 시작할 수 없다.
  • 사용자는 데이터를 계속 생산하므로 데이터셋은 절대 완료되지 않는다. 따라서 일괄 처리 프로세서는 인위적으로 일정 기간씩 데이터 청크를 나눠야 한다.
  • 고정된 시간 조각이라는 개념을 완전히 버리고 단순히 이벤트가 발생할 때마다 처리하는 것이 스트림 처리의 기본 개념이다.
  • 이벤트 스트림은 일괄 처리 데이터와는 반대로 한정되지 않고 점진적으로 처리된다.

이벤트 스트림 전송

  • 입력이 파일일 때 대개 첫 번째 단계로 파일을 분석해 레코드의 연속으로 바꾸는 처리를 한다. 스트림 처리 문맥에서 레코드는 보통 이벤트라고 하지만 특정 시점에 일어난 사건에 대한 세부 사항을 포함하는, 작고 독립된 불변 객체라는 점에서 본질적으로 동일하다. 이벤트는 일반적으로 일기준 시계를 따르는 이벤트 발생 타임스탬프를 포함한다.
  • 생산자(producer, publisher, sender)가 이벤트를 한 번 만들면 해당 이벤트를 복수의 소비자(consumer, subscriber, recipient)가 처리할 수 있다. 파일 시스템에서는 관련 레코드 집합을 파일 이름으로 식별하지만 스트림 시스템에서는 대개 토픽이나 스트림으로 관련 이벤트를 묶는다.
  • 폴링 방식은 비용이 크기 때문에 이벤트 알림 전달 목적으로 개발된 특별한 도구들이 있다.

메시징 시스템

  • 생산자는 이벤트를 포함한 메시지를 전송한다. 메시지는 소비자에게 전달된다.
  • 메시징 시스템은 다수의 생산자 노드가 같은 토픽으로 메시지를 전송할 수 있고 다수의 소비자 노드가 토픽 하나에서 메시지를 받아갈 수 있다.
  • 발행/구독 모델에서는 여러 시스템들이 다양한 접근법을 사용한다. 아래 두가지 질문으로 시스템을 구별할 수 있다.
    • 생산자가 소비자가 메시지를 처리하는 속도보다 빠르게 메시지를 전송한다면 어떻게 될까? 세가지 선택지가 있다.
      • 메시지를 버리거나
      • 큐에 메시지를 버퍼링하거나
      • 배압(backpressure, 흐름제어라고도 함. 생산자가 메시지를 더 보내지 못하게 막음)을 적용하거나
    • 노드가 죽거나 일시적으로 오프라인이 된다면 어떻게 될까? 손실되는 메시지가 있을까? 지속성을 갖추려면 저장, 복제해야하므로 비용이 든다. 메시지를 잃어도 된다면 처리량은 늘고 지연시간은 낮아진다.

생산자에서 소비자로 메시지를 직접 전달하기

  • 직접 메시징 시스템은 설계 상황에서는 잘 동작하지만 일반적으로 메시지가 유실될 수 있는 가능성을 고려해서 애플리케이션 코드를 작성해야 한다.
  • 생산자와 소비자가 항상 온라인 상태라고 가정하기 때문에, 생산자나 소비자가 오프라인이 된다면 메시지가 유실되는 문제가 발생할 수 있다.

메시지 브로커

  • 직접 메시징 시스템의 대안으로 메시지 브로커(메시지 큐)를 통해 메시지를 보내는 것이다.
  • 메시지 브로커는 메시지 스트림을 처리하는 데 최적화된 데이터베이스의 일종이다. 메시지 브로커는 서버로 구동되고 생산자와 소비자는 서버의 클라이언트로 접속한다. 생산자는 브로커로 메시지를 전송하고 소비자는 브로커에서 메시지를 읽어 전송받는다.
  • 브로커에 데이터가 모이기 때문에 지속성 문제가 생산자와 소비자에서 브로커로 옮겨간다.
  • 큐 대기를 하면 소비자는 일반적으로 비동기로 동작한다.

메시지 브로커와 데이터베이스 비교

  • 데이터베이스는 명시적으로 데이터가 삭제될 때까지 데이터를 보관한다. 메시지 브로커는 소비자에게 데이터 배달이 성공할 경우 자동으로 메시지를 삭제한다.
  • 메시지 브로커는 메시지를 빨리 지우기 때문에 작업 집합이 작다고 가정한다. 큐 크기가 작다. 많은 메시지를 버퍼링해야 한다면 처리 시간이 길어지고 전체 처리량이 저하된다.
  • 데이터베이스는 보조 색인, 데이터 검색을 위한 다양한 방법을 지원한다. 메시지 브로커는 특정 패턴과 부합하는 토픽의 부분 집합을 구독하는 방식을 지원한다.
  • 데이터베이스에 질의할 때 결과는 질의 시점의 스냅숏을 기준으로 한다. 메시지 브로커는 임의 질의를 지원하지 않지만 데이터가 변하면 클라이언트에게 알려준다.

복수 소비자

  • 복수 소비자가 같은 토픽에서 메시지를 읽을 때 사용하는 주요 패턴
    • 로드 밸런싱 각 메시지는 소비자 중 하나로 전달된다. 따라서 소비자들은 해당 토픽의 메시지를 처리하는 작업을 공유한다. 브로커는 메시지를 전달할 소비자를 임의로 지정한다. (공유 구독)
    • 팬 아웃 각 메시지는 모든 소비자에게 전달된다. 팬 아웃 방식을 사용하면 여러 독립적인 소비자가 브로드캐스팅된 동일한 메시지를 서로 간섭 없이 청취할 수 있다.

확인 응답과 재전송

  • 소비자는 언제라도 장애가 발생할 수 있다. 메시지를 잃어버리지 않기 위해 메시지 브로커는 확인 응답을 사용한다. 클라이언트는 메시지 처리가 끝났을 때 브로커가 메시지를 큐에서 제거할 수 있게 브로커에게 명시적으로 알려야 한다.
  • 브로커가 확인 응답을 받기 전에 클라이언트로의 연결이 닫히거나 타임아웃되면 브로커는 메시지가 처리되지 않았다고 가정하고 다른 소비자에게 다시 전송한다.
  • 부하 균형 분산과 메시지 재전송을 조합하면 필연적으로 메시지 순서가 변경된다.

파티셔닝된 로그

데이터베이스의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간이 짧은 알림 기능을 조합한 것이 로그 기반 메시지 브로커의 기본 아이디어다.

로그를 사용한 메시지 저장소

  • 로그는 단순히 디스크에 저장된 추가 전용 레코드의 연속이다. 브로커를 구현할 때도 같은 구조를 사용한다. 생산자가 보낸 메시지는 로그 끝에 추가하고 소비자는 로그를 순차적으로 읽어 메시지를 받는다.
  • 디스크 하나를 쓸 때보다 처리량을 높이기 위해 로그를 파티셔닝할 수 있다. 그러면 각 파티션은 다른 파티션과 독립적으로 읽고 쓰기가 가능한 분리된 로그가 된다. 토픽은 같은 형식의 메시지를 전달하는 파티션들의 그룹으로 정의한다.
  • 각 파티션 내에서 브로커는 모든 메시지에 오프셋이라는 단조 증가하는 순번을 부여한다. 파티션이 추가 전용이고 파티션 내 전체 메시지는 전체 순서가 있으므로 순번을 부여하는 것은 타당하다. 단 다른 파티션 간 메시지의 순서는 보장하지 않는다.
  • 카프카, 키네시스 스트림, 트위터의 분산 로그가 이런 방식으로 동작하는 로그 기반 메시지 브로커다.
  • 이런 메시지 브로커는 모든 메시지를 디스크에 저장하지만 여러 장비에 파티셔닝해 초당 수백만 개의 메시지를 처리할 수 있고 메시지를 복제함으로써 장애에 대비할 수 있다.

로그 방식의 전통적인 메시징 방식의 비교

  • 로그 기반 접근법은 팬 아웃 메시징 방식을 제공한다. 각 클라이언트는 할당된 파티션의 메시지를 모두 소비한다.
  • 메시지를 처리하는 비용이 비싸고 메시지 단위로 병렬화 처리하고 싶지만 메시지 순서는 그렇게 중요하지 않다면 JMS/AMQP 방식의 메시지 브로커가 적합하다. 반면 처리량이 많고 메시지를 처리하는 속도가 빠르지만 메시지 순서가 중요하면 로그 기반 접근법이 적합하다.

소비자 오프셋

  • 파티션 하나를 순서대로 처리하면 메시지를 어디까지 처리했는지 알기 쉽다. 소비자의 현재 오프셋보다 작은 오프셋을 가진 메시지는 이미 처리한 메시지고 소비자의 현재 오프셋보다 큰 오프셋을 가진 메시지는 아직 처리하지 않은 메시지이다.
  • 메시지 오프셋은 단일 리더 데이터베이스 복제에서 널리 쓰이는 로그 순차 번호와 유사하다. 메시지 브로커는 데이터베이스의 리더처럼 동작하고 소비자는 팔로워처럼 동작한다.

디스크 공간 사용

  • 로그는 크기가 제한된 버퍼로 구현하고 버퍼가 가득 차면 오래된 메시지 순서대로 버린다. 이런 버퍼를 원형 버퍼, 링 버퍼라고 한다.
  • 이런 시스템은 큐가 작을 때는 빠르지만 디스크에 기록하기 시작하면 매우 느려진다.

소비자가 생산자를 따라갈 수 없을 때

이럴 때는 메시지 버리기, 버퍼링, 배압 적용하기의 선택지가 있다.

로그 기반 접근법은 고정 크기의 버퍼를 사용하는 버퍼링 방식이다.

오래된 메시지 재생

  • 로그 기반 메시지 브로커는 메시지를 소비하는 게 읽기 전용 연산이기 때문에 파일을 읽는 작업과 유사하다.
  • 소비자의 출력을 제외하고, 메시지 처리의 유일한 부수 효과는 소비자 오프셋 이동이다.
  • 로그 기반 메시징과 일괄 처리는 변환 처리를 반복해도 입력 데이터에 영향을 주지 않고 파생 데이터를 만든다.

데이터베이스와 스트림

이종 데이터 시스템에서 발생하는 문제를 살펴보고, 이벤트 스트림의 아이디어를 적용해 문제를 해결하는 방법을 살펴보자.

시스템 동기화 유지하기

  • 주기적으로 데이터베이스 전체를 덤프하는 작업이 너무 느리면 대안으로 이중 기록을 사용할 수 있다. 이를 사용하면 데이터가 변할 때마다 애플리케이션 코드에서 명시적으로 각 시스템에 기록한다. ex. 데이터베이스에 기록하고 검색 색인을 갱신한다. 그리고 캐시를 무효화한다.
  • 이중 기록의 문제점
    • 경쟁 조건
    • 한쪽 쓰기가 성공할 때 다른 쪽 쓰기는 실패할 수 있다는 점

변경 데이터 캡처

변경 데이터 캡처(CDC)는 데이터베이스에 기록하는 모든 데이터의 변화를 관찰해 다른 시스템으로 데이터를 복제할 수 있는 형태로 추출하는 과정이다. 데이터가 기록되자마자 변경 내용을 스트림으로 제공할 수 있으면 유용하다.

변경 데이터 캡처의 구현

  • 변경 데이터 캡처는 파생 데이터 시스템이 레코드 시스템의 정확한 데이터 복제본을 가지게 하기 위해 레코드 시스템에 발생하는 모든 변경 사항을 파생 데이터 시스템에 반영하는 것을 보장하는 메커니즘이다.
  • 변경 데이터 캡처는 본질적으로 변경 사항을 캡처할 데이터베이스 하나를 리더로 하고 나머지를 팔로워로 한다.
  • 변경 데이터 캡처는 메시지 브로커와 동일하게 비동기 방식으로 동작한다.

초기 스냅숏

전체 데이터가 필요할 때, 전체 로그 히스토리가 없다면 일관성 있는 스냅숏을 사용해야 한다. 데이터베이스 스냅숏은 변경 로그의 위치나 오프셋에 대응돼야 한다.

로그 컴팩션

  • 로그 컴팩션 원리 저장 엔진은 주기적으로 같은 키의 로그 레코드를 찾아 중복을 제거하고 각 키에 대해 가장 최근에 갱신된 내용만 유지한다. 컴팩션과 병합은 백그라운드로 실행된다.
  • 로그 구조화 저장 엔진에서 특별한 널 값(툼스톤)으로 갱신하는 것은 키의 삭제를 의미하고 로그 컴팩션을 수행할 때 실제로 값을 제거한다.
  • 이 방식을 사용하면 로그에 데이터베이스에 있는 모든 키의 최신 값이 존재하는 것이 보장된다. 즉, CDC 원본 데이터베이스의 스냅숏을 만들지 않고도 데이터베이스 콘텐츠 전체의 복사본을 얻을 수 있다.

변경 스트림용 API 지원

최근 데이터베이스들은 기능 개선이나 리버스 엔지니어링을 통해 CDC 지원을 하기보다 점진적으로 변경 스트림을 기본 인터페이스로 지원하기 시작했다.

이벤트 소싱

  • 이벤트 소싱은 변경 데이터 캡처와 유사하게 애플리케이션 상태 변화를 모두 변경 이벤트 로그로 저장한다. 변경 데이터 캡처와 가장 큰 차이점은 이 아이디어를 적용하는 추상화 레벨이 다르다는 점이다.
    • 변경 데이터 캡처에서 애플리케이션은 데이터베이스를 변경 가능한 방식으로 사용해 레코드를 자유롭게 갱신하고 삭제한다.
    • 이벤트 소싱에서 애플리케이션 로직은 이벤트 로그에 기록된 불변 이벤트를 기반으로 명시적으로 구축한다. 이벤트 저장은 추가만 가능하고 불변이다. 이벤트는 저수준에서 상태 변경을 반영하는 것이 아니라 애플리케이션 수준에서 발생한 일을 반영하게끔 설계됐다.
  • 새로운 기능을 추가하더라도 이벤트 소싱 접근법을 사용하면 새로 발생한 부수 효과를 기존 이벤트에서 쉽게 분리할 수 있다.

이벤트 로그에서 현재 상태 파생하기

  • 이벤트 로그는 그 자체로는 그렇게 유용하지 않다. 사용자는 일반적으로 시스템의 현재 상태를 보기 원하지 수정 히스토리를 원하지 않기 때문이다. 따라서 이벤트 소싱을 사용하는 애플리케이션은 시스템에 기록한 데이터를 표현한 이벤트 로그를 가져와 사용자에게 보여주기에 적당한 애플리케이션 상태로 변환해야 한다. 변환 과정은 결정적 과정이어야 한다. (다시 수행해도 같은 상태를 만들 수 있어야 한다)
  • 이벤트 소싱 시스템에는 모든 원시 이벤트를 영원히 저장하고 필요할 때마다 모든 이벤트를 재처리할 수 있어야 한다는 의도가 있다.

명령과 이벤트

  • 이벤트 소싱 철학은 이벤트와 명령(command)를 구분하는 데 주의한다. 사용자 요청이 처음 도착했을 때 이 요청은 명령이다. 명령은 실패할 수 있다. (예를 들어 무결성 조건이 위반되면 실패한다.) 명령이 승인되면 명령은 지속성 있는 불변 이벤트가 된다.
  • 이벤트는 생성 시점에 사실이 되므로 이벤트 스트림 소비자는 이벤트를 거절하지 못한다. 이벤트가 이미 불변 로그이고 다른 소비자도 이미 받았을 것이기 때문이다. 따라서 명령의 유효성은 이벤트가 되기 전에 동기식으로 검증해야 한다.
  • 사용자 요청을 이벤트 두 개로 분할하면 비동기 처리로 유효성 검사를 할 수 있다. (가예약 이벤트, 예약 확정 이벤트)

상태와 스트림 그리고 불변성

  • 상태가 변할 때마다 해당 상태는 시간이 흐름에 따라 변한 이벤트의 마지막 결과다.
  • 모든 변경 로그는 시간이 지남에 따라 바뀌는 상태를 나타낸다. 변경 로그를 지속성 있게 저장한다면 상태를 간단히 재생성할 수 있다.

불변 이벤트의 장점

  • 회계에서는 거래 정보를 원장에 추가만 하지, 수정이나 삭제는 하지 않는다. 이렇게 추가만 하는 불변 이벤트 로그를 쓴다면 문제 상황의 진단과 복구가 훨씬 쉽다.
  • 불변 이벤트는 현재 상태보다 훨씬 많은 정보를 포함한다. ex. 사용자가 특정 제품을 장바구니에 넣었다가 삭제했다는 정보 등

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

  • 불변 이벤트 로그에서 가변 상태를 분리하면 동일한 이벤트 로그로 다른 여러 읽기 전용 뷰를 만들 수 있다.
  • 기존 데이터를 새로운 방식으로 표현하는 새 기능을 추가하려면 이벤트 로그를 사용해 신규 기능용으로 분리한 읽기 최적화된 뷰를 구축할 수 있다.
  • 데이터를 쓰는 형식과 읽는 형식을 분리해 다양한 읽기 뷰를 허용한다면 상당한 유연성을 얻을 수 있다. 이 개념을 명령과 질의 책임의 분리(command query responsibility segregation, CQRS)라 부른다.
  • 읽기 최적화된 뷰는 데이터를 비정규화하는 것이 전적으로 합리적이다. 변환 프로세스가 뷰와 이벤트 로그 사이의 일관성을 유지하는 메커니즘을 제공하기 때문이다.

동시성 제어

  • 이벤트 소싱과 변경 데이터 캡처의 가장 큰 단점은 이벤트 로그의 소비가 대개 비동기로 이뤄진다는 점이다. 그래서 사용자가 로그에 이벤트를 기록하고 이어서 로그에서 파생된 뷰를 읽어도 기록한 이벤트가 아직 읽기 뷰에 반영되지 않았을 가능성이 있다.
  • 해결책
    • 동기식으로 수행하기
    • 이벤트 로그로 현재 상태를 만들기 이벤트를 로그에 추가만 하면 되며 원자적으로 만들기 쉽다.
    • 이벤트 로그와 애플리케이션 상태를 같은 방식으로 파티셔닝하면 간단한 단일 스레드 로그 소비자는 쓰기용 동시성 제어는 필요하지 않다. 파티션 내에서 이벤트의 직렬 순서를 정의하면 로그에서 동시성의 비결정성을 제거할 수 있다.

불변성의 한계

불변을 유지하는 것은 데이터셋이 뒤틀리는 양에 따라 다르다.

데이터를 진짜로 삭제하는 작업은 매우 어렵다.

스트림 처리

  • 스트림을 처리하는 방법
    • 이벤트에서 데이터를 꺼내 데이터베이스나 캐시, 검색 색인 또는 유사한 저장소 시스템에 기록하고 다른 클라이언트가 이 시스템에 해당 데이터를 질의한다.
    • 이벤트를 사용자에 직접 보낸다.
    • 하나 이상의 입력 스트림을 처리해 하나 이상의 출력 스트림을 생산한다.
  • 스트림을 처리하는 코드 조각을 연산자(operator)나 작업(job)이라고 한다.
  • 일괄 처리 작업과 가장 큰 차이점은 스트림은 끝나지 않는다는 점이다.

스트림 처리의 사용

스트림 처리는 특정 상황이 발생하면 조직에 경고를 해주는 모니터링 목적으로 오랜 기간 사용돼 왔다. 최근에는 다른 용도가 등장했다.

복잡한 이벤트 처리 (complex event processing, CEP)

  • 질의를 처리 엔진에 제출하고 스트림을 유지한다. 해당 매치를 발견하면 복잡한 이벤트를 방출한다.
  • CEP에서 질의와 데이터의 관계는 일반적 데이터베이스와 반대다. 질의가 오랜 기간 저장되고 들어오는 입력 스트림으로부터 들어오는 이벤트가 질의를 지나 흘러가면서 매칭되는 질의를 찾는다.

스트림 분석

분석은 연속한 특정 이벤트 패턴을 찾는 것보다 대량의 이벤트를 집계하고 통계적 지표를 뽑는 것을 더 우선한다.

구체화 뷰 유지하기

이벤트 소싱에서 애플리케이션 상태는 일종의 구체화 뷰다. 구체화 뷰를 만들려면 잠재적으로 임의의 시간 범위에 발생한 모든 이벤트가 필요하다.

스트림 상에서 검색하기

복수 이벤트로 구성된 패턴을 찾는 CEP 외에도 전문 검색 질의와 같은 복잡한 기준을 기반으로 개별 이벤트를 검색해야 하는 경우도 있다.

메시지 전달과 RPC

유사 RPC 시스템과 스트림 처리 사이에는 겹치는 영역이 있다.

시간에 관한 추론

일괄 처리에서 태스크는 과거에 쌓인 대량의 이벤트를 빠르게 처리한다. 프로세스를 수행하는 시간과 이벤트가 실제로 발생한 시간과는 아무 관계가 없다. 스트림 처리 프레임워크는 이벤트에 내장된 타임스탬프를 보는 것도 있고, 장비의 시스템 시계(처리 시간)를 보는 것도 있다.

이벤트 시간 대 처리 시간

  • 처리가 지연되는 데에는 많은 이유가 있다. 메시지가 지연되면 메시지 순서를 예측하지 못할 수도 있다.
  • 이벤트 시간과 처리 시간을 혼동하면 좋지 않은 데이터가 만들어진다.

준비 여부 인식

  • 이벤트 시간 기준으로 윈도우를 정의할 때 발생하는 까다로운 문제는 특정 윈도우에서 모든 이벤트가 도착했다거나 아직도 이벤트가 계속 들어오고 있는지를 확신할 수 없다는 점이다.
  • 윈도우를 이미 종료한 후에 도착한 낙오자 이벤트를 처리할 방법이 필요하다.
    • 낙오자를 처리하는 방법
      • 낙오자 이벤트 무시
      • 수정값 발행 (수정값은 낙오자 이벤트가 포함된 윈도우를 기준으로 갱신된 값)

어쨌든 어떤 시계를 사용할 것인가?

  • 이벤트가 시스템의 여러 지점에 버퍼링됐을 때 이벤트에 타임스탬프를 할당하는 것은 더 어렵다. 예를 들어, 오프라인일 때는 장치 로컬에 버퍼링하다가 인터넷 연결이 가능해지면 이벤트를 서버로 보낸다던지
  • 잘못된 장치 시계를 조정하는 한 가지 방법은 세 가지 타임스탬프를 로그로 남기는 것이다.
    1. 이벤트가 발생한 시간 (장치 시계)
    2. 이벤트를 서버로 보낸 시간 (장치 시계)
    3. 서버에서 이벤트를 받은 시간 (서버 시계)

윈도우 유형

  • 이벤트 타임스탬프를 어떻게 결정할지 안다면 다음 단계는 윈도우 기간을 어떻게 정의해야 하는지 결정하는 일이다.
  • 일반적으로 사용하는 윈도우 유형
    • 텀블링 윈도우 고정 길이를 사용한다. 모든 이벤트는 정확히 한 윈도우에 속한다.
    • 홉핑 윈도우 고정 길이를 사용한다. 결과를 매끄럽게 만들기 위해 윈도우를 중첩할 수 있다.
    • 슬라이딩 윈도우 각 시간 간격 사이에서 발생한 모든 이벤트를 포함한다.
    • 세션 윈도우 고정된 기간이 없다. 대신 같은 사용자가 짧은 시간동안 발생시킨 모든 이벤트를 그룹화해서 세션 윈도우를 정의한다.

스트림 조인

  • 스트림 상에서 새로운 이벤트가 언제든 나타날 수 있다는 사실은 스트림 상에서 수행하는 조인을 일괄 처리 작업에서 수행하는 조인보다 훨씬 어렵게 만든다.
  • 스트림 조인의 유형을 스트림 스트림 조인, 스트림 테이블 조인, 테이블 테이블 조인으로 구분한다. 각각에 대한 설명이 책에 있는데 아직 이해가 잘 안간다.. 다음에 또 읽어야지

내결함성

스트림 처리자가 결함에 견디는 방법을 살펴보자.

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

  • 마이크로 일괄 처리 (microbatching) 스트림을 작은 블록으로 나누고 각 블록을 소형 일괄 처리와 같이 다루는 방법이다. 스파크 스트리밍에서 사용한다. 일괄 처리 크기가 작을수록 스케줄링과 코디네이션 비용이 커지고, 일괄 처리가 클수록 스트림 처리의 결과를 보기까지 지연시간이 길어진다.
  • 체크포인트 아파치 플링크가 사용한다. 주기적으로 상태의 롤링 체크포인트를 생성하고 지속성 있는 저장소에 저장한다. 스트림 연산자에 장애가 발생하면 스트림 연산자는 가장 최근 체크포인트에서 재시작하고 해당 체크포인트와 장애 발생 사이의 출력은 버린다.

원자적 커밋 재검토

  • 장애가 발생했을 때 정확히 한 번 처리되는 것처럼 보이려면 처리가 성공했을 때만 모든 출력과 이벤트 처리의 부수 효과가 발생하게 해야 한다.
  • 이런 효과는 원자적으로 모두 일어나거나 또는 모두 일어나지 않아야 하지만 서로 동기화가 깨지면 안된다.

멱등성

  • 멱등 연산은 여러 번 수행하더라도 오직 한 번 수행한 것과 같은 효과를 내는 연산이다.
  • 연산 자체가 멱등적이지 않아도 여분 메타데이터로 연산을 멱등적으로 만들 수 있다. 카프카로부터 메시지를 소비할 때 모든 메시지에는 영속적이고 단조 증가하는 오프셋이 있다. 외부 데이터베이스에 값을 기록할 때 마지막으로 그 값을 기록하라고 트리거한 메시지의 오프셋을 함께 포함한다면 이미 갱신이 적용됐는지 확인할 수 있어 반복해서 같은 갱신이 수행되는 것을 막을 수 있다.

실패 후에 상태 재구축하기

  • 상태가 필요한 스트림 처리는 실패 후에도 해당 상태가 복구됨을 보장해야 한다.
  • 방법
    • 원격 데이터 저장소에 상태를 유지하고 복제하는 방법

    • 스트림 처리자의 로컬에 상태를 유지하고 주기적으로 복제하는 방법

      이 모든 트레이드오프는 기반 인프라의 성능 특성에 달려있다.

0개의 댓글