////
Search

11장 - 스트림 처리

Date
2023/02/05 13:11
Tags

1. 이벤트 스트림 전송

스트림 처리 문맥에서 레코드는 보통 이벤트라고 하지만 특정 시점에 일어난 사건에 대한 세부 사항을 포함하는, 작고 독립된 불변 객체라는 점에서 본질적으로 동일하다.
일괄 처리에서 한 번 기록하면 여러 작업에서 읽을 수 있습니다. 스트리밍에서도 이와 비슷
생산자(producer)가 이벤트를 한 번 만들면 해당 이벤트를 복수의 소비자가 처리가능
파일 시스템에서는 관련 레코드 집합을 파일 이름으로 식별하지만 스트림 시스템에서는 대개 토픽(topic) 이나 스트림으로 관련 이벤트를 묶는다.
이론상으로는 파일이나 데이터베이스가 있으면 생산자와 소비자를 연결하기는 충분하며지만, 지연 시간이 낮으면서 지속해서 처리하는 방식을 지향할 때 데이터스토어를 이런 용도에 맞게 설계하지 않았다면 폴링 방식은 비용이 크다.
데이터베이스는 전통적으로 알림 메커니즘을 강력하게 지원하지 않는다.
관계형 데이터베이스에서는 보통 트리거(trigger) 기능이 있음
그러나 트리거는 기능이 제한적이고 데이터베이스를 설계한 이후에 도입된 개념
이벤트 알림 전달 목적으로 개발된 도구는 따로 있음

2. 메시징 시스템

새로운 이벤트에 대해 소비자에게 알려주려고 쓰이는 일반적인 방법은 메시징 시스템(messaging system) 을 사용하는 것입니다.
생산자는 이벤트를 포함한 메시지를 전송하고, 메시지는 소비자에게 전달된다.
메시징 시스템을 구축하는 가장 간단한 방법은 생산자와 소비자 사이에 유닉스 파이프나 TCP 연결과 같은 직접 통신 채널을 사용하는 방법이다.
발행/구독(publish/subscribe) 모델에서는 여러 시스템들이 다양한 접근법을 사용합니다. 아래 두 질문이 이 시스템을 구별하는데 상당히 도움이 된다.
생산자가 소비자가 메시지를 처리하는 속도보다 빠르게 메시지를 전송한다면 어떻게 될지?
세가지 선택지인 메시지를 버리거나, 큐에 메시지를 버퍼링하거나, 생산자를 적용한다.
메시지의 유실을 허용할지 말지는 애플리케이션에 따라 상당히 다르다.

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

많은 메시지 시스템은 중간 노드를 통하지 않고 생산자와 소비자를 네트워크로 직접 통신함
UDP 멀티캐스트는 낮은 지연이 필수인 주식 시장과 같은 금융 산업에서 널리 사용됨
ZeroMQ 같은 브로커가 필요없는 메시징 라이브러리는 TCP 또는 IP 멀티캐스팅 상에서 발행/구독 메시징을 구현함
StatsD과 BruBeck은 네트워크 상의 모든 장비로부터 지표를 수집하고 모니터링하고 UDP 메시징을 사용함
소비자가 네트워크에 서비스를 노출하면 생산자는 HTTP나 RPC 요청을 직접 보낼 수 있음
직접 메시징 시스템은 설계 상호아에서는 잘동작하지만 일반적으로 메시지가 유실될 수 있는 가능성을 고려해서 애플리케이션 코드를 작성해야 함
즉, 직접 메시징 시스템은 일반적으로 생산자와 소비자가 항상 온라인 상태라고 가정함
소비자가 오프라인이라면 메시지를 전달하지 못하는 상태에 있는 동안 전송된 메시지는 잃어버릴 수 있음

2.2. 메시지 브로커

