스트림 프로세싱(Stream Processing) - 1

일반적으로 배치 처리의 문제점은 입력의 변화가 특정 기간이 끝나야 반영이 되는 문제가 있습니다. 이러한 지연(Lag)을 줄이려면 더 자주 실행할 수 있도록 해야 합니다. 고정된 타임 슬라이스 별로 처리하는 것이 아닌 이벤트가 발생할 때마다 처리하도록 하는 것입니다. 이것이 바로 스트림 처리입니다. 첫번째 그림은 배치 처리의 시간 지연을 나타내는 그림입니다. 배치 처리의 경우 예를 들어 입력 데이터가 하루 단위로 생성된다고 할 때 처리하고 반영되는데 하루의 지연이 발생합니다.

batch processing

batch processing

아래의 그림은 스트림 처리의 지연 시간을 나타내는 그립니다. 스트림 처리의 경우 바로 처리가 가능하기 때문에 시간 지연이 거의 발생하지 않는 것을 확인할 수 있습니다.

stream processing

stream processing

일반적으로 스트림은 시간 흐름에 따라 점진적으로 생산되는 데이터를 말합니다. 여기서는 데이터 관리 메커니즘으로 이벤트 스트림에 대해 살펴볼 예정입니다.

이벤트 스트림 전송

간단히 스트림 처리에서 사용하는 용어들에 대해 살펴보겠습니다. 배치 처리와 비교하면 조금더 손쉽게 이해할 수 있습니다. 배치의 입력은 레코드의 집합인 파일입니다. 스트림 처리의 입력은 하나의 이벤트 혹은 레코드라고 할 수 있습니다. 이벤트의 경우 이벤트가 발생한 시간 정보를 포함하고 있습니다. 이벤트의 예로는 웹 페이지를 보거나 상품을 구매하는 일과 같이 사용자가 취한 행동 등이 될 수 있습니다.

스트림 처리의 이벤트는 한정되지 않고 계속 처리되는 데이터 입니다. 그렇기 때문에 스트림 처리에서는 이벤트를 생산하는 생산자(혹은 발행자나 전송자라고도 합니다)가 존재합니다. 그리고 이렇게 발생한 이벤트를 가져가서 처리하는 소비자(구독자 또는 수신자)가 존재합니다. 이 이벤트는 한 소비자가 아닌 여러 소비자가 소비할 수 있습니다. 또한 파일 시스템에서 관련 레코드 집합을 파일 이름으로 구분하는 것 같이 스트림 시스템에서는 관련 이벤트들을 토픽으로 구분합니다.

그럼 간단한 스트림 시스템을 만들기 위해선 어떻게 하면 될까요? 생산자와 소비자를 연결해주는 역할을 하는 것이 필요합니다. 파일이나 데이터베이스가 그러한 역할을 하기에 충분합니다. 생산자는 이벤트를 데이터베이스에 기록하고 소비자가 이를 주기적으로 폴링해서 데이터를 가져가서 처리하면 될 것입니다. 그러나 기존의 데이터베이스가 스트림 목적으로 설계하지 않았다면 데이터베이스로부터 폴링을 위한 비용이 굉장히 클 것입니다. 그래서 폴링보다는 새로운 이벤트가 발생할 때마다 소비자에게 알려주는 것이 더 나을 것입니다. 이와 같이 새로운 이벤트가 발생하면 소비자에게 알려주기 위해 사용하는 것이 메시징 시스템입니다.

메시징 시스템

메시징 시스템을 구축하는 간단한 방법은 생산자와 소비자 사이에 TCP 연결과 같은 직접 통신 채널을 연결하는 방법입니다. TCP 연결은 전송자와 수신자가 일대일로 연결됩니다. 그러나 메시징 시스템은 다음 그림과 같이 다수의 생산자가 같은 토픽으로 메시지를 전송할 수 있고 다수의 소비자가 하나의 토픽에서 메시지를 받아갈 수 있습니다.

이러한 메시징 시스템은 pub/sub 모델을 사용합니다. 이러한 pub/sub 모델에서는 다음의 조건들에 따라 다양하게 설계되었습니다.

아래의 그림은 배압을 적용했을 때를 나타내는 그림입니다.

이와 같이 애플리케이션에서 메시지 손실을 허용할지 말지 여부에 따라 어떤 메시지 시스템을 적용할지 다릅니다. 그러므로 다양한 애플리케이션의 요구사항에 부합할 수 있는 메시징 시스템을 적용해야 합니다. 그러면 메시징 시스템은 어떻게 구성할 수 있는지 살펴보도록 하겠습니다.

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

