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