EDA에도 패턴들이 이렇게나 많이 존재 하다니..
- 개발
- 2026. 2. 19. 23:47
EDA(Event Driven Architecture)가 MQ나 이벤트 리스너를 통해 동작한다는 것은 알고 있었습니다. 하지만 "어느 상황에서 사용해야 하는가?"에 대해서는 명확하게 정리되어 있지 않았습니다. 단순히 도메인 간 의존성을 줄이기 위한 구조 정도로만 이해하고 있었던 것 같습니다. 그러나 공부를 하다 보니, EDA는 단순한 비동기 처리 구조가 아니었습니다. 처리량 문제, 데이터 정합성 문제, 데이터 모델링 문제 등
분산 환경에서 발생하는 다양한 문제를 해결하기 위한 설계 방식이었습니다. 특히 흥미로웠던 점은, 각각의 문제에 따라 적용되는 아키텍처 패턴이 달랐다는 점입니다. Competing Consumer, Saga, Transactional Outbox, Event Sourcing 등 단순히 "이벤트 기반"이라는 공통점만 있을 뿐, 해결하려는 문제는 서로 달랐습니다. 한번 알아봅시다.
시작에 앞서... EDA란 무엇인가?
이벤트라는 것은 무엇일까요? 이벤트는 시스템 내부에서 발생하는 상태 변화(State Change) 를 의미합니다.
예를 들면, 주문이 생성됨, 결제가 완료됨, 회원 정보가 수정됨과 같은 것들입니다.
즉, 무언가 의미 있는 일이 발생했다는 신호라고 볼 수 있습니다.
그렇다면 상태 변화라는 것은 단순히 SQL 한 줄로 해결되는 것 아닐까요?
UPDATE order SET status = 'PAID' WHERE id = 1;
이렇게만 작성해도 상태 값은 변경됩니다. 그렇다면 굳이 EDA를 사용할 이유가 있을까요?
DB는 상태를 저장하는 역할을 합니다. 하지만 저장만으로는 그 상태 변화에 대해 시스템이 반응하지는 않습니다.
물론, repository.save()를 호출한 뒤에 후속 로직을 이어서 작성하면 되는 것 아닌가 생각할 수 있습니다.
하지만 이 방식은 상태 변경과 후속 행동을 하나의 코드 흐름 안에 묶어버립니다.
주문 상태를 변경하면서 결제를 처리하고, 재고를 차감하고, 이메일을 발송하는 로직이 한 서비스 안에 계속 추가된다면 어떻게 될까요?
상태 변화와 반응이 점점 강하게 결합됩니다. 서비스는 점점 더 많은 책임을 가지게 되고, 변경에 취약한 구조가 됩니다.
EDA는 이 지점에서 다른 접근을 제시합니다.
상태를 단순히 저장하는 것이 아니라, 상태가 변경되었다는 사실을 외부에 알리는 것입니다.
이벤트 방식은 변경된 상태를 시스템 전반에 "뿌려주는" 구조라고 이해할 수 있습니다.
누가 그 이벤트에 반응할지는 발행한 쪽에서는 알 필요가 없습니다.
이제 본격적으로 EDA에 대해 알아봅시다.
EDA 핵심 구성요소에는 4가지 구성요소가 존재합니다.
producer, broker, consumer, stream이렇게 4가지가 존재합니다.
그림을 대략적으로 그리면 다음과 같이 그릴 수 있습니다.