다양한 메시지 시스템은 중간에 따로 노드를 통하지 않고 직접 생산자와 소비자가 네트워크로 연결되서 통신합니다. 이렇게 직접 생산자와 소비자가 연결되서 처리하는 경우에는 일반적으로 메시지가 유실될 가능성을 염두해야 합니다.

메시지 브로커

메시지 브로커는 생산자와 소비자 사이에 있는 중간 노드의 역할을 합니다. 메시지 브로커는 스트림 처리하는데 최적화 된 데이터베이스의 일종이라고 생각하면 됩니다. 생산자는 메시지 브로커로 메시지를 전송하고 소비자는 브로커로부터 메시지를 읽어서 처리할 수 있습니다. 생산자와 소비자는 메시지 브로커에 클라이언트로 접속해서 처리하면됩니다.

메시지 브로커의 경우 위의 직접 네트워크로 연결되서 통신하는 방식과 다르게 클라이언트의 상태 변경(예를 들어 접속, 접속 해제, 장애)에 잘 대처할 수 있습니다. 예를 들어 소비자가 네트워크 장애가 발생해서 잠시 끊겼다가 다시 연결됐을 때도 메시지가 유실되는 것을 걱정하지 않아도 됩니다. 그외 다양한 장점들이 존재합니다.

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

앞서 메시지 브로커는 스트림 처리를 위한 데이터베이스의 일종이라고 하였습니다. 그러면 일반적인 데이터베이스와 메시지 브로커를 비교하여 살펴보겠습니다.

메시지 브로커 데이터베이스
데이터 보관 기간 대부분 일시적 영구적
데이터 검색 특정 패턴과 일치하는 토픽의 부분 집합 구독 형식 보조 색인 등의 다양한 검색 방법 지원
데이터 변화 알림 데이터가 변하면 알려줌 알려주지 않음

여러(Multiple) 소비자

여러 사용자가 하나의 토픽에서 메시지를 읽을 때 사용하는 패턴은 2가지가 있습니다. 다음 그림과 같이 로드 밸런싱과 팬 아웃 패턴입니다. 로드 밸런싱은 메시자가 여러 소비자 중 하나에 전달이 되는 것이고 팬 아웃은 메시지가 모든 소비자에게 전달되는 것입니다.

또한 두 패턴은 함께 사용 가능합니다. 예를 들어 두 소비자 그룹에서 하나의 토픽을 구독하여 각 그룹들은 해당 토픽의 모든 메시지를 받습니다. 그러나 그룹 내에서는 각 메시지를 하나의 노드에게만 전달하게 하는 것입니다.

확인 응답(Acknowledgments)과 재전송(Redelivery)

앞서 설명한대로 브로커를 통해 장애에 대처할 수 있다고 하였습니다. 브로커에서 소비자에게 메시지를 전달하였지만 소비자에서 장애가 발생하여 처리를 못한 경우가 발생할 수 있습니다. 메시지 브로커는 이러한 문제를 Ack를 통해 해결합니다. 그래서 소비자는 메시지가 정상적으로 받아서 처리했으면 브로커에게 명시적으로 알려줘야 합니다.

이전에 설명한 여러 소비자가 로드 밸런싱을 통해 메시지를 처리하는 경우 장애가 발생했을 때 메시지 순서가 바뀔 수도 있습니다. 다음 그림에서 처럼 두 소비자 중 소비자 2가 장애가 발생했을 때 Ack를 브로커에게 전달하지 않기 때문에 브로커는 다시 소비자 1에게 메시지를 재전송하게 됩니다. 이 경우 메시지의 순서가 뒤바뀔 수도 있습니다. 메시지의 순서가 상관 없는 경우에는 상관 없으나 메시지 순서가 중요한 경우에는 소비자마다 독립된 큐를 사용해야 합니다.

로그 기반 메시지 브로커