직접 메시징 시스템의 대안으로 널리 사용되는 방법은 메시지 브로커(메시지 큐) 를 통해 메시지를 보내는 것입니다.
메시지 브로커는 근본적으로 메시지 스트림를 처리하는 데 최적화된 데이터베이스의 일종입니다.
브로커에 데이터가 모이기 때문에 이 시스템은 클라이언트의 상태 변경(접속, 접속 해제, 장애)에 쉽게 대처할 수 있음
지속성 문제가 생산자와 소비자에서 브로커로 옮겨갔기 때문
큐 대기를 하면 소비자는 일반적으로 비동기로 동작함
생산자가 메시지를 보낼 때 생산자는 브로커가 해당 메시지를 버퍼에 넣었는지만 확인하고 소비자가 메시지를 처리하기까지 기다리지 않음
메시지를 소비자로 배달하는 것은 정해지지 않은 미래 시점이지만 때로는 큐에 백로그가 있다면 상당히 늦을 수 있음

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

어떤 메시지 브로커는 XA 또는 JTA를 이용해 2단계 커밋을 수행하기도 합니다. 메시지 브로커와 데이터베이스에는 중요한 실용적 차이가 있지만 이 특징은 데이터베이스의 속성과 상당히 비슷
데이터베이스는 명시적으로 데이터 삭제될 때까지 데이터를 보관합니다. 반면 메시지 브로커 대부분은 소비자에게 데이터 배달이 성공할 경우 자동으로 메시지를 삭제함
메시지 브로커는 대부분 메시지를 빨리 지우기 때문에 작업 집합이 상당히 작다고 가정합니다. 즉 큐 크기가 작음
데이터베이스는 보조 색인을 지원하고 데이터 검색을 위한 다양한 방법을 지원하는 반면 메시지 브로커는 특정 패턴과 부합하는 토픽의 부분 집합을 구독하는 방식을 지원함
데이터베이스에 질의할 때 그 결과는 일반적으로 질의 시점의 데이터 스냅숏을 기준으로 함

2.4. 복수 소비자

두 가지 패턴은 함께 사용 가능합니다.

2.4.1. 로드 밸런싱

각 메시지는 소비자 중 하나로 전달됩니다. 따라서 소비자들은 해당 토픽의 메시지를 처리하는 작업을 공유함
브로커는 메시지를 전달할 소비자를 임의로 지정함

2.4.2. 팬 아웃

각 메시지는 모든 소비자에게 전달됨

2.5. 확인 응답과 재전송

소비자들은 언제라도 장애가 발생할 수 있기 때문에 메시지를 잃어버리지 않기 위해 메시지 브로커는 확인 응답을 사용한다.
브로커가 확인 응답을 받기 전에 클라이언트로의 연결이 닫히거나 타임아웃되면 브로커는 메시지가 처리되지 않았다고 가정하고 다른 소비자에게 다시 전송한다.
부하 균형 분산과 결합할 때 이런 재전송 행위는 메시지 순서에 영향을 미치게 된다.
메시지 브로커는 JMS와 AMQP 표준에서 요구하는 대로 메시지 순서를 유지하려 노력할지라도 부하 균형 분산과 메시지 재전송을 조합하면 필연적으로 메시지 순서가 변경된다.
그러나, 부하 균형 분산 기능을 사용하지 않는다면 이 문제를 피할 수 있다.

3. 파티셔닝된 로그

네트워크 상에서 패킷을 전송하거나 네트워크 서비스에 요청하는 작업은 보통 영구적 추적을 남기지 않는 일시적 연산이다.
메시지 브로커가 메시지를 디스크에 지속성 있게 기록하더라도 메시지가 소비자에게 전달된 후 즉시 삭제한다.
데이터베이스의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간이 짧은 알림 기능을 조합한 것이 로그 기반 메시지 브로커(log-based message broker)다.

3.1. 로그를 사용한 메세지 저장소

