Pub/Sub를 사용한 이벤트 기반 아키텍처

이 문서에서는 온프레미스 메시지 큐 기반 아키텍처와 Pub/Sub에서 구현되는 클라우드 기반의 이벤트 기반 아키텍처 사이의 차이점에 대해 설명합니다. 클라우드 기반 기술에 온프레미스 패턴을 직접 적용하려고 시도하면 처음부터 클라우드를 강력하게 만들 수 있는 고유한 가치를 놓칠 수 있습니다.

이 문서는 온프레미스 아키텍처에서 클라우드 기반 디자인으로 디자인을 마이그레이션하려는 시스템 설계자를 대상으로 합니다. 이 문서에서는 사용자가 메시징 시스템에 대해 기본적으로 이해하고 있다고 가정합니다.

다음 다이어그램은 메시지 큐 모델 및 Pub/Sub 모델의 개요를 보여줍니다.

메시지 큐 모델 구조와 Pub/Sub를 사용하는 이벤트 기반 모델을 비교해서 보여줍니다.

앞의 다이어그램은 메시지 큐 모델을 Pub/Sub 이벤트 스트림 모델과 비교해서 보여줍니다. 메시지 큐 모델에서 게시자는 각 구독자가 특정 큐를 리슨할 수 있는 큐로 메시지를 게시합니다. Pub/Sub를 사용하는 이벤트 스트림 모델에서는 게시자가 여러 구독자가 리슨할 수 있는 주제로 메시지를 게시합니다. 다음 섹션에서는 이러한 모델 간의 차이점에 대해 설명합니다.

이벤트 스트림 및 큐 기반 메시징 비교

온프레미스 시스템을 사용하는 경우 이미 엔터프라이즈 서비스 버스(ESB)메시지 큐에 익숙하다고 가정합니다. 이벤트 스트림은 새로운 패턴이며 현대식 실시간 시스템의 구체적 이점과 중요한 차이가 있습니다.

이 문서에서는 전송 메커니즘과 이벤트 기반 아키텍처의 페이로드 데이터에 관한 주요 차이점에 대해 설명합니다.

메시지 전송

이러한 모델에서 데이터를 이동하는 시스템을 메시지 브로커라고 부르며, 여기에는 다양한 프레임워크가 구현되어 있습니다. 첫 번째 개념 중 하나는 게시자로부터 수신자로 메시지를 전송하는 기본 방식입니다. 온프레미스 메시지 프레임워크에서 시작 시스템은 메시지 큐를 전송 방식으로 사용하여 명시적이고, 결합 해제된 원격 메시지를 다운스트림 처리 시스템으로 전송합니다.

다음 다이어그램은 메시지 큐 모델을 보여줍니다.

게시자의 메시지가 각 구독자의 고유 큐로 게시됩니다.

위 다이어그램에서 메시지는 메시지 큐를 사용하여 업스트림 게시자 프로세스에서 다운스트림 구독자 프로세스로 이동합니다.

시스템 A(게시자)가 시스템 B(구독자)에 대해 지정된 메시지 브로커의 큐로 메시지를 전송합니다. 큐의 구독자가 여러 클라이언트로 구성될 수 있지만, 이러한 모든 클라이언트는 확장 및 가용성을 위해 배포된 시스템 B의 중복 인스턴스입니다. 추가 다운스트림 프로세스(예: 시스템 C)가 생성자(시스템 A)의 동일 메시지를 사용해야 할 경우, 새 큐가 필요합니다. 메시지를 새 큐로 게시하도록 생성자를 업데이트해야 합니다. 이 모델을 종종 메시지 전달이라고 부릅니다.