브로커는 메시지를 일시적으로 보관하는 개념으로 만들어졌습니다. 그렇기 때문에 일반적인 메시지 브로커의 경우 메시지를 디스크에 지속성 있게 기록하더라도 메시지가 소비자에게 전달된 후에는 삭제를 합니다. 데이터베이스와 파일 시스템은 이와는 반대의 접근법을 사용합니다. 즉, 누군가 명시적으로 데이터를 삭제할 때까지 영구 보관을 하는 것입니다. 그럼 메시지 시스템에서 데이터를 영구 보관이 가능하면 어떤 것이 좋을까요? 예를 들어 브로커가 소비자가 메시지를 처리했다라는 Ack가 와서 데이터를 삭제하는 경우 메시지 복구가 불가능합니다. 그래서 소비자는 처리를 다시 실행하도 동일한 결과를 받지 못합니다. 그러나 영구 저장을 하면 입력의 변경이 없이 반복 수행이 가능해지고, 재실행하면 동일 결과를 받을 수 있습니다. 이와 같은 효과는 많은 장점을 제공합니다. 이와 같이 데이터베이스의 지속적인 저장 방식과 메시지 시스템의 알림 기능을 조합한 것이 로그 기반 메시지 브로커(log-based message broker)입니다.

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

로그 기반 브로커의 경우 생산자가 보낸 메시지는 로그 끝에 추가 하고 소비자는 로그를 순차적으로 읽어 메시지를 받는 형식으로 구현합니다. 보통 하나의 디스크를 쓰는 것보다 여러 디스크를 사용하여 로그를 파티셔닝해서 처리량을 높이도록 구현합니다.

위의 그림과 같이 파티션 내의 브로커는 메시지의 오프셋을 부여합니다. 그리고 소비자는 파티션 별로 오프셋 정보를 가지고 있어서 자신이 얼만큼 읽었는지를 알고 있습니다.
이와 깉이 로그 기반 메시지 브로커의 대표적인 예가 아파치 카프카, 아마존 키네시스 스트림, 트위터의 DistributedLog 등이 있습니다.

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

전통적인 메시징 방식의 대표적인 것이 JMS/AMQP 방식이 있습니다. 보통 메시지의 순서가 중요하지 않고, 메시지를 처리하는 비용이 비싸고 메시지 단위로 병렬화 처리하고 싶은 경우 이러한 메시징 방식을 사용합니다. 그러나 처리량이 많고 메시지를 처리하는 비용은 싸지만(처리하는 속도는 빠르지만) 메시지의 순서가 중요한 경우에는 로그 기반의 메시지 브로커가 적합합니다.

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

위에서 설명한대로 소비자의 메시지 처리량이 생산자를 따라갈 수 없을 때는 3가지 선택지가 있다고 했습니다.

로그 기반의 메시지 브로커의 경우 버퍼링 형태라고 할 수 있습니다. 물론 소비자가 보유할 수 있는 메시지 버퍼의 크기보다 더 뒤처져 있는 경우에는 메시지를 읽을 수 없는 경우도 있습니다. 그러나 보통 소비자와 생산자가 얼마나 차이나는지 모니터링을 통해 문제를 고쳐서 따라 잡을 수 있는 시간은 충분히 벌 수 있습니다.

만약 소비자 처리 속도가 느려 메시지가 생산되는 속도를 따라 잡지 못하다가 너무 뒤쳐지게 된 경우 소비자 오프셋이 이미 삭제된 메시지를 가리킬 수 있습니다. 로그 기반 메시지 브로커는 보통 크기가 제한된 버퍼의 형태로 구현합니다. 그래서 버퍼가 가득 차게 되면 오래된 메시지부터 삭제를 하게 됩니다. 이러한 크기가 제한된 버퍼는 원형 버퍼의 형태로 구현합니다.

지난 메시지 재처리

AMQP와 JMS 유형의 메시지 브로커는 Ack를 받은 메시지는 삭제한다고 하였습니다. 그에 반해 로그 기반 메시지 브로커는 메시지나 로그를 삭제하지 않고 오프셋만 변경합니다. 이 오프셋은 소비자가 관리하기 때문에 원한다면 오프셋을 조절하여 어제 처리한 메시지를 재처리하는 등의 지난 메시지를 다시 처리할 수 있습니다.

정리

이번 포스트에서는 이벤트 스트림에 대해 알아보았습니다. 스트림 처리는 고정 크기의 입력이 아닌 끝이 없는 스트림 상에서 연속적으로 처리되는 것입니다. 이러한 스트림은 주로 메시지 브로커를 사용하여 처리합니다. 이번 포스트에서는 메시지 브로커를 2가지 관점에서 살펴보았습니다. 하나는 전통적인 메시지 브로커(AMQP/JMS 스타일)와 다른 하나는 로그 기반 메시지 브로커입니다.

다음 포스트에서는 이어서 이벤트 스트림의 아이디어를 데이터베이스와 연관시켜 살펴보도록 하겠습니다.

References



comments powered by Disqus