로그는 단순히 디스크에 저장된 추가 전용 레코드의 연속이다.
브로커를 구현할 때도 생산자가 보낸 메시지는 로그 끝에 추가하고 소비자는 로그를 순차적으로 읽어 메시지를 받는다.
디스크 하나를 쓸 때보다 처리량을 높이기 위해 확장하는 방법으로 로그를 파티셔닝 하는 방법이 있다.
다른 파티션은 다른 장비에서 서비스할 수 있다.
각 파티션 내에서 브로커는 모든 메시지에 오프셋이라고 부르는, 단조 증가하는 순번을 부여한다.
파티션이 추가 전용이고 따라서 파티션 내 전체 메시지는 전체 순서가 있기 때문에 순번을 부여하는 것은 타당하다.
단 다른 파티션 간 메시지의 순서는 보장하지 않는다.

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

로그 기반 접근법은 당연히 팬 아웃 메시징 방식을 제공한다.
소비자가 서로 영향 없이 독립적으로 로그를 읽을 수 있고 메시지를 읽어도 로그에서 삭제되지 않기 때문이다.
개별 메시지를 소비자 클라이언트에게 할당하지 않고 소비자 그룹 간 로드밸런싱하기 위해 브로커는 소비자 그룹의 노드들에게 전체 파티션을 할당할 수 있다.
각 클라이언트는 할당된 파티션의 메시지를 모두 소비한다.
일반적으로 소비자에 로그 파티션이 할당되면 소비자는 단일 스레드로 파티션에서 순차적으로 메시지를 읽는다.
이러한 거친 방식의 로드밸런싱 방법은 몇 가지 불리한 면이 있다.
토픽 하나를 소비하는 작업을 공유하는 노드 수는 많아야 해당 토픽의 로그 파티션 수로 제한됩니다. 같은 파티션 내 메시지는 같은 노드로 전달되기 때문이다.
특정 메시지 처리가 느리면 파티션 내 후속 메시지 처리가 지연된다
즉, 메시지를 처리하는 비용이 비싸고 메시지 단위로 병렬화 처리하고 싶지만 메시지 순서는 그렇게 중요하지 않다면 JMS/AMQP 방식의 메시지 브로커가 적합하다.
반면 처리량이 많고 메시지를 처리하는 속도가 빠르지만 메시지 순서가 중요하다면 로그 기반 접근법이 효과적이다.

3.3. 소비자 오프셋

파티션 하나를 순서대로 처리하면 메시지를 어디까지 처리했는지는 알기 쉽다.
브로커는 모든 개별 메시지마다 보내는 확인 응답을 추적할 필요가 없다.
메시지 오프셋은 단일 리더 데이터베이스 복제에서 널리 쓰는 로그 순차 번호(log sequence number) 와 상당히 유사하다.
데이터베이스 복제에서 팔로워가 리더와 연결이 끊어졌다가 다시 접속할 때 로그 순차 번호를 사용한다.
소비자 노드에 장애가 발생하면 소비자 그룹 내 다른 노드에 장애가 발생한 소비자의 파티션을 할당하고 마지막 기록된 오프셋부터 메시지를 처리하기 시작한다.
장애가 발생한 소비자가 처리했지만 아직 오프셋을 기록하지 못한 메시지가 있다면 이 메시지는 두 번 처리되는 문제가 있다.

3.4. 디스크 공간 사용

로그를 계속 추가한다면 결국 디스크 공간을 전부 사용하게 됩니다.
디스크 공간을 재사용하기 위해 실제로는 로그를 여러 조각으로 나누고 가끔 오래된 조각을 삭제하거나 보관 저장소로 이동합니다.
소비자가 처리 속도가 느려 메시지가 생산되는 속도를 따라잡지 못하면 소비자가 너무 뒤처져 소비자 오프셋이 이미 삭제한 조각을 가리킬 수 도 있습니다.
즉 메시지 일부를 잃어버릴 가능성이 있습니다.
결과적으로 로그는 크기가 제한된 버퍼로 구현하고 버퍼가 가득차면 오래된 메시지 순서대로 버립니다.
이러한 버퍼를 원형 버퍼(circular buffer) 또는 링 버퍼(ring buffer) 라고 합니다.

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