이러한 큐의 메시지 전송 레이어는 메시지 순서 보장을 제공하거나 제공하지 않을 수 있습니다. 메시지 큐는 태스크 큐와 비슷하게 엄격한 선입선출(FIFO) 액세스 모델로 시퀀싱된 데이터가 포함되는 순서 보장 모델을 제공할 것으로 예상되는 경우가 많습니다. 이 패턴은 처음에 쉽게 구현할 수 있지만 결국 확장 및 운영 과제가 발생합니다. 순서가 지정된 메시지를 구현하기 위해서는 시스템에서 데이터를 정리하기 위한 중앙 프로세스가 필요합니다. 이러한 프로세스는 단일 장애점이 되기 때문에 확장 기능을 제한하며, 서비스 가용성을 낮춥니다.

이러한 아키텍처의 메시징 브로커는 구독자가 수신한 메시지 추적 및 구독자 부하 모니터링과 같은 추가적인 논리를 구현하는 경향이 있습니다. 구독자는 전체 시스템 정보를 처리하는 대신 단순히 메시지 수신에 따라 특정 기능만 실행하는 단순 대응적인 경우가 많습니다. 이러한 유형의 아키텍처를 스마트 파이프(메시지 큐 시스템) 및 덤 엔드포인트(구독자)라고 부릅니다.

Pub/Sub 전송

메시지 지향 시스템과 비슷하게, 이벤트 스트리밍 시스템은 또한 소스 시스템에서 결합 해제된 대상 시스템으로 메시지를 전송합니다. 하지만 각 메시지를 프로세스 대상 큐로 전송하는 대신 이벤트 기반 시스템이 메시지를 공유 주제에 게시합니다. 그런 후 하나 이상의 수신기가 관련 메시지를 리슨하기 위해 해당 주제를 구독합니다.

다음 다이어그램은 업스트림 게시자에서 단일 주제로 여러 메시지를 전송한 후 관련 다운스트림 구독자로 라우팅하는 방법을 보여줍니다.

게시자의 메시지가 모든 구독자에 대해 단일 주제로 게시됩니다.

이러한 게시 및 구독 패턴으로부터 pub/sub라는 용어가 비롯되었습니다. 이 패턴은 또한 Pub/Sub라는 Google Cloud 제품의 기초이기도 합니다. 이 문서 전체에서 pubsub는 패턴을 나타내고 Pub/Sub는 제품을 나타냅니다.

pubsub 모델에서 메시징 시스템은 구독자에 대한 정보를 확인할 필요가 없습니다. 수신된 메시지가 무엇인지 추적하지 않으며, 소비 프로세스에 대한 부하를 관리하지 않습니다. 대신 구독자가 수신된 메시지를 추적하고 부하 수준 및 확장을 자체적으로 관리합니다.

한 가지 중요한 이점은 pubsub 모델에서 데이터에 대해 새 사용자가 발견되었을 때 새 큐 또는 중복 데이터에 게시하도록 원래 시스템을 업데이트할 필요가 없다는 것입니다. 대신 기존 시스템에 대한 영향 없이 새 소비자를 새 구독에 연결합니다.

이벤트 스트리밍 시스템의 호출은 거의 항상 비동기적이며, 이벤트를 전송하고 응답을 기다리지 않습니다. 비동기 이벤트를 통해 생성자 및 소비자 모두에 대해 더 큰 확장 옵션이 허용됩니다. 하지만 FIFO 메시지 순서 보장이 필요할 때는 이러한 비동기 패턴으로 인해 문제가 발생할 수 있습니다.

메시지 큐 데이터

메시지 큐 시스템과 pubsub 기반 시스템 간에 전달되는 데이터를 일반적으로 두 컨텍스트 모두에서 메시지라고 부릅니다. 하지만 데이터가 제공되는 모델은 다릅니다. 메시지 큐 시스템에서 메시지는 다운스트림 데이터 상태를 변경하기 위한 명령어를 반영합니다. 온프레미스 메시지 큐 시스템에 대해 데이터를 조사할 경우 게시자는 소비자가 수행해야 할 작업을 명시적으로 설명할 수 있습니다. 예를 들어 인벤토리 메시지에 다음이 표시될 수 있습니다.