producer 상태 변화가 발생했을 때 이벤트를 생성하고, 이를 Broker에 발행(publish)하는 역할
consumer Consumer는 Broker에 저장된 이벤트(Stream)를 구독하고 읽어 처리하는 역할
broker Broker는 이벤트를 저장하고, 관리하고, 구독을 처리하는 중개 시스템 ex) kafka, rabbitMQ
stream Broker 내부에 시간 순서대로 append-only 방식으로 기록되는 이벤트 로그
위 그림을 토대로 설명하자면
- Producer는 이벤트를 Broker에 기록합니다.
- Broker는 그 이벤트를 Stream(append-only 로그)에 저장합니다.
- Consumer는 Stream을 읽습니다.
- Consumer는 자신이 어디까지 읽었는지(offset)를 따로 관리합니다.
이제 알아보자 EDA 패턴
이벤트를 발행하고, Broker에 기록하고, Consumer가 이를 읽어 처리하는 구조 자체는 그리 어렵지 않습니다.
개념만 놓고 보면 단순한 흐름입니다.
하지만 실제 시스템에서는 이야기가 달라집니다.
다음과 같은 문제들이 자연스럽게 발생합니다.
- 메시지가 폭증한다면 어떻게 해야 할까?
- DB 저장은 되었는데, 이벤트 발행이 실패한다면?
- 여러 서비스가 함께 처리하다가 중간에 실패한다면?
- 조회 성능이 점점 느려진다면?
- 상태의 이력을 전부 보존하고 싶다면?
이벤트를 "뿌리는 것"만으로는 이러한 문제를 해결할 수 없습니다.
그래서 EDA 환경에서는 다양한 패턴들이 등장하게 됩니다.
이 문제들을 조금 더 정리해보면, 크게 세 가지 범주로 나눌 수 있습니다.
- 처리량 문제 (Throughput)
- 정합성 문제 (Consistency)
- 데이터 모델링 및 읽기 최적화 문제 (Modeling / Read Optimization)
EDA 패턴은 결국 이 세 가지 문제를 해결하기 위해 등장한 설계 전략이라고 볼 수 있습니다.
처리량 문제
공통 질문은 하나입니다.
메시지가 너무 많아지면 어떻게 처리할 것인가?
Competing Consumer Pattern
- 하나의 큐(또는 토픽)를 여러 Consumer가 경쟁적으로 처리
- 병렬 처리를 통해 처리량(throughput) 증가
- 트래픽 증가 시 스케일 아웃에 적합
Asynchronous Task Execution Pattern
- 작업을 이벤트(메시지)로 넘겨 비동기로 처리
- 실패를 전제로 재시도 정책을 포함
- 일시적인 장애가 있어도 결국 처리(eventual processing) 되도록 설계
정합성 문제
EDA에서 가장 까다로운 영역입니다.
DB 저장과 이벤트 발행이 어긋나면?
여러 서비스가 연쇄적으로 처리하다가 중간에 실패하면?
Transactional Outbox Pattern
- DB 업데이트와 이벤트 기록(outbox insert)을 하나의 트랜잭션으로 묶음
- 이벤트 유실을 방지(저장은 됐는데 발행이 안 되는 상황 방지)
- 이후 별도 프로세스가 outbox를 읽어 브로커로 발행
데이터 모델링 및 읽기 최적화 문제
공통적으로 이런 상황에서 필요합니다.
- 조회 성능이 느리다
- 이력을 남기고 싶다
- 읽기 모델을 따로 만들고 싶다
Consume and Project Pattern
- 이벤트를 소비해 읽기 전용(read model) 뷰를 생성
- CQRS에서 자주 사용
- 조회 성능을 개선하고, 원본 시스템의 부하를 줄임
Event Sourcing
- 현재 상태(state) 대신 이벤트(event)를 저장
- 이벤트 재생(replay)으로 상태 복원 가능
- 감사 로그(audit trail)와 시점 조회(temporal query)에 유리
Event Aggregation Pattern
- 여러 세부 이벤트를 모아 하나의 상위 이벤트로 합침
- 네트워크/처리의 "자잘함(chattiness)"을 줄임
- 소비자 입장에서 처리 흐름을 단순화
마무리
음 패턴이해보다는 EDA가 어떤건지에 대해 집중적으로 학습을 진행했던거 같습니다.
그럼에도 불구하고 이렇게 마무리짓는 이유는 일단 시간이 없기도 하고 볼륨이 너무 커서 일단은 보류하기로 결정하였습니다.
'개발' 카테고리의 다른 글
| 어떻게 트랜잭셕은 Read only 여부를 알 수 있을까? (0) | 2026.02.22 |
|---|---|
| Redis 분산락은 왜 완전히 안전하다고 보기 어려울까? (0) | 2026.02.20 |
| 분산락을 적용해보자! (0) | 2026.02.18 |
| 어째서 JVM을 알아야 하는가? (0) | 2026.02.15 |
| 어째서 메시지 브로커가 왜 필요한가? (0) | 2026.02.13 |