본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
강의 요약
오늘 강의에서는 분산 시스템의 근본적인 단점과 이를 해결하기 위한 이벤트 드리븐 아키텍처(Event-Driven Architecture, EDA)의 접근법에 대해 다루었다. 분산 시스템은 확장성과 가용성 측면에서 많은 이점을 제공하지만, 네트워크 지연, 데이터 일관성 문제, 운영 복잡도 증가 등의 본질적인 트레이드오프가 존재한다. EDA는 이벤트 브로커를 통한 비동기 통신으로 이러한 문제를 일부 해소할 수 있지만, 동시에 새로운 복잡성을 동반한다는 점을 학습했다. 조금 더 알아보자.
Event-Driven Architecture의 구조와 이점
EDA의 구조와 이점에 대해서 알아보자.
핵심 구성 요소
- EDA는 세 가지 핵심 요소로 구성된다.
- Event Producer는 상태 변화를 이벤트로 발행하는 주체로, 이커머스에서 주문이 생성되면 "OrderPlaced" 이벤트를 발행한다.
- Event Broker는 이벤트를 수신하고 저장하며 관심 있는 컨슈머에게 라우팅하는 중간 계층이다.
- Event Consumer는 특정 토픽을 구독하여 이벤트를 수신하고 처리하는 주체로, 재고 서비스가 주문 이벤트를 수신하여 재고를 차감하는 식이다.
느슨한 결합 달성
- Event Broker를 중심으로 한 Publish-Subscribe 패턴은 프로듀서와 컨슈머 간 직접적인 의존성을 제거한다.
- 주문 서비스는 누가 주문 이벤트를 소비하는지 알 필요가 없으며, 재고 서비스는 주문 이벤트가 어디서 발행되는지 알 필요가 없다.
- 새로운 서비스 추가 시 해당 토픽을 구독하기만 하면 되며, 기존 서비스 코드는 전혀 수정할 필요가 없다.
Topic 기반 라우팅
- Topic은 이벤트를 분류하는 계층적 문자열이다.
- 예를 들어 "commerce/order/created", "commerce/order/cancelled" 같은 형태로 구성하며, 컨슈머는 와일드카드를 사용하여 "commerce/order/*"처럼 여러 관련 이벤트를 한 번에 구독할 수 있다.
- 이를 통해 불필요한 이벤트를 필터링하고 네트워크 트래픽을 최소화한다.
실제 적용 사례
- Uber는 EDA를 통해 일일 수백만 건의 승차 요청을 처리한다. 사용자가 승차를 요청하면 "RideRequested" 이벤트가 발행되고, 매칭 서비스는 이를 소비하여 근처 드라이버를 찾고, ETA 서비스는 도착 예정 시간을 계산하며, 요금 서비스는 거리와 수요를 기반으로 가격을 산정한다. 각 서비스는 독립적으로 확장 가능하며, 한 서비스의 장애가 다른 서비스로 직접 전파되지 않는다.
- Sysco Shop은 대규모 식품 서비스 이커머스 플랫폼으로, GraphQL을 통해 들어온 주문을 Event Stream으로 발행한다. 주문 이벤트는 재고 서비스, 추천 서비스, 분석 서비스 등 여러 컨슈머에게 실시간으로 전달되며, 각 서비스는 자신의 속도로 이벤트를 처리한다.
EDA의 과제
하지만 EDA가 분산의 모든 단점들을 해결하는것일까? 그렇지 않을 것이다.
복잡성 증가
- 비동기 흐름은 디버깅을 어렵게 만든다. 동기식 호출에서는 스택 트레이스로 실행 경로를 추적할 수 있지만, 이벤트 기반 시스템에서는 이벤트가 여러 서비스를 거쳐 전파되므로 문제 발생 시 원인을 파악하기 어렵다.
- 분산 트레이싱 도구 없이는 특정 이벤트가 어떤 경로로 처리되었는지 알기 힘들다.
이벤트 순서 보장
- 여러 파티션이나 컨슈머 인스턴스가 존재할 때 이벤트 순서가 보장되지 않을 수 있다. 주문 생성 이벤트보다 주문 취소 이벤트가 먼저 처리되면 데이터 불일치가 발생한다.
- Kafka의 경우 동일한 키를 가진 이벤트를 같은 파티션으로 라우팅하여 순서를 보장하지만, 이는 해당 파티션 내에서만 유효하다.
멱등성 보장 필요
- 네트워크 장애나 컨슈머 재시작으로 인해 동일한 이벤트가 여러 번 처리될 수 있다.
- 재고 차감이나 결제 처리 같은 작업이 중복 실행되면 심각한 문제가 발생한다.
- 각 이벤트에 고유 ID를 부여하고, 처리 완료된 이벤트 ID를 추적하여 중복 처리를 방지해야 한다.
최종 일관성 이해
- EDA는 즉각적인 일관성이 아닌 최종 일관성을 제공한다.
- 주문이 생성된 직후 재고가 즉시 차감된다고 보장할 수 없으며, 수 밀리초에서 수 초 후에 일관된 상태에 도달한다.
- 이는 업무 요구사항에 따라 허용 가능한 수준인지 판단해야 한다.
Simple vs Complex Event Processing
- Simple Event Processing은 단일 이벤트에 대한 직접적인 반응이다.
- 주문이 생성되면 재고를 차감하고 배송을 준비하는 식이다. 구현이 간단하고 예측 가능하다.
- Complex Event Processing은 여러 이벤트를 집계하여 패턴을 감지한다.
- Uber의 서지 프라이싱은 교통 상황, 수요, 드라이버 가용성 등 다수의 이벤트를 분석하여 동적으로 가격을 책정한다.
- 이는 고급 의사결정이 가능하지만 구현과 운영이 복잡하다.
비용과 리소스 관리
- 지속적인 이벤트 처리는 상당한 컴퓨팅 자원을 소비한다.
- 클라우드 환경에서는 메시지 처리량, 저장 용량, 네트워크 전송에 따라 비용이 증가한다.
- Event Broker의 메시지 보관 기간과 복제 설정을 적절히 조정하여 비용을 최적화해야 한다.
동기 vs 비동기 통신의 조화
- 모든 통신을 비동기로 전환할 필요는 없다.
- 결제 승인처럼 즉각적인 응답이 필요한 경우 동기 호출이 더 적절하다.
- Service Mesh는 동기식 마이크로서비스 간 통신을 관리하며, 로드 밸런싱, 서킷 브레이킹, 서비스 디스커버리를 제공한다.
- Uber는 승객과 드라이버 매칭처럼 즉각적인 응답이 필요한 부분에는 Service Mesh를 통한 동기 통신을 사용하고,
- 위치 업데이트나 분석 데이터 같은 백그라운드 처리에는 EDA를 적용한다.
- 각 서비스에 Sidecar Proxy를 배치하여 트래픽을 관리하고, 이벤트 처리와 동기 호출을 혼합하여 실시간 응답성과 확장성을 동시에 확보한다.
참고 출처
- https://www.infoq.com/articles/scalable-resilient-event-systems/
- https://medium.com/@seetharamugn/the-complete-guide-to-event-driven-architecture-b25226594227
- https://medium.com/sysco-labs/event-driven-architecture-how-enterprises-manage-billions-of-events-b21646384528