<m:SetInventoryLevel>
    <inventoryValue>3001</inventoryValue>
</m: SetInventoryLevel>

이 예시에서 생성자는 소비자에게 인벤토리 수준을 3001로 설정하라고 표시합니다. 이 접근 방식은 생성자가 각 소비자의 비즈니스 논리를 이해해야 하고 서로 다른 사용 사례에 대해 개별적인 메시지 구조를 만들어야 하기 때문에 어려울 수 있습니다. 이 메시지 큐 시스템은 대부분의 기업들에서 구현한 대규모 모놀리식이 포함된 일반적인 방식이었습니다. 하지만 더 빨리 이동하고, 더 크게 확장하고, 이전보다 더 혁신하려는 경우에는 변경 자체가 위험하고 느릴 수 있기 때문에 이러한 중앙 집중식 시스템이 병목 현상의 원인이 될 수 있습니다.

또한 이 패턴에는 운영적 과제가 있습니다. 잘못된 데이터, 중복된 레코드 또는 다른 문제가 발생하여 수정이 필요할 때는 이 메시징 모델이 중대한 문제를 일으킬 수 있습니다. 예를 들어 이전 예시에 사용된 메시지를 롤백해야 할 경우 이전 상태에 대한 참조가 없기 때문에 수정된 값을 설정하기 위해 무엇을 해야 할지 알 수 없습니다. 메시지가 전송되기 전 인벤토리 값이 3000 또는 4000이었는지에 대한 정보가 없습니다.

Pubsub 데이터

이벤트는 메시지 데이터를 전송하기 위한 또 다른 방법입니다. 특별한 점은 이벤트 기반 시스템이 발생해야 할 결과 대신 실제로 발생한 이벤트에 집중한다는 것입니다. 소비자가 수행해야 할 작업을 나타내는 데이터를 전송하는 대신 생성된 실제 이벤트의 세부정보에 집중합니다. 이벤트 기반 시스템은 다양한 플랫폼에서 구현할 수 있지만 pubsub 기반 시스템에서 자주 보입니다.

예를 들어 인벤토리 이벤트가 다음과 같이 표시될 수 있습니다.

{ "inventory":-1 }

이전 이벤트 데이터는 발생한 이벤트로 인벤토리가 1만큼 감소한 것을 나타냅니다. 메시지는 미래에 변경될 상태가 아닌 과거에 발생한 이벤트에 집중합니다. 게시자가 비동기 방식으로 메시지를 전송할 수 있으므로 메시지 큐 모델보다 이벤트 기반 시스템이 더 쉽게 확장됩니다. pubsub 모델에서는 비즈니스 논리를 분리할 수 있으므로 생성자가 수행되는 작업만 이해하면 되고, 다운스트림 프로세스를 이해할 필요가 없습니다. 해당 데이터의 구독자는 수신되는 데이터를 처리할 최선의 방법을 선택할 수 있습니다. 이러한 메시지는 필수 명령어가 아니기 때문에 메시지 순서가 덜 중요합니다.

이 패턴에서는 변경사항을 쉽게 롤백할 수 있습니다. 이 예시에서는 인벤토리 값을 무효로 하고 반대 방향으로 이동할 수 있기 때문에 추가 정보가 필요하지 않습니다. 늦게 또는 순서에 맞지 않게 오는 메시지는 더 이상 문제가 되지 않습니다.

모델 비교

이 시나리오에서는 인벤토리에 4개의 동일 제품 항목 사용됩니다. 한 고객이 제품 1개를 반품하고 다음 고객이 동일 제품을 3개 구입합니다. 이 시나리오에서는 반품된 제품에 대한 메시지가 지연되었다고 가정합니다.

다음 표에서는 올바른 순서로 인벤토리 수를 수신하는 메시지 큐 모델의 인벤토리 수준을 잘못된 순서로 인벤토리 수를 수신하는 동일 모델과 비교합니다.