소비자의 오프셋을 모니터링하면서 눈에 띄게 뒤쳐지는 경우에는 이를 경고를 한다.
운영자는 소비자 처리가 느린 문제를 고쳐 메시지를 잃기 전에 따라 잡을 시간을 충분히 벌 수 있다.
어떤 소비자가 너무 뒤처져서 메시지를 읽기 시작해도 해당 소비자만 영향을 받고 다른 소비자들의 서비스를 망치지는 않는다.

3.6. 오래된 메시지 재생

AMQP와 JMS 유형의 메시지 브로커에서 메시지를 처리하고 확인 응답하는 작업은 브로커에서 메시지를 제거하기 때문에 파괴적 연산
반면 로그 기반 메시지 브로커는 메시지를 소비하는 게 오히려 파일을 읽는 작업과 더 유사한데 로그를 변화시키지 않는 읽기 전용 연산
소비자의 출력을 제외한, 메시지 처리의 유일한 부수 효과는 소비자 오프셋 이동이다.
이는 필요하다면 쉽게 조작할 수 있다.
로그 기반 메시징과 일괄 처리는 변환 처리를 반복해도 입력 데이터에 영향을 전혀 주지 않고 파생 데이터를 만든다.
로그 기반 메시징 시스템은 많은 실험을 할 수 있고 오류와 버그를 복구하기 쉽기 때문에 조직 내에서 데이터플로를 통합하는데 좋은 도구다.

4. 데이터베이스와 스트림

이벤트는 특정 시점에 발생한 사건을 기록한 레코드다.
사건은 측정 판독일 수도 있지만 데이터베이스에 기록하는 것일 수 도 있다.
데이터베이스와 스트림 사이의 연결점이 단지 디스크에 로그를 저장하는 물리적 저장소 이상이다.
복제 로그는 데이터베이스 기록 이벤트의 스트림이다.
데이터베이스가 트랜잭션을 처리할 때 리더는 데이터베이스 기록 이벤트를 생산한다.

4.1. 시스템 동기화 유지하기

관련이 있거나 동일한 데이터가 여러 다른 장소에서 나타나기 때문에 서로 동기화가 필수다.
일반적으로 데이터 웨어하우스는 벌크 로드한다.
주기적으로 데이터베이스 전체를 덤프하는 작업이 너무 느리면 대안으로 사용하는 방법으로 이중 기록(dual write) 가 있다.
이중 기록을 사용하면 데이터가 변할 때마다 애플리케이션 코드에서 명시적으로 각 시스템에 기록한다.
이중 기록은 몇가지 심각한 문제가 있으며, 대표적인 예시로 경쟁 조건이 있으며, 다른 문제는 한쪽 쓰기가 성공할 때 다른 쪽 쓰기는 실패할 수 있다는 점이다.

4.2. 변경 데이터 캡처 (CDC)

최근 들어 변경 데이터 캡처(change data capture, CDC) 에 관심이 높아지고 있다.
변경 데이터 캡처는 데이터베이스에 기록하는 모든 데이터의 변화를 관찰해 다른 시스템으로 데이터블을 복제할 수 있는 형태로 추출화는 과정이다.
CDC는 데이터가 기록되자마자 변경내용을 스트림으로 제공할 수 있으면 특히 유용하다.

4.2.1. 변경 데이터 캡처의 구현

검색 색인과 데이터 웨어하우스에 저장된 데이터는 레코드 시스템에 저장된 데이터의 또 다른 뷰일 뿐이므로 로그 소비자를 파생 데이터 시스템이라 할 수 있다.
변경 데이터 캡처는 본질적으로 변경 사항을 캡처할 데이터베이스 하나를 리더로 하고 나머지를 팔로워로 한다.
로그 기반 메시지 브로커는메시지 순서를 유지하기 때문에 원본 데이터베이스에서 변경 이벤트를 전송하기에 적합하다.
변경 데이터 캡처를 구현하는데 데이터베이스 트리거를 사용하기도 한다.
데이터 테이블의 모든 변화를 관찰하는 트리거를 등록하고 변경 로그 테이블에 해당 항목을 추가하는 방식이다.
이 방식은 고장나기 쉽고 성능 오버헤드가 상당하다.
변경 데이터 캡처는 메시지 브로커와 동일하게 비동식 방식으로 동작합니다.
운영상 이점이 있는 설계로 느린 소비자가 추가되도 레코드 시스템에 미치는 영향이 적습니다. 그러나, 복제 지연의 모든 문제가 발생하는 단점이 있습니다.

