Event Driven Architecture

Created
Sep 18, 2021
Created by
Tags
Architecture
Property
 

Event 의 특징

 
  • 어떤 상태에 변화가 생겼을 때 이벤트를 발생시킨다.
  • 이벤트는 immutable 하다. Producer 에 의해 변경되거나 삭제될 수 없다.
  • 생성된 순서를 보장한다.
  • 이벤트에 관심이 있는 개체는 이벤트가 발생했을 때 이를 감지하고 비즈니스 로직을 수행한다.
 

EDA(Event Driven Architecture) 의 구조

 
EDA (Event Driven Architecture) 는 아래와 같이 이벤트를 발생시키는 Producer, 이벤트를 감지하는 Consumer, Producer 와 Consumer 사이에 이벤트를 전달하는 message backbone 으로 구성되어 있다. 이런 구조는 하나의 시스템 안에서 각자의 마이크로서비스들이 loosely coupled 하게 서로의 정보를 교환하도록 해준다.
notion image
 
 
Message backbone 으로는 RabbitMQ 같은 publish/subscribe 형태의 message broker 또는 Apache Kafka 같은 event streamer 를 사용한다.
publish/subscribe 모델에서 message broker 는 여러 consumer 들이 동일한 토픽을 구독하는 게 가능하다. 모든 consumer 들이 메세지를 받고나면 메세지는 삭제된다.
반면에 event streamer 모델에서 consumer 들은 오프셋을 사용해서 event stream 내에 어느 이벤트까지 처리했는지 트래킹한다. event streamer 에서는 consumer 들이 같은 이벤트 메세지를 여러 번 가져갈 수 있고 이론 상으로 이벤트는 스트림에 영원히 머무를 수 있다. 이는 새로 추가된 consumer 가 스트림에서 가장 처음에 발생한 이벤트부터 읽을 수도 있다는 뜻이다. (더 자세한 내용은 여기 를 참고)
두 가지 모델 중 어느 것을 사용할 지는 이벤트의 발생 빈도, 데이터의 지속성 필요 여부, producer 의 특성에 따라 선택해야 한다.
 
 

Event-Driven Architecture 의 장점

 

producer & consumer decoupling 이 가능하다

event driven architecture 에서 producer 는 자신들이 내보낸 이벤트를 각 consumer 들이 어떻게 처리할 것인지 신경쓰지 않아도 된다. 따라서 producer 에 영향을 주지 않고 consumer 를 추가할 수 있다.
REST API 로 정보를 교환한다면 producer의 API를 호출하는 consumer 가 늘어날 때 producer 를 scale up 해야하는 비용이 발생할 수 있다.
EDA 에서 consumer 가 늘어나면 message backbone 사양을 늘릴 필요가 있겠지만 REST 모델에 비해 producer와 consumer 각자가 독립적으로 scale up/out 이 가능하다는 데 의미가 있다.
 

시스템 회복성이 좋다

어떤 consumer 마이크로서비스에 장애가 발생했다고 하자. EDA 에서는 consumer 가 다운되어도 producer 에서는 consumer의 상태와 상관없이 이벤트를 message backbone 에 쌓는다. 따라서 다운되었던 consumer 가 회복하고 나면 장애로 인해 처리를 실패했던 이벤트부터 replay 할 수 있다.
REST 모델에서는 네트워크가 유실되거나 producer 내부에 오류가 발생할 것을 대비하여 consumer 쪽에서 retry 로직을 넣어야 하는 데 비해 EDA 에서는 그럴 필요가 없다.
이런 특징은 오프라인 되기 쉬운 서비스에 적합하다. 예를 들어 producer가 이벤트를 발생시키면 사용자 앱에 푸시 알림을 하는 consumer 가 있다고 하자. 사용자가 기기를 꺼놓는 동안에 consumer는 아무 일을 하지 않고 producer 쪽에서는 이벤트를 message backbone 에 계속 쌓는다. 사용자가 기기를 켰을 때 그 동안 쌓였던 메세지를 consumer 가 replay 하여 차례대로 앱에 푸시를 하는 것.
 

polling 을 하지 않아도 된다