메시지 큐(올바른 순서) 메시지 큐(잘못된 순서)
초기 인벤토리: 4 초기 인벤토리: 4
메시지 1: setInventory(5) 메시지 2: setInventory(2)
메시지 2: setInventory(2) 메시지 1: setInventory(5)
인벤토리 수준: 2 인벤토리 수준: 5

메시지 큐 모델에서는 메시지에 미리 계산된 값이 포함되기 때문에 메시지가 수신되는 순서가 중요합니다. 이 예시에서 메시지가 올바른 순서로 도착할 경우 인벤토리 수준이 2입니다. 하지만 메시지가 잘못된 순서로 도착하면 인벤토리 수준이 정확하지 않음을 나타내는 5입니다.

다음 표에서는 올바른 순서로 인벤토리 수를 수신하는 pubsub 기반 시스템의 인벤토리 수준과 잘못된 순서로 인벤토리 수를 수신하는 동일 시스템을 비교합니다.

Pubsub(올바른 순서) Pubsub(잘못된 순서)
초기 인벤토리: 4 초기 인벤토리: 4
메시지 2: "inventory":-3 메시지 1: "inventory":+1
메시지 1: "inventory":+1 메시지 2: "inventory":-3
인벤토리 수준: 2 인벤토리 수준: 2

pubsub 기반 시스템에서는 이벤트를 생성하는 서비스로 알 수 있기 때문에 메시지 순서가 중요하지 않습니다. 메시지 도착 순서에 관계없이 인벤토리 수준이 정확합니다.

다음 다이어그램은 메시지 큐 모델에서 상태 변경 방식을 구독자에게 알려주는 명령어가 큐로 실행되고, pubsub 모델에서는 게시자에서 수행된 작업을 나타내는 이벤트 데이터에 구독자가 대응하는 방법을 보여줍니다.

명령어 대응과 이벤트 대응을 비교해서 보여주는 확인 예시입니다.

이벤트 기반 아키텍처 구현

이벤트 기반 아키텍처를 구현할 때는 여러 개념을 고려해야 합니다. 다음 섹션에서는 이러한 주제 중 몇 가지를 소개합니다.

전송 보장

시스템 논의에 나타나는 한 가지 개념은 메시지 전송 보장의 신뢰성입니다. 공급업체 및 시스템마다 제공되는 신뢰성 수준이 다를 수 있기 때문에 여러 유형 간의 차이를 이해하는 것이 중요합니다.

첫 번째 보장 유형은 간단한 질문으로 시작됩니다. 메시지가 전송되는 경우, 그러한 전송이 보장되나요? 이것을 최소 1회 전송이라고 부릅니다. 메시지가 최소 1회 전송되도록 보장하지만 2번 이상 전송될 수 있습니다.

다른 보장 유형은 최대 1회 전송 보장입니다. 최대 1회 전송의 경우 메시지가 최대 한 번만 전송되지만, 실제로 전송되었는지는 보장할 수 없습니다.

전송 보장의 마지막 유형은 정확히 1회 전송입니다. 이 모델에서는 시스템이 전송이 보장되는 메시지 사본을 정확히 하나만 전송합니다.

순서 및 중복

온프레미스 아키텍처에서 메시지는 종종 FIFO 모델을 따릅니다. 이 모델을 달성하기 위해 중앙화된 처리 시스템이 메시지 순서를 관리하여 정확한 순서를 보장합니다. 순서가 지정된 메시징은 실패한 메시지가 있을 때 모든 메시지를 순서대로 다시 전송해야 하기 때문에 문제가 될 수 있습니다. 중앙 집중식 시스템은 가용성 및 확장성 면에서 문제가 될 수 있습니다. 순서를 관리하는 중앙 시스템을 확장하기 위해서는 일반적으로 기존 머신에 리소스를 더 추가하는 것 말고 다른 방법이 없습니다. 단일 시스템이 순서를 관리하기 때문에 신뢰성 문제가 발생하면 해당 머신뿐만 아니라 전체 시스템에 영향을 줍니다.