4.3. 이벤트 소싱

이벤트 소싱은 도메인 주도 설계(domain-driven design, DDD) 커뮤니티에서 개발한 기법이다.
이벤트 소싱은 변경 데이터 캡처와 유사하게 애플리케이션 상태 변화를 모두 변경 이벤트 로그로 저장한다.
변경 데이터 캡처와 가장 큰 차이점은 이 아이디어를 적용하는 추상화 레벨이 다르다는 점
변경 데이터 캡처에서 애플리케이션은 데이터베이스를 변경 가능한 방식으로 사용해 레코드를 자유롭게 갱신하고 삭제한다.
이벤트 소싱에서 애플리케이션 로직은 이벤트 로그에 기록된 불변 이벤트를 기반으로 명시적으로 구축한다.
이벤트 소싱은 데이터 모델링에 쓸 수 있는 강력한 기법이다.
애플리케이션 관점에서 사용자의 행동을 불변 이벤트로 기록하는 방식은 변경 가능한 데이터베이스 상에서 사용자의 행동에 따른 효과를 기록하는 방식보다 훨씬 유의미하다.
이벤트 소싱을 사용하면 애플리케이션을 지속해서 개선하기가 매우 유리합니다. 어떤 상황이 발생한 후 상황 파악이 쉽기 때문에 디버깅에 도움이 되고 애플리케이션 버그를 방지한다.

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

이벤트 로그 그 자체로는 그렇게 유용하지 않습니다. (현재 상태가 중요하지 수정 히스토리는 중요하지 않습니다.)
이벤트 소싱을 사용하는 애플리케이션은 시스템에 기록한 데이터를 표현한 이벤트 로그를 가져와 사용자에게 보여주기에 적당한 애플리케이션 상태로 변환해야 한다.
변경 데이터 캡처와 마찬가지로 이벤트 로그를 재현하면 현재 시스템 상태를 재구성할 수 있다.
이벤트 소싱을 사용하는 애플리케이션은 일반적으로 이벤트 로그에서 파생된 현재 상태의 스냅숏을 저장하는 메커니즘이 있기 때문에 전체 로그를 반복해서 재처리할 필요는 없다.

4.5. 명령과 이벤트

이벤트 소싱 철학은 이벤트와 명령(command) 을 구분하는 데 주의한다.
사용자 요청이 처음 도착했을 때는 요청은 명령으로 이 시점의 명령이 실패할 수도 있다.
애플리케이션은 먼저 명령이 실행한지 확인하고 무결성이 검증되고 명령이 승인되면 명령은 지속성 있는 불변 이벤트가 된다.
이벤트는 생성 시점에 사실(fact)가 된다.
변경 및 취소가 되었더라도 기존 정보는 여전히 사실로 남아 있으며, 변경 및 취소는 나중에 추가된 독립적인 이벤트가 된다.
이벤트 스트림 소비자는 이벤트를 거절하지 못한다.
소비자가 이벤트를 받은 시점에서는 이벤트는 이미 불변 로그의 일부분이며 다 소비자도 이미 받았을 것이다.
따라서 명령의 유효성은 이벤트가 되기 전에 동기식으로 검증해야 한다.

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

상태가 어떻게 바뀌었든 항상 이러한 변화를 일으킨 일련의 이벤트가 있다.
사건이 발생했다가 취소되더라도 발생했다는 점은 엄연한 사실이다.
모든 변경 로그(changelog) 는 시간이 지남에 따라 바뀌는 상태를 나타낸다.
변경 로그를 지속성 있게 저장한다면 상태를 간단히 재생성할 수 있는 효과가 있다.
이벤트 로그를 레코드 시스템으로 생각하고 모든 변경 가능 상태를 이벤트 로그로부터 파생된 것으로 생각하면 시스템을 거치는 데이터 흐름에 관해 추론하기가 쉽다.

