들어가기 전에
일괄처리의 한계를 이야기하면서 시작한다.
- 유한한 크기로 한정한다.
- 특히 정렬과 같은 것들은 전체 레코드를 읽어야 출력을 시작 할 수 있다.
실제로는 데이터는 시간이 지나면서 점진적으로 발생하기에 한정되지도 않고, 특정 기간씩 청크를 나눠야 한다.
그러나 이러한 특정 기간(보통 일)에 처리가 된다는건 사용자 입장에서는 매우 느릴 수 있다. 결국 이벤트가 발생할 때마다 처리를 시작하는 아이디어가 나왔는데 이게 스트림 처리의 기본 개념이다.
일반적으로 “스트림"은 시간 흐름에 따라 점진적으로 생산된 데이터를 일컫는다.
이벤트 스트림 전송
-
스트림에서의 입출력은 레코드(이벤트)이고, 특정 시점에 일어난 사건에 대한 세부사항을 포함하는 작고 독립된 불변 객체이다.
-
이벤트는 텍스트 문자열이나 json, 혹은 바이너리 형태로 부호화된다.
-
일괄 처리에서 파일은 한 번 기록하면 여러 작업에서 읽을 수 있고, 스트리밍에서도 이와 비슷하다.
-
생산자가 이벤트를 한 번 만들면 소비자가 처리 할 수있고, 파일시스템에서는 관련 레코드 집합을 파일 이름으로 식별하지만, 스트림에서는 토픽이나 스트림으로 관련 이벤트를 묶는다.
이번 절의 핵심인 문제는 이러한 이벤트를 알아내기 위해서는 알림 혹은 폴링과 같은 방식이 필요하다 즉 이벤트 발생을 캐치하기 위해 알림을 받거나 폴링하면서 새로운 이벤트를 캐치해야하는데, 전통적인 데이터 베이스는 이러한 것들을 고려하지 않고 설계되었으며, 일부 지원되는 기능들도 설계 이후에 도입된 개념들이라서 좋진 않다.
메시징 시스템
결론적으로 새로운 이벤트에 대해 소비자에게 알려주려고 쓰이는 일반적인 방법은 메시징 시스템을 사용하는 것이다.
구축하는데 가장 간단하게는 유닉스 파이프처럼 구현하거나, TCP연결을 직접 열어버리는 방법이지만, 대부분 시스템은 이 기본 모델을 확장한다.
원하는건 다수의 생산자가 같은 토픽으로 메시지를 전송하고, 다수의 소비자가 토픽하나에서 메시지를 받아가는 것이다.
위와 같은 모델을 pub/sub
모델이라고 한다. 그리고 이러한 접근법을 가진 시스템을 구분하는데 도움이 되는 두 가지 질문이 있다.
- 생산자가 소비자의 메시지를 처리하는 속도보다 빠르게 메세지를 전송한다면 어떻게 될까?
- 노드가 죽거나 일시적으로 오프라인이 된다면 어떻게 할까?
생산자에서 소비자로 메시지를 직접 전달하기
보통 udp 멀티캐스트와 같은 접근법을 사용한다. 일단 신뢰성 낮은 udp(일단 그냥 보내고 잘갔는지 맞게 왔는지 서로 확인 안함)를 쓰고, 애플리케이션 코드로 보완하는 방법들인것같다. 직접 메시징 시스템은 설계 상황에서는 잘 동작하지만, 유실에 대한 대응 코드가 어플리케이션에 들어가야 한다.
메시지 브로커
메시지 브로커는 근본적으로 메시지 스트림을 처리하는데 최적화된 데이터베이스의 일종이다.
생산자는 브로커로 소비자는 브로커에서 메시지를 전송하거나 전송받는다.
데이터를 브로커에 모으기 때문에 아래와 같은 장점이 있다고 한다.
- 클라이언트 상태 변경에 쉽게 대처 가능.
- 디스크에 저장하거나, 큐를 제한없이 늘어나게해서 유실에 대비한다.
그리고 큐대기를 하면 소비자는 일반적으로 비동기로 동작한다.
복수 소비자
기본적으로는 두가지 방법
- 로드밸런싱 - 메시지는 소비자중 하나로 전달 -> 병렬처리를 위해서
- 팬아웃 - 여러 독립적인 소비자가 동일한 메시지를 간섭없이 청취 할 수 있도록.
확인 응답과 재전송
소비자는 언제라도 장애가 발생할 수 있고, 소비자가 메세지를 처리하지 못하거나 부분적으로만 처리한 후 장애가 나는 상황이 생긴다. 그래서 브로커는 확인 응답을 사용한다. 클라이언트의 연결이 닫히거나 타임아웃이 나면 브로커는 다시 소비자에게 메세지를 전송한다.
문제는 이렇게 장애가 나서 확인응답을 못받아서 다시 전송한다고 해도 소비자가 많은 상황에서는 문제가 생기는데,
바로 메시지 순서가 유지되지 않는다는 것이다. (메시지간 인과성이 있다면 매우 중요한 문제다)