확장성 및 가용성이 높은 메시징 서비스에는 메시지의 최소 1회 전송 보장을 위해 여러 처리 시스템이 사용되는 경우가 많습니다. 시스템이 많아지면 메시지 순서를 보장할 수 없습니다.

이벤트 기반 아키텍처는 메시지 순서가 중요하지 않고 중복 메시지도 용인할 수 있습니다. 순서가 필요할 경우 하위 시스템이 집계 및 윈도잉 기법을 구현할 수 있지만, 이 방식은 해당 구성요소에서의 확장성 및 가용성 희생이 필요합니다.

필터링 및 팬아웃 기법

이벤트 스트림에 모든 구독자에 필요하거나 필요하지 않을 수 있는 데이터가 포함될 수 있기 때문에 지정된 구독자가 수신하는 데이터를 제한해야 할 경우가 자주 있습니다. 이 요구사항을 관리하기 위한 패턴은 이벤트 필터 및 이벤트 팬아웃의 두 가지입니다.

다음 다이어그램은 구독자에 대해 메시지를 필터링하는 이벤트 필터가 사용되는 이벤트 기반 시스템을 보여줍니다.

구독자에 대해 메시지를 필터링하는 이벤트 필터가 사용되는 이벤트 기반 모델입니다.

앞의 다이어그램에서 이벤트 필터에는 구독자에게 연결되는 이벤트를 제한하는 필터링 메커니즘이 사용됩니다. 이 모델에서는 단일 논리에 모든 메시지 변형이 포함됩니다. 구독자가 각 메시지를 읽고 적용 가능한지 확인하는 대신 메시징 시스템의 필터링 논리가 메시지를 평가하고 다른 구독자가 접근하지 못하도록 제한합니다.

다음 다이어그램은 여러 주제를 사용하는 이벤트 팬아웃이라고 부르는 이벤트 필터 패턴의 변형을 보여줍니다.

주제에 따라 메시지를 다시 게시하는 이벤트 팬아웃을 사용하는 이벤트 기반 모델입니다.

앞의 다이어그램에서는 기본 주제에 메시지의 모든 변형이 포함되지만, 이벤트 팬아웃 메커니즘은 해당 구독자 하위 집합에 관련된 주제에 따라 메시지를 다시 게시합니다.

처리되지 않은 메시지 큐

최상의 시스템의 경우에도 오류는 발생할 수 있습니다. 처리되지 않은 메시지 큐는 이러한 오류를 해결하기 위한 한 가지 기법입니다. 대부분의 이벤트 기반 아키텍처에서 메시지 시스템은 구독자가 확인할 때까지 구독자에게 메시지를 계속 제공합니다.

메시지 본문에 포함된 부적합한 문자와 같이 메시지에 문제가 있으면 구독자가 메시지를 확인하지 못할 수 있습니다. 그러면 시스템이 이러한 시나리오를 해결하지 못하거나 심지어 프로세스를 종료할 수도 있습니다.

시스템은 일반적으로 확인되지 않은 또는 오류가 발생한 메시지를 다시 시도합니다. 부적합한 메시지가 미리 정해진 일정 기간이 지나서도 확인되지 않은 상태로 지속되면 메시지가 결국 시간 초과되고 주제에서 삭제됩니다. 운영적인 관점에서는 메시지가 사라지게 두는 대신 메시지를 검토하는 것이 유용합니다. 바로 여기에서 처리되지 않은 메시지 큐 문제가 발생합니다. 주제에서 메시지를 삭제하는 대신 메시지를 다른 주제로 이동하여 여기에서 다시 처리하거나 검토하여 오류가 발생한 원인을 확인합니다.

스트림 내역 및 리플레이