5.1. 불변 이벤트의 장점

데이터베이스에 잘못된 데이터를 기록했을 때 코드가 데이터를 덮어썻다면 복구하기가 매우 어렵습니다. 추가만 하는 불변 이벤트 로그를 썼다면 문제 상황의 진단과 복구가 훨씬 쉽다.
불변 이벤트는 현재 상태보다 훨씬 많은 정보를 포함한다.
예시로 장바구니에 항목을 넣었다가 제거한 경우, 현재 정보에는 없지만 분석가에게는 고객이 특정 항목을 구매하려 했다가 하지 않았다는 것을 알 수 있는 유용한 정보다.

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

불변 이벤트 로그에서 가변 상태를 분리하면 동일한 이벤트 로그로 다른 여러 읽기 전용 뷰를 만들 수 있다.
이는 한 스트림이 여러 소비자를 가질 때와 동일한 방식으로 작동한다.
이벤트 로그에서 데이터베이스로 변환하는 명시적인 단계가 있으면 시간이 흐름에 따라 애플리케이션을 반전시키기 쉽다.
일반적으로 데이터에 어떻게 질의하고 접근하는지 신경 쓰지 않는다면 데이터 저장은 상당히 직관적인 작업이다.
데이터를 쓰는 형식과 읽는 형식을 분리해 다양한 읽기 뷰를 허용한다면 상당한 유연성을 얻을 수 있다.
위 개념을 명령과 질의 책임의 분리(command query responsibility segregation, CQRS) 라 부른다.
데이터베이스와 스키마 설계의 전통적인 접근법은 데이터가 질의를 받게 될 형식과 같은 형식으로 데이터를 기록해야 한다는 잘못된 생각에 기초한다.
읽기 최적화된 뷰는 데이터를 비정규화하는 것이 전적으로 합리적이다.

5.3. 동시성 제어

이벤트 소싱과 변경 데이터 캡처의 가장 큰 단점은 이벤트 로그의 소비가 대개 비동기로 이뤄진다는 점이다.
사용자가 로그에 이벤트를 기록하고 이어서 로그에서 파생된 뷰를 읽어도 기록한 이벤트가 아직 읽기 뷰에 반영되지 않았을 가능성이 있다.
해결책 중 하나는 읽기 뷰의 갱신과 로그에 이벤트를 추가하는 작업을 동기적으로 수행하는 방법이다.
이벤트 로그로 현재 상태를 만들면 동시성 제어 측면이 단순해진다.
다중 객체 트랜잭션은 단일 사용자 동작이 여러 다른 장소의 데이터를 변경해야 할 때 필요하다.
이벤트를 로그에 추가하기만 하면 되며 원자적으로 만들기쉽다.

5.4. 불변성의 한계

이벤트 소스 모델을 사용하지 않는 많은 시스템에서도 불변성에 의존한다.
영구적으로 모든 변화의 불변 히스토리를 유지하는 것은 데이터셋이 뒤틀리는 양에 따라 다르다.
대부분 데이터를 추가하는 작업이고 갱신이나 삭제는 드물게 발생하는 작업부하는 불변으로 만들기 쉽다.
반대로 빈번한 갱신과 삭제를 하는 작업부하는 불변 히스토리가 감당하기 힘들 정도로 커지거나 파편화 문제가 발생할 수도 있다.
성능적인 이유 외에도 데이터가 모두 불변성임에도 관리상의 이유로 데이터를 삭제할 필요가 있는 상황일 수 있다.
히스토리를 새로 쓰고 문제가 되는 데이터를 처음부터 기록하지 않았단 것 처럼 희망하는 경우가 있다.
데이토믹(Datomic)은 이 기능을 적출(exicision) 이라 부르고 포씰 버전 관리 시스템에서는 셔닝(shunning) 이라는 비슷한 개념이다.
데이터를 진짜로 삭제하는 작업은 놀라울 정도로 어려우며, 많은 곳에 복제본이 남아있기 때문이다.

