이벤트 기반 아키텍처(EDA) 설계: 트랜잭셔널 메시징과 데이터 일관성
이벤트 기반 아키텍처(EDA)의 핵심 구성 요소와 비동기 메시징 흐름을 정의합니다. 특히 데이터베이스 업데이트와 이벤트 발행의 원자성을 보장하는 Transactional Outbox 패턴을 중심으로, 분산 시스템에서 신뢰성 있는 메시지 전송과 데이터 일관성을 구현하기 위한 설계 원칙을 다룹니다.
이벤트 기반 아키텍처 (Event-Driven Architecture, EDA)
이벤트 기반 아키텍처는 실시간으로 발생하는 이벤트를 중심으로 설계된 현대적인 소프트웨어 아키텍처 패턴입니다. 이 아키텍처에서는 사용자 인터페이스(UI)에서 버튼 클릭, 온라인 쇼핑몰 장바구니에 아이템 추가, 결제 완료 알림 등과 같은 이벤트를 처리하고, 발생하는 즉시 이를 소비하는 시스템으로 전달합니다. 전통적인 request/response 아키텍처가 주로 정의된 주기적 요청에 반응하고, 사용자가 명시적으로 요청한 사항에 대해 처리하는 방식이라면, 이벤트 기반 아키텍처는 비동기적이고 실시간으로 발생하는 이벤트에 기반하여 시스템을 처리합니다.
핵심 구성 요소
이벤트 기반 아키텍처는 이벤트를 중심으로 설계되며, 이 이벤트는 시스템의 출발점이자 처리 과정입니다. 이 아키텍처에서 주요 구성 요소는 다음과 같습니다:
-
Publisher Publisher는 이벤트를 생성하고 이를 브로커나 메시징 시스템을 통해 전달하는 역할을 합니다. 이벤트 데이터는 중앙 저장소나 큐에 저장될 수 있으며, 이는 후속 처리를 위한 데이터 흐름을 만들어냅니다.
-
Subscriber Subscriber는 특정 이벤트를 수신하여 이를 처리하는 시스템이나 컴포넌트입니다. Subscriber는 관심 있는 이벤트를 수신하고, 이를 바탕으로 특정 동작을 수행하거나, 다른 시스템과의 연계를 통해 추가 처리를 진행합니다.
-
Sources 이벤트가 발생하는 출처입니다. 사용자 입력, 시스템 상태 변화, 외부 시스템의 알림 등이 모두 소스가 될 수 있습니다. 이벤트의 소스는 다양한 형태로 존재할 수 있으며, 이를 통해 실시간 데이터 흐름이 시작됩니다.
-
Sinks 싱크는 이벤트가 최종적으로 전달되는 목적지입니다. 구독자가 처리한 이벤트 데이터는 저장소, 데이터베이스, 다른 애플리케이션의 API 등으로 전송되어 최종 결과를 저장하거나 후속 처리를 진행합니다.
이벤트 처리 흐름
이벤트 기반 아키텍처의 핵심은 이벤트 처리의 흐름입니다. 일반적으로 다음과 같은 과정으로 이루어집니다:
-
이벤트 생성: 사용자의 행동이나 시스템의 변화로 인해 이벤트가 발생합니다. 예를 들어, 사용자가 온라인 쇼핑몰에서 상품을 장바구니에 추가하면 장바구니 추가 이벤트가 발생합니다.
-
이벤트 발행: 이벤트는 출판자에 의해 발생하고, 이를 메시징 시스템(예: Kafka, RabbitMQ)이나 이벤트 브로커를 통해 전파합니다.
-
이벤트 소비(consume): Subscriber는 관심 있는 이벤트를 구독하여 수신하고, 수신된 이벤트에 따라 적절한 처리를 수행합니다. 예를 들어, 결제 시스템은 결제 완료 이벤트를 구독하여 결제 결과에 따른 후속 처리를 진행합니다.
-
결과 전달: 이벤트 처리 결과는 싱크로 전달되며, 여기서 데이터가 저장되거나, 추가적인 시스템과의 통합이 이루어집니다.
이벤트 기반 아키텍처의 장점
- 실시간 응답성 (Real-Time Responsiveness) 이벤트 기반 아키텍처는 이벤트를 실시간으로 감지하고 즉시 반응할 수 있습니다. 이를 통해 사용자나 다른 시스템에서 발생하는 이벤트에 대해 low latency로 반응할 수 있으며, 사용자 경험이 더욱 인터랙티브하고 몰입감 있게 개선됩니다.
- 확장성 및 유연성 (Scalability and Flexibility) 채팅 및 메시징 애플리케이션처럼 수많은 동시 사용자와 메시지를 처리해야 하는 시스템에서는 확장성이 매우 중요합니다. 이벤트 기반 아키텍처는 수평 확장을 지원하며, 시스템의 부하를 여러 인스턴스나 마이크로서비스에 분배할 수 있습니다. 이를 통해 트래픽 증가나 변동하는 수요에 원활하게 적응할 수 있으며, 성능이나 신뢰성을 저하시키지 않고 확장할 수 있습니다.
- 느슨한 결합 및 모듈화 (Loose Coupling and Modularity) 이벤트 기반 아키텍처는 애플리케이션 구성 요소 간의 **느슨한 결합(loose coupling)**을 촉진합니다. 각 구성 요소는 독립적으로 작동하며, 하나의 구성 요소에 대한 변경이 다른 구성 요소에 영향을 미치지 않도록 합니다. 이러한 모듈화 덕분에 구성 요소는 시스템 내의 다른 부분이나 다른 애플리케이션에서 재사용할 수 있어, 개발 시간과 비용을 절감할 수 있습니다.
- 비동기 처리(Asynchronous Processing) 이벤트 기반 아키텍처는 비동기적인 특성을 가지므로, 시스템은 이벤트가 발생하는 즉시 반응할 수 있으며, 다른 작업을 차단하지 않고 독립적으로 처리할 수 있습니다. 이는 실시간으로 발생하는 데이터나 요청에 빠르게 대응할 수 있게 합니다.
- 확장성 및 통합 (Extensibility and Integration) 이벤트 기반 아키텍처는 이벤트를 활용하여 타사 API, 서비스, 데이터베이스와의 통합을 쉽게 할 수 있게 합니다. 이를 통해 애플리케이션의 기능 확장 및 호환성을 높일 수 있으며, 예를 들어 사용자 인증, 데이터 저장소, 분석 기능 등 추가적인 기능을 손쉽게 구현할 수 있습니다.
- 보안 및 개인정보 보호 (Security and Privacy) 이벤트 기반 아키텍처는 실시간 채팅이나 메시징 애플리케이션에서 필요한 보안 기능을 제공할 수 있는 강력한 토대를 제공합니다. 개발자는 액세스 제어, 인증, 암호화와 같은 보안 조치를 다양한 수준에서 구현할 수 있습니다. 이를 통해 무단 접근을 방지하고 민감한 정보를 안전하게 보호할 수 있습니다.
- 자원 소비 절감 (Reduced Resource Consumption) 이벤트 기반 아키텍처는 푸시 기반으로 동작하므로 이벤트가 발생할 때만 이를 처리합니다. 이 방식은 지속적인 폴링을 필요로 하지 않기 때문에 CPU 자원 소모와 대역폭 소비를 줄여줍니다. 시스템 자원 사용을 최적화하고, 불필요한 자원 낭비를 줄이는 데 효과적입니다.
이벤트 기반 아키텍처(EDA)는 실시간 이벤트를 처리하고, 비동기적이며 확장 가능한 시스템을 구축할 수 있는 유연한 소프트웨어 아키텍처입니다. 이벤트의 생성, 전파, 소비, 처리 과정을 통해 다양한 시스템이 협력하며 효율적으로 동작할 수 있도록 도와줍니다. 이는 특히 마이크로서비스, 스트림 처리, 실시간 시스템 등에 유용하게 활용됩니다.
트랜잭셔널 메시징: 신뢰성 있는 이벤트 발행
💡 아키텍처 설계 가이드: 이 섹션에서 다루는 데이터 일관성 패턴은 Chris Richardson의 저서 **Microservices Patterns**의 핵심 설계 원칙을 바탕으로 합니다.
분산 시스템 및 마이크로서비스 아키텍처(MSA)에서 이벤트 기반 아키텍처를 적용할 때, 데이터베이스 상태 변경과 메시지 발행 간의 일관성을 유지하도록 설계되어야 합니다.
일반적으로 비즈니스 로직을 통해 데이터베이스를 업데이트한 뒤 메시지 브로커로 이벤트를 전송하는데, 두 작업이 서로 다른 시스템에서 수행되기 때문에 하나의 트랜잭션으로 처리할 수 없습니다.
이로 인해 둘 중 하나만 성공하는 이중 쓰기(Dual Write) 문제가 발생할 수 있으며, 결과적으로 시스템 간 데이터 정합성을 훼손할 수 있습니다.
👉 이러한 문제를 해결하기 위한 대표적인 접근 방식이 Transactional Outbox 패턴입니다.
1. Transactional Outbox 패턴
이 패턴은 데이터베이스 내부에 OUTBOX라는 테이블을 두어 임시 메시지 저장소로 활용합니다.
RDBMS 환경에서는 비즈니스 객체를 생성, 수정, 삭제하는 동일한 로컬 ACID 트랜잭션 안에서 발행할 이벤트를 OUTBOX 테이블에 함께 저장합니다. 이를 통해 비즈니스 데이터 변경과 메시지 저장이 하나의 트랜잭션으로 묶이며, 원자성을 보장할 수 있습니다.
NoSQL 데이터베이스의 경우에도 데이터 모델에 따라 유사한 방식으로 구현할 수 있습니다. 예를 들어, 엔티티 레코드에 이벤트 정보를 포함시키고 단일 연산으로 함께 기록하거나, 변경 스트림 기능을 활용하여 이벤트를 추출하는 방식 등이 사용됩니다.
2. 메시지 발행 메커니즘 (Message Relay)
OUTBOX 테이블에 저장된 메시지를 메시지 브로커(예: Kafka, RabbitMQ 등)로 전달하는 구성 요소를 Message Relay라고 합니다. 이 메커니즘은 일반적으로 다음 두 가지 방식으로 구현됩니다.
A. Polling Publisher 패턴
별도의 프로세스나 스케줄러가 주기적으로 OUTBOX 테이블을 조회하여 메시지를 발행하는 방식입니다.
- 조회:
SELECT * FROM OUTBOX ORDER BY ... ASC와 같은 쿼리로 아직 발행되지 않은 메시지를 조회합니다. - 발행: 메시지를 브로커로 전송합니다.
- 상태 변경: 처리 완료된 메시지는
DELETE쿼리로 테이블에서 삭제하거나 처리 완료 상태로 갱신합니다.
- 장점: 구현이 단순하고 SQL 기반으로 쉽게 적용할 수 있습니다.
- 단점: 주기적인 조회로 인해 데이터베이스 부하가 증가할 수 있으며, 대량 트래픽 환경에서는 적절한 튜닝이 필요합니다.
B. Transaction Log Tailing 패턴
데이터베이스의 **트랜잭션 로그(Commit Log)**를 직접 추적하여 변경 사항을 감지하는 방식입니다. 애플리케이션이 테이블을 조회하는 대신, 데이터베이스의 변경 로그(예: MySQL binlog, PostgreSQL WAL)를 기반으로 이벤트를 발행합니다.
이 방식은 변경 사항을 보다 빠르게 감지할 수 있으며, 불필요한 조회를 줄일 수 있다는 장점이 있습니다. 또한 RDBMS뿐만 아니라 일부 NoSQL 데이터베이스에서도 유사한 스트림 기능을 통해 구현할 수 있습니다.
대표적인 활용 도구는 다음과 같습니다:
1. Debezium: 범용 CDC 표준
데이터베이스(MySQL, Postgres, MongoDB 등)의 변경 사항을 캡처하여 Apache Kafka로 전달하는 강력한 오픈소스 CDC(Change Data Capture) 플랫폼입니다.
- ✔ 주요 강점
- 폭넓은 호환성
- MySQL, PostgreSQL, Oracle, SQL Server, MongoDB 등 다양한 데이터베이스를 지원합니다.
- 유연한 배포 구조
- Kafka Connect 기반으로 확장성과 고가용성을 확보할 수 있으며, Debezium Server를 통해 Kafka 없이 Kinesis, Pub/Sub 등으로도 전송 가능합니다.
- 스키마 변경 대응 (Schema Evolution)
-
스키마 변경 상황에서도 파이프라인이 쉽게 깨지지 않도록 구성할 수 있습니다.
Debezium은 Schema History Topic을 통해 변경 이력을 관리하고, 변경된 구조를 이벤트에 반영합니다. 또한 Schema Registry와 연동할 경우, 호환성 정책을 적용해 컨슈머 영향도를 줄일 수 있습니다.
-
- 폭넓은 호환성
2. LinkedIn Databus: 고성능 독립형 CDC
LinkedIn 내부에서 사용하기 위해 개발된 CDC 시스템으로, 미들웨어 의존성을 최소화한 독립적인 구조를 가집니다.
- ✔ 주요 강점
- 미들웨어 독립성:
- 클라이언트가 Databus Relay에 연결하여 이벤트를 구독하는 구조로, 별도의 메시징 브로커(Kafka) 없이 동작합니다.
- 데이터 재처리 (Infinite Lookback):
- 특정 시점부터 데이터를 다시 읽어올 수 있어 장애 복구나 재처리에 유리합니다.
- 저지연 처리:
- 중간 계층을 최소화한 구조로 빠른 데이터 전달이 가능합니다.
- 미들웨어 독립성:
3. DynamoDB Streams: AWS 관리형 CDC
DynamoDB 테이블에서 발생하는 변경 사항을 시간 순서대로 기록하여 스트림 형태로 제공하는 AWS 완전 관리형 서비스입니다.
- ✔ 주요 강점
- 운영 부담 최소화
- 별도의 인프라 없이 바로 사용할 수 있으며 스케일링과 장애 처리는 AWS가 담당합니다.
- AWS 생태계 통합
- AWS Lambda와 직접 연동되어 실시간 로직 트리거(예: 알림 발송, 데이터 가공)를 쉽게 구성할 수 있습니다.
- 순서 보장
- 변경 이벤트의 순서를 유지하여 데이터 일관성을 확보할 수 있습니다.
- 운영 부담 최소화
💡 솔루션 비교 요약
| 구분 | Debezium | LinkedIn Databus | DynamoDB Streams |
|---|---|---|---|
| 본질 | 범용 CDC 이벤트 로그 생성 | 내부 데이터 전파 아키텍처 | 서버리스 이벤트 CDC |
| 핵심 설계 | 트랜잭션 로그 기반 표준화 | 브로커 제거 + 직접 전달 | AWS 네이티브 이벤트 계층 |
| 운영 방식 | Kafka / Connect 중심 | 자체 Relay 기반 | 완전 관리형 (Serverless) |
| 확장성 | 강력한 생태계 기반 확장성 | 성능은 높으나 범용성 제한 | AWS 리전 내 자동 확장 |
| 핵심 가치 | 이벤트 표준화 + 데이터 재현성 | 초저지연 데이터 분배 | 운영 부담 없는 이벤트 처리 |
3. 메시징 라이브러리와 프레임워크 활용
메시지 브로커의 저수준 클라이언트 라이브러리를 사용하는 경우, 비즈니스 로직이 인프라에 결합되고 반복적인 코드가 증가할 수 있습니다.
이러한 복잡성을 줄이기 위해 Eventuate Tram과 같은 고수준 메시징 프레임워크를 활용할 수 있습니다. 해당 프레임워크는 메시지 처리와 관련된 일부 공통 기능을 추상화하여 개발 생산성을 높이는 데 도움을 줍니다.
구체적으로 다음과 같은 기능을 제공합니다.
- 트랜잭셔널 메시징 지원: Transactional Outbox 패턴을 통해 데이터베이스를 임시 메시지 저장으로 활용하고, 이를 브로커로 전달하는 발행 메커니즘(Message Relay)의 전 과정을 프레임워크가 추상화하여 내부적으로 처리합니다. 개발자는 복잡한 트랜잭션 제어 로직을 직접 구현할 필요 없이 안전하게 이벤트를 발행할 수 있습니다.
- 중복 메시지 감지: 분산 환경에서 발생할 수 있는 메시지 중복 전달 문제를 해결합니다. 프레임워크는 Consumer 측에서 수신된 메시지의 중복 여부를 자동으로 감지하고 필터링함으로써 멱등성(Idempotency) 있는 처리를 보장합니다.
- 고수준 메시징 패턴 지원: 단순한 메시지 전송을 넘어, 애그리거트(Aggregate) 상태 변화에 따른 도메인 이벤트 발행(Domain Event Publishing), 그리고 비동기 명령/응답(Command/Reply) 기반 메시징 등 복잡한 비즈니스 협업 설계에 최적화된 고수준 인터페이스를 제공합니다.
📖 참고: 특정 프레임워크의 도입이 부담스럽다면, 해당 프레임워크가 구현하고 있는 아키텍처 패턴의 원리를 먼저 이해하는 것이 중요합니다. **Microservices Patterns (Manning)**에서는 이러한 패턴들을 직접 구현할 수 있는 상세한 가이드와 Java 예제를 제공합니다.
Reference
- 📚Microservices Patterns With examples in Java (Chris Richardson)
- 🔗 Hazelcast - Event-Driven Architecture
- 🔗 The Complete Guide to Event Driven Architecture (Medium)
- 🔗 An Easy Path from API-based Microservices to an Event Driven Architecture (Medium)
- 🔗 Event-Driven Architecture: Building Loosely Coupled Distributed Systems (Medium)
Tags: Event-Driven Architecture, EDA, Microservices, MSA, Transactional Outbox, Eventual Consistency, Debezium, Distributed Systems, Software Architecture