이벤트 스트림은 데이터에 대한 송신 흐름입니다. 이 내역 데이터를 액세스하는 것이 유용합니다. 시스템이 특정 상태에 도달하게 된 방식을 알아야 할 수 있습니다. 데이터 감사가 필요한 보안 관련 질문이 있을 수 있습니다. 이벤트 기반 시스템의 장기적 운영 관점에서는 이벤트에 대한 내역 로그를 캡처하는 기능이 중요합니다.

내역 이벤트 데이터의 일반적인 용도는 리플레이 시스템에서 사용하는 것입니다. 리플레이는 테스트 목적으로 사용됩니다. 스토리지 및 테스트와 같은 다른 환경에서 프로덕션의 이벤트 데이터를 리플레이함으로써 실제 데이터 세트에 대해 새 기능이 적합한지 검사할 수 있습니다. 또한 내역 데이터를 리플레이하여 오류 상태로부터 복구할 수 있습니다. 시스템이 작동 중지되거나 데이터 손실이 발생하면 알려진 정상 지점으로부터 이벤트 내역을 리플레이하고 손실된 상태를 다시 빌드할 수 있습니다.

또한 구독자가 서로 다른 시간에 이벤트 시퀀스에 액세스해야 할 경우에는 로그 기반 큐 또는 로깅 스트림에서 이러한 이벤트를 캡처하는 것이 좋습니다. 로깅 스트림은 오프라인 기능이 있는 시스템에서 확인할 수 있습니다. 스트림 내역을 사용하면 마지막으로 읽은 포인터부터 시작하여 스트림 읽기를 수행하여 최근의 새 항목을 처리할 수 있습니다.

데이터 보기: 실시간 및 거의 실시간

모든 데이터가 시스템을 통해 전송될 때는 이 데이터를 사용할 수 있는 기능이 중요합니다. 이러한 이벤트 스트림을 액세스하고 사용하기 위해 많은 기법들이 있지만, 일반적인 사용 사례는 특정 순간에 데이터의 전체 상태를 확인하는 것입니다. 이러한 데이터 확인은 다른 시스템 또는 사용자에게 사용될 수 있는 "수량" 또는 "현재 수준"과 같은 계산 관련 질문인 경우가 많습니다. 이러한 질문의 답을 확인하기 위해 여러 가지 구현이 존재합니다.

  • 실시간 시스템은 연속 실행이 가능하고 현재 상태를 추적할 수 있지만, 시스템에 메모리 내 계산만 사용되기 때문에 다운타임이 발생하면 계산이 0으로 설정됩니다.
  • 이 시스템은 모든 요청에 대해 내역 테이블로부터 값을 계산할 수 있지만, 데이터 증가가 결국 무제한으로 커질 때 모든 요청에 대해 값을 계산하려고 시도하기 때문에 문제가 될 수 있습니다.
  • 이 시스템은 특정 간격에 따라 계산 스냅샷을 만들지만, 스냅샷만 사용해서는 실시간 데이터를 반영할 수 없습니다.

구현하면 유용한 패턴은 거의 실시간에 가까운 기능과 실시간 기능을 모두 포함하는 람다 아키텍처입니다. 예를 들어 전자상거래 사이트의 한 제품 페이지에 인벤토리 데이터에 대해 거의 실시간 보기가 사용될 수 있습니다. 고객이 주문할 때 실시간 서비스는 인벤토리 데이터에 대해 초 단위 최신 상태를 확인하기 위해 사용됩니다. 이 패턴을 구현하기 위해 서비스는 특정 간격에 따라 계산된 값이 포함된 스냅샷 테이블로부터 거의 실시간 요청에 대응합니다. 실시간 요청은 스냅샷 테이블 및 마지막으로 수행된 스냅샷 이후의 내역 테이블의 값을 모두 사용하여 정확한 현재 상태를 가져옵니다. 이벤트 스트림에 대한 이러한 구체화된 보기는 실제 비즈니스 프로세스를 구동하기 위한 실행 가능한 데이터를 제공합니다.

다음 단계