6. 스트림 처리

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

6.1. 스트림 처리의 사용

6.1.1. 복잡한 이벤트 처리

복잡한 이벤트 처리(complex event processing, CEP) 는 이벤트 스트림 분석용으로 개발된 방법이다.
CEP는 특정 이벤트 패턴을 검색해야 하는 애플리케이션에 특히 적합하다.
CEP 시스템은 감지할 이벤트 패턴을 설명하는데 종종 SQL 같은 고수준 선언형 질의 언어나 그래픽 사용자 인터페이스를 사용하기도 한다.
질읜느 처리 엔진에 제출하고 처리 엔진은 입력 스트림을 소비해 필요한 매칭을 수행하는 상태 기계를 내부적으로 유지합니다. 해당 매치를 발견하면 엔진은 복잡한 이벤트(complex event)를 방출한다.
이러한 시스템에서 질의와 데이터의 관계는 일반적 데이터베이스와 비교했을 때 반대다.
질의는 오랜 기간 저장되고 입력 스트림으로부터 들어오는 이벤트는 지속적으로 질의를 지나 흘러가면서 이벤트 패턴에 매칭되는 질의를 찾는다.

6.1.2. 스트림 분석

스트림 처리를 사용하는 다른 영역으로 스트림 분석(analytics) 가 있다.
분석은 연속한 특정 이벤트 패턴보다는 대량의 이벤트를 집계하고 통계적 지표를 뽑는 것을 더 우선한다.
특정 유형의 이벤트 빈도 측정(시간당 얼마나 자주 발생하는지)
특정 기간에 걸친 값의 이동 평균(rolling average) 계산
이전 시간 간격과 현재 통계값의 비교(추세를 감지하거나 지난 주 대비 비정상적으로 높거나 낮은 지표에 대한 경고)
위의 통계들은 고정된 시간 간격 기준으로 계산하며 집계 시간 간격을 윈도우(window) 라고 한다.
스트림 분석 시스템은 때로 확률적 알고리즘을 사용하기도 한다.
대표적인 분석 용도로 설계된 분산 스트림으로 아파치 스톰, 스파크 스트리밍, 플링크, 콩코드, 쌈자, 카프카 스트림 등이 있습니다. 호스팅 서비스로는 구글 클라우드 데이터플로, 애저 스트림 분석이 있다.

6.1.3. 구체화 뷰 유지하기

데이터베이스 변경에 대한 스트림은 캐시, 검색 색인, 데이터 웨어하우스 같은 파생 데이터 시스템이 원본 데이터베이스의 최신 내용을 따라잡게 하는데 쓸 수 있다.
이런 예들은 구체화 뷰를 유지하는 특별한 사례로 볼 수 있다.
데이터셋에 대한 또 다른 뷰를 만들어 효율적으로 질의할 수 있게하고 기반이 되는 데이터가 변경될 때마다 뷰를 갱신한다.
이벤트 소싱에서 애플리케이션 상태는 이벤트 로그를 적용함으로써 유지된다.
구체화 뷰를 만들려면 잠재적으로 임의의 시간 범위에 발생한 모든 이벤트가 필요하다.
대부분 제한된 기간의 윈도우에서 동작하는 일부 분석 지향 프레임워의 가정과 이벤트를 영원히 유지해야 할 필요성은 서로 상반되지만 이론상으로는 어떤 스트림 처리자라도 구체화 뷰를 유지하는 데 사용할 수 있다.

6.1.4. 스트림 상에서 검색하기