EDA 에서 클라이언트는 어떤 요청에 대한 결과를 곧바로 받고자 할 때 polling 을 할 필요 없이 producer의 push 를 받기만 하면 된다. 따라서 network IO 비용이 감소한다.
 

event sourcing 패턴이 single source of truth 를 가능하게 한다

 
single source of truth : 정보 모델과 연관 데이터를 오직 한 곳에서만 생성/수정하는 관례. 어떤 서비스가 데이터를 공급하면 제어도 해당 서비스에서 해야 한다. 이렇게 하면 어떤 데이터가 너무 오래되었거나 잘못되었을 때 이 데이터가 올바른 것인지 판단하기 위해 데이터를 공급/제어하는 서비스(source of truth)를 참조하기만 하면 된다.
 
Event의 특징에서 언급한 것처럼 비즈니스에서 어떤 개체의 상태에 변화가 생겼을 때 새로운 이벤트가 발생하고, 이 이벤트는 변경할 수 없다. 따라서 하나의 이벤트는 하나의 사실을 대표한다.
 
notion image
EDA 에서는 event sourcing 이라는 패턴을 사용하는데, 위 그림에서 보이는 것처럼 비즈니스 객체 (Customer, Item, Address 에 해당)에 변화가 생겼을 때 consumer 는 객체의 최종 상태만을 스토리지에 저장하는 게 아니라 이벤트 자체를 스토리지에 쌓는 것을 말한다. 따라서 이벤트 소싱을 받는 consumer 의 스토리지에는 UPDATE, DELETE 가 발생하지 않고 CREATE 만 발생한다.
 
Item 을 예로 들어서, Customer 들이 Item을 구매할 때마다 Item의 재고가 줄어드는데 consumer 입장에서는 item_count -= 1 와 같은 로직으로 최종 재고 수량만을 스토리지에 UPDATE 하는 게 아니라
 
customer 1, item 1
customer 3, item 2
customer 1, item 3
 
과 같이 상태 자체를 스토리지에 저장하고, Item의 재고를 조회할 때 이 row들을 집계해서 재고 수량을 제공하는 것이다.
이러한 패턴에서는 이벤트가 아주 빈번하게 발생하는만큼 스토리지에 쌓이는 row 가 많아지기 때문에 최종 상태를 가져오는 데 시간이 오래 걸릴 수 있다. 따라서 스냅샷(snapshot) 개념을 도입한다. 특정 수 만큼 이벤트가 발생했을 때 그 때의 최종 상태를 스냅샷으로 저장해놓고, 그 이후에 조회할 때는 스냅샷 이후에 발생한 이벤트들만 집계하는 것.
이애플리케이션을 재시작해서 비즈니스 객체를 가장 최근의 상태로 생성해야 할 때 event sourcing 을 받은 스토리지가 source of truth 가 된다.
event sourcing 은 race codition 이 발생할 가능성을 없애며, race condition 을 방지하기 위해 RDBMS 를 사용하는 경우 isolation level 을 올려야 하는 비용을 들이지 않아도 된다.
 
 

Event-Driven Architecture 의 단점

 

비즈니스 흐름을 파악하기 어렵다

producer 와 consumer 가 decoupling 되는만큼 비즈니스 흐름을 이해하기 어렵게 한다. (시스템의 규모가 커지면 여러 event notification 들이 하나의 비즈니스 로직을 구성하게 되는데 코드 상으로는 이를 알아보기가 어렵다.) 이는 문제가 발생했을 때 문제의 근간을 파악하는 데 장애가 될 수 있다. 따라서 각 마이크로서비스를 모니터링하는 게 중요하고 시스템 흐름을 이해할 수 있도록 문서화하는 데 신경 써야 한다.
 

consumer 의 데이터 일관성을 보장하지 못할 수 있다

어떤 마이크로서비스 A 에서 Item 등록과 삭제 이벤트를 produce 하고 B는 이를 consume 한다고 하자. B는 항상 아이템 등록 이벤트 이후에 삭제 이벤트가 발생하는 것을 예상하고 있으나 네트워크 상의 이유로 삭제 이벤트가 등록 이벤트보다 먼저 consume 될 수 있다. 일관성을 유지해야 하는 B는 이러한 예외를 감안해야 하며 이는 B의 코드 복잡도를 높일 수 있다.
 
 
 
 
출처