Event-Driven & Reactive Systems
상태 변화를 이벤트로 전파하는 비동기 통신 모델과 시스템 부하에 유연하게 대응하는 반응형 프로그래밍 역학을 다루는 학습 노드입니다.
Article
M
Me
hyunyoun's Blog
system-architecture-distributed-systemssystem-architecturedistributed-systemsevent-drivenreactive-systemscqrslearningkafka9 min read
1. Overview
이벤트 기반 및 반응형 시스템(Event-Driven & Reactive Systems, ERS)은 "A가 끝나야 B를 실행한다"는 전통적인 동기식(Synchronous) 제어 흐름을 부수고, "무언가 일어났음(Event)"을 허공(Message Queue)에 외치면 관심 있는 자들이 알아서 주워 먹는(Pub-Sub) 극단적으로 느슨한 결합의 아키텍처를 다룹니다.
트래픽이 폭주할 때 시스템이 뻗는 이유는, 앞단의 서버가 뒷단의 서버 응답을 목빠지게 기다리느라 스레드(Thread)가 고갈되기 때문입니다. 학습자는 이를 타파하기 위해 이벤트를 비동기적으로 밀어내는 큐(Queue)와 토픽(Topic)의 물리적 완충(Buffering) 효과를 배웁니다. 더불어, 쏟아지는 데이터를 묵묵히 받아내다가도 본인의 한계를 넘어서면 "그만 보내!"라고 역압(Backpressure)을 가하는 반응형 매니페스토(Reactive Manifesto)를 통해, 무너지지 않고 우아하게 저항하는(Resilient) 시스템 생태계를 설계합니다.
2. Scope & Boundaries
In-Scope
- 비동기 모델과 결합도 (Asynchronous Models): 동기식(Request-Response) vs 비동기식(Fire-and-forget), 시간적/공간적 결합 해제.
- 이벤트 아키텍처 패턴 (Event-Driven Architecture): 발행/구독(Publish-Subscribe), 토픽(Topic), 소비자 그룹(Consumer Group), 파티셔닝(Partitioning).
- 반응형 프로그래밍 물리 (Reactive Systems): 리액티브 매니페스토 4대 원칙(Responsive, Resilient, Elastic, Message Driven), 역압(Backpressure) 알고리즘.
- 고급 상태 관리 (State in EDA): 이벤트 소싱(Event Sourcing), CQRS(Command and Query Responsibility Segregation), 아웃박스 패턴(Transactional Outbox).
Out-of-Scope
- 메시지 브로커(Kafka)의 하드웨어 클러스터 구축: Kafka의 리더 선출 파티션 복제나 RabbitMQ Erlang 클러스터링 인프라 구축 08-04. Distributed Messaging 영역으로 위임.
- UI 화면단에서의 이벤트 루프 처리: 브라우저 렌더링에서 발생하는 클릭 이벤트의 JavaScript 비동기 처리 상세 12-01. Front-End Core 영역으로 위임.
Boundaries
- ERS vs. Distributed Messaging (08-04): 08-04(DM)가 '카프카라는 브로커 엔진이 어떻게 초당 백만 메시지를 디스크에 안 깨지고 쓰는가'에 대한 하부 인프라에 집중한다면, ERS는 **'그 카프카를 이용해 주문/결제 마이크로서비스 간의 데이터 흐름(CQRS)과 트랜잭션 롤백(Saga)을 어떻게 설계할 것인가'**라는 상위 아키텍처 패턴에 집중합니다.
3. Counterexample
- 동기식 늪에 빠진 마이크로서비스 (Architecture Fallacy): 모노리스를 쪼개겠답시고 주문 API가 결제 API를 부르고, 결제 API가 재고 API를 HTTP(REST)로 동기(Sync) 호출하게 만든 설계. 재고 서버가 3초간 응답을 안 하면 결제 서버의 스레드가 3초간 물려있고, 결국 맨 앞의 주문 서버까지 연쇄적으로 스레드 풀이 고갈되어 전체 시스템이 멈춥니다(Cascading Failure). 이는 물리적 서버만 쪼갰을 뿐, 논리적으로는 강하게 결합된 분산 모노리스(Distributed Monolith)일 뿐이며, 비동기 **이벤트 버스(Event Bus)**를 활용하지 못한 극악의 안티패턴입니다.
- DB 업데이트와 이벤트 발행의 듀얼 라이트 (Transactional Fallacy): 애플리케이션 코드가 데이터베이스에
UPDATE를 친 직후, 카프카에이벤트 발생메시지를 쏘도록(Dual Write) 짠 로직. DB에는 커밋됐는데 카프카 쏘기 직전에 서버가 죽으면, DB 상태는 변했지만 다른 서비스들은 그 사실을 영원히 모르게 됩니다. 이 치명적인 분산 트랜잭션의 한계를 극복하기 위한 **아웃박스 패턴(Outbox Pattern)**이나 CDC(Change Data Capture) 물리 법칙을 무시한 행위입니다.
4. Prerequisites
- 분산 시스템의 원리 (Basic): 네트워크로 떨어진 시스템 간에 메시지가 유실되거나 중복 전송될 수 있다는 기본 한계를 인지해야 합니다. (07-02. DPC)
- 자료 구조와 병행성 (Recommended): 스트림 처리(Stream Processing)를 이해하려면, 큐(Queue) 구조와 비차단 I/O(Non-blocking)에 대한 메커니즘을 숙지하는 것이 좋습니다. (03-02. PCM)
5. Learning Map
6. Learning Topics
Basic
Core Topic 01: 이벤트 기반 아키텍처와 시공간 분리 (EDA & Pub-Sub)
- Why to Learn: 회원 가입 로직에 '웰컴 이메일 발송', '포인트 지급' 코드가 스파게티처럼 엉겨 붙어 코드를 수정할 수 없게 되는 참사를 막기 위함입니다.
- What to Learn:
- Concepts: 이벤트 기반 아키텍처(EDA), 동기(Synchronous) vs 비동기(Asynchronous) 차이.
- Skills: 발행-구독(Publish-Subscribe) 모델 패턴, 메시지 브로커를 통한 공간적 결합 해제(Sender는 Receiver의 IP를 모름)와 시간적 결합 해제(Receiver가 죽어있어도 Sender는 보낼 수 있음).
- Tools: RabbitMQ, Apache Kafka 기초 개념 구조.
- Trade-offs: 시스템 확장 시 결합도가 극도로 낮아져 새로운 소비자(Consumer) 모듈을 무한정 붙이기 좋은 극강의 확장성 vs 시스템 전체 흐름이 어디서 어떻게 흘러가는지 한눈에 추적하기 어려워지는 디버깅 지옥.
- How to Learn:
- 1단계: '결제 모듈'이 3개의 타겟 모듈(포인트, 알림, 배송) API를 직접 호출하던 1<3>3> 동기식 연결망(Mesh)을 그려, 타겟 1개가 지연되면 결제 전체가 느려지는 현상을 증명합니다.
- 2단계: 결제 모듈은 "결제 완료 이벤트"를 중앙 토픽(Topic)에 던지고 0.1초 만에 응답을 끝내버리고, 타겟 모듈 3개가 알아서 이벤트를 줏어가서 각자 처리하는 1
비동기 스트림으로 재설계합니다.
- Implement: 파이썬
asyncio와 단순한 인메모리Queue를 활용하여, 이벤트를 생성하는 생산자(Producer) 코루틴이 이벤트를 큐에 던지면, 3개의 개별 소비자(Consumer) 코루틴이 각자의 속도로 이를 가져다 처리하는 로컬 비동기 엔진.
Recommended
Core Topic 02: 반응형 프로그래밍과 역압 (Reactive & Backpressure)
- Why to Learn: 1,000명의 동시 접속자를 버티는 서버가 10,000명이 몰렸을 때 뻗어버리는 게 아니라, 처리할 수 있는 만큼만 부드럽게 넘겨받아 '느려지더라도 절대 죽지는 않는' 회복성(Resilience)을 부여하기 위해서입니다.
- What to Learn:
- Concepts: 리액티브 매니페스토(응답성, 탄력성, 회복성, 메시지 구동), 논블로킹(Non-blocking) I/O.
- Skills: 옵저버(Observer) 패턴 기반의 스트림 데이터 처리, 푸시(Push) vs 풀(Pull) 모델, 역압(Backpressure) 메커니즘.
- Tools: RxJS, Project Reactor, Akka Streams.
- Trade-offs: 스레드가 블로킹되지 않으므로 소수의 스레드만으로 수만 개의 요청을 감당하는 미친 리소스 효율성 vs 전통적인
try-catch나for문을 쓸 수 없고 함수형 스트림 연산자(Map/FlatMap/Filter) 조합법을 새로 익혀야 하는 살인적인 러닝 커브.
- How to Learn:
- 1단계: 초당 1,000개의 이벤트가 생산자(Publisher)에서 폭포수처럼 떨어지는데 소비자(Subscriber)는 초당 10개밖에 처리 못 할 때, 메모리 큐가 가득 차 서버가 OOM(Out of Memory)으로 사망하는 상황을 시뮬레이션합니다.
- 2단계: 소비자가 생산자에게 "나 지금 10개 처리했으니, 딱 10개만 더 줘(Request(n))"라는 피드백 신호를 올려보내어, 상류의 데이터 방출 속도를 물리적으로 제어(Backpressure)하는 리액티브 스트림 방어막을 칩니다.
- Implement: 무한히 증가하는 숫자 스트림을 생성하는 퍼블리셔(Publisher)와, 이를 받아 0.5초 대기(Sleep) 후 출력하는 서브스크라이버(Subscriber)를 구성하되, 역압(Backpressure) 버퍼 용량을 초과하면 오래된 데이터를 자동으로 버리는(Drop) 생존 로직 작성.
Practical
Core Topic 03: 이벤트 소싱과 CQRS (Event Sourcing & CQRS)
- Why to Learn: 은행 시스템에서 "현재 잔고 5만 원"이라는 최종 결과값(State) 하나만 덮어쓰기로 저장했다가, 누군가 해킹으로 값을 조작했을 때 과거 내역을 100% 추적(Audit) 및 복원하기 위해 설계의 차원을 통째로 뒤엎기 위해서입니다.
- What to Learn:
- Concepts: 이벤트 소싱(Event Sourcing), CQRS(Command and Query Responsibility Segregation).
- Skills: 상태를 이벤트의 덧셈(Fold/Reduce)으로 도출하는 물리, 쓰기 전용 스토어(Append-only Log)와 읽기 전용 뷰(Materialized View)의 분리, 스냅샷(Snapshot)을 통한 재생(Replay) 성능 최적화.
- Tools: EventStoreDB, Apache Kafka.
- Trade-offs: 언제든 과거 특정 시점의 데이터 상태로 완벽히 시간 여행(Time-travel)이 가능하고 디버깅이 투명해지는 절대적 감사(Auditing) 추적성 vs "유저의 현재 포인트 조회"라는 단순한 기능조차 수백 개의 이벤트를 합산하거나 별도의 읽기 DB를 구성해야 하는 극심한 구조적 복잡도.
- How to Learn:
- 1단계: RDBMS의
UPDATE users SET balance = 500방식이 기존 1000이라는 데이터를 영구 파괴(Destructive Write)함을 인지합니다. - 2단계: 이를 버리고, "1000 생성", "-200 인출", "-300 인출" 이라는 사실(Event)만 변경 불가능한 로그로 덧붙여(Append) 저장한 뒤, 조회(Query) 요청이 오면 이 로그를 처음부터 쭉 재생(Reduce)하여 500이라는 상태를 도출하는 패러다임 시프트를 훈련합니다.
- 1단계: RDBMS의
- Implement: 쇼핑 카트의 상품 담기/빼기 이벤트를 순차적으로 배열에 푸시(Push)하는 쓰기 모델(Command)과, 주기적으로 해당 배열을 순회하여 현재 카트 상태를 JSON으로 만들어 캐시해 두는 읽기 모델(Query)을 완전히 분리한 미니 CQRS 엔진.
Advanced
Core Topic 04: 분산 정합성과 아웃박스/사가 패턴 (Distributed Consistency)
- Why to Learn: 하나의 트랜잭션이 여러 마이크로서비스에 걸쳐 있을 때, 일부만 성공하고 일부는 실패하는 최악의 데이터 파편화 현상을 이벤트 기반 복구 로직으로 완벽히 통제하기 위해서입니다.
- What to Learn:
- Concepts: 아웃박스 패턴(Transactional Outbox), 듀얼 라이트(Dual-write) 문제, 사가 패턴(Saga Pattern: Choreography vs Orchestration).
- Skills: 최종적 일관성(Eventual Consistency) 도달 시간 계산, 보상 트랜잭션(Compensating Transaction) 물리 설계, 멱등성(Idempotency) 키 제어.
- Tools: Debezium(CDC), AWS Step Functions.
- Trade-offs: 이벤트가 꼬이지 않게 중앙 관리자(Orchestrator)를 두면 흐름이 명확해지지만 중앙 통제기가 죽으면 멈추는 SPOF 리스크 vs 마이크로서비스끼리 서로 릴레이하듯 알아서 이벤트를 토스(Choreography)하게 만들면 병목은 없으나 트랜잭션의 생사를 추적하기가 기하급수적으로 복잡해지는 제어권 박탈.
- How to Learn:
- 1단계: DB 업데이트는 성공했는데 메시지 큐 전송에 실패(Dual-write 문제)하여 시스템 상태가 박살 나는 상황을 해결하기 위해, DB 내부에
outbox_events테이블을 만들어 본 데이터와 한 트랜잭션으로 커밋하고 별도 릴레이 데몬이 이를 읽어 메시지 큐로 쏘는 원자적 전송 뼈대를 스케치합니다. - 2단계: "주문 완료 재고 차감 결제 실패" 순서로 분산 로직이 흘렀을 때, RDBMS의
ROLLBACK명령이 통하지 않으므로, 재고 마이크로서비스로 "재고 차감 취소(보상) 이벤트"를 다시 발행하여 논리적인 롤백(Saga)을 완성하는 그래프를 그립니다.
- 1단계: DB 업데이트는 성공했는데 메시지 큐 전송에 실패(Dual-write 문제)하여 시스템 상태가 박살 나는 상황을 해결하기 위해, DB 내부에
- Implement: 3단계 분산 프로세스를 모사하여, 1/2단계는 성공 콘솔을 찍다가 3단계 스레드에서 고의로
Exception을 발생시키면, catch 블록에서 2단계와 1단계의 '취소 API'를 역순으로 호출하여 상태를 원래대로 복원하는 텍스트 기반 사가 코레오그래피 시뮬레이션.
7. Terminology
8. References
Primary References
- [P1] CS2023 - AR/Distributed Systems — Event interaction.
- [P2] SWEBOK - Software Construction — Event-based execution models.
Secondary References
- [Enterprise Integration Patterns] Gregor Hohpe — The definitive guide to messaging.
- [Reactive Design Patterns] Roland Kuhn — Practical reactive structures.
Industry References
- [The Reactive Manifesto] — Foundational principles of modern reactive web.
- [Confluent: Event Driven Architecture Guide] — Industrial scale event patterns.
9. Final Checklist
Primary Checklist
- 동기식(Sync) 호출 방식이 서비스 간의 연쇄 장애(Cascading Failure)를 물리적으로 어떻게 유발하는지 설명 가능한가? (P1, P5)
- '이벤트'가 발생했다는 사실만으로 어떻게 서로 다른 데이터베이스의 정합성을 결국 맞추게(Eventual Consistency) 되는지 논리를 기술할 수 있는가? (P1)
Secondary Checklist
- 소비자의 처리 속도가 느려질 때 메시지 큐의 점유량이 시스템 메모리에 미치는 물리적 임팩트를 예측하고 있는가?
- 멱등(Idempotent) 설계가 부재한 시스템에서 네트워크 재시도(Retry)가 발생했을 때 데이터가 중복 생성되는 시나리오를 식별 가능한가?
Industry Checklist
- 실무 마이크로서비스 설계 시, 비즈니스 트랜잭션의 신뢰성을 위해 '아웃박스 패턴(Outbox Pattern)'의 필요성을 제안할 수 있는가? (SFIA)
- 고성능 실시간 대시보드 구축을 위해 폴링(Polling) 대신 리액티브 스트림을 선택해야 하는 물리적 비용 근거를 제시 가능한가?