복수 이벤트로 구성된 패턴을 찾는 CEP외에도 전문 검색 질의와 같은 복잡한 기준을 기반으로 개별 이벤트를 검색해야 하는 경우도 있다.
대표적인 예시로 엘라스틱서치의 여과 기능이 있다.
스트림 검색은 질의를 먼저 저장하고, CEP와 같이 문서는 질의를 지나가면서 실행된다.
모든 질의에 대해 모든 문서를 테스트할 수 있습니다. 다만 많은 질의가 있다면 느려질 것이므로 이에 대한 최적화를 진행할 수도 있다.

6.2. 시간에 관한 추론

스트림 처리자는 종종 시간을 다뤄야할 때가 있으나 이 개념은 까다롭다.
일괄 처리에서 태스크는 과거에 쌓인 대량의 이벤트를 빠르게 처리합니다. 그러나 프로세스를 숫행하는 시간과 이벤트가 실제로 발생한 시간과는 아무 관계가 없기 때문이다.
일괄 처리는 몇 분 안에 과거 이벤트 1년 치를 읽어야 할 수도 있으며 대부분의 경우는 이부분이 더 중요한 정보다.
그러나 많은 스트림 처리 프레임워크는 윈도우 시간을 결정할 때 처리하는 장비의 시스템 시계(처리 시간)을 이용합니다. 이 접근법은 간단하다는 장점이 있다.
이벤트 생성과 처리 사이의 간격이 무시할 정도로 작다면 합리적이나, 처리가 지연되면 문제가 많이 생긴다.

6.2.1. 이벤트 시간 대 처리 시간

처리가 지연되는 데는 많은 이유가 있다.
큐 대기, 네트워크 결함, 메시지 브로커나 처리자에서 경쟁을 유발하는 성능 문제, 스트림 소비자의 재시작 등
메시지가 지연되면 메시지 순서를 예측 못할 수도 있다.
이벤트 시간과 처리 시간을 혼동하면 좋지 않은 데이터가 만들어진다.
즉, 실제 요청률은 안정적이지만 백로그를 처리하는 동안 요청이 비정상적으로 튀는 것처럼 보인다.

6.2.2. 준비 여부 인식

이벤트 시간 기준으로 윈도우를 정의할 때 발생하는 까다로운 문제는 특정 윈도우에서 모든 이벤트가 도착했다거나 아직도 이벤트가 계속 들어오고 있는 지를 확신할 수 없다는 점이다.
타임아웃을 설정하고 얼마 동안 새 이벤트가 들어오지 않으면 윈도우가 준비되었다고 선언할 수 있지만 일부 이벤트는 네트워크 중단 때문에 지연되어 다른 장비 어딘가에 버퍼링될 가능성도 여전히 존재한다.
따라서 윈도를 이미 종료한 후에 도착한 낙오자 이벤트를 처리할 방법이 필요한데 크게 두가지로 구성된다.
낙오자 이벤트를 무시하거나 수정 값을 발행한다.

6.2.3. 어쨋든 어떤 시계를 사용할 것인가

이벤트가 시스템의 여러 지점에 버퍼링됐을 때 이벤트에 타임스탬프를 할당하는 것은 더 어렵다.
이벤트의 타임 스탬프는 모바일 로컬 시계를 따르는, 실제 사용자와 상호작용이 발생했던 실제 시각이어야 한다.
그러나 이 도한 항상 신뢰하기는 어렵다.
잘못된 장비 시계를 조정하는 한 가지 방법은 세 가지 타임스탬프를 로그로 남기는 것이다.
이벤트가 발생한 시간, 장치 시계를 따른다.
이벤트를 서버로 보낸 시간, 장치 시계를 따른다.
서버에서 이벤트를 받은 시간, 서버 시계를 따른다.
두번째와 세번째의 타임스탬프 차이를 구하면 장치 시계와 서비 시계 간의 오프셋을 추정할 수 있다.
이를 통해 이벤트가 실제로 발생한 시간을 추측할 수 있다.
위 문제는 스트림 처리뿐만아니라 일괄 처리에서도 동일하게 문제가 발생함
다만 스트림 처리를 할 때가 시간의 흐름을 잘 알 수 있기 때문에 이 문제가 더 두드러짐