190. 이벤트 기반 아키텍처 (EDA, Event-Driven Architecture)

⚠️ 이 문서는 마이크로서비스(MSA) 간에 서로를 직접 호출(REST API 동기 통신)하다가 하나가 죽으면 도미노처럼 뻗어버리는 강결합의 치명적 문제를 해결하기 위해, **"나는 방금 이런 일을 했어"라는 이벤트(메시지)만 허공(메시지 브로커)에 던져놓고 쿨하게 자기 할 일을 하러 가는 '퍼블리시/서브스크라이브(Pub/Sub) 기반의 완벽한 비동기 디커플링 아키텍처'**를 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: 서비스 A가 서비스 B를 '명령(Command, 너 이거 해라)'하는 구조에서, 서비스 A가 '상태 변화(Event, 나 이거 했음)'를 방송하면 관심 있는 B, C, D가 알아서 그 방송을 듣고 자기 할 일을 처리하는 구조로의 패러다임 전환이다.
  2. 가치: 주문 서비스는 알림 서비스가 서버 점검으로 죽어 있든 말든 신경 쓰지 않고 주문을 0.1초 만에 받아준다. 알림 서비스는 1시간 뒤에 살아나서 밀려있던 방송(Event)을 마저 듣고 고객에게 카톡을 보내면 그만이다 (극한의 장애 격리와 탄력성).
  3. 기술 체계: Kafka(카프카), RabbitMQ, AWS SNS/SQS 같은 이벤트 브로커(Event Broker)가 중앙에 위치하며, 한 번 발행된 이벤트는 절대 수정될 수 없는 불변성(Immutability)을 가져 '이벤트 소싱(Event Sourcing)' 패턴과 찰떡궁합을 이룬다.

Ⅰ. 동기식 호출(REST API)의 도미노 재앙

직접 부르면 멱살이 잡혀 같이 죽는다.

  1. 강결합 (Tight Coupling)의 구조:
    • 고객이 [주문]을 하면, 주문 서비스는 $\rightarrow$ [결제] API를 부르고 $\rightarrow$ [재고] API를 부르고 $\rightarrow$ 마지막으로 [알림] API를 불러 "고객님 결제 성공 카톡"을 보낸다.
  2. 사슬의 붕괴:
    • 하필 [알림] 서버에 트래픽이 몰려 응답이 10초 걸렸다.
    • [주문] 서버는 알림 서버가 응답을 줄 때까지 HTTP 연결을 끊지 못하고 10초 동안 화면을 멈춘 채(Blocking) 기다려야 한다. 대기하는 손님이 1만 명이 되면 주문 서버의 쓰레드(Thread)가 바닥나 주문 시스템 전체가 뻗어버리는 최악의 장애(Cascading Failure)가 터진다.
    • 알림 카톡 하나 못 보낸 것 때문에 수십억 원짜리 주문 결제가 불가능해진 어처구니없는 상황이다.

📢 섹션 요약 비유: 회사에서 결재판을 들고 사장님부터 말단 사원까지 1열로 쭉 서서 앞사람이 도장을 찍어줄 때까지 뒷사람이 아무 일도 못 하고 멍하니 서 있는 것(동기 통신)과 같습니다. 중간에 한 명이라도 화장실에 가면 결재 라인 전체가 마비됩니다.


Ⅱ. 퍼블리시/서브스크라이브 (Pub/Sub)의 해방

허공에 외치면 누군가는 듣고 일한다. 이것이 느슨한 결합(Loose Coupling)이다.

  1. 이벤트 브로커 (Event Broker) 배치:
    • 서비스 사이에 '아파치 카프카(Apache Kafka)' 같은 거대한 게시판을 둔다.
  2. 발행자 (Publisher)의 쿨한 퇴장:
    • 고객이 주문을 하면, 주문 서비스는 게시판에 [이벤트: 주문생성됨 ID=123]이라는 메모를 딱 하나 붙여놓고(Publish) 0.01초 만에 고객에게 "주문 완료!" 화면을 띄워준다. 뒤에서 결제가 되든 알림이 가든 주문 서비스는 신경 끄고 퇴근한다.
  3. 구독자 (Subscriber)들의 자율적 업무:
    • 게시판을 구독(Subscribe)하고 있던 [재고] 서비스와 [알림] 서비스가 메모를 발견한다.
    • 각자 그 메모를 가져가서 자기 페이스대로 창고에서 물건을 빼고, 카톡을 발송한다. 만약 알림 서버가 고장 나 3시간 동안 죽어 있더라도, 메모는 게시판에 안전하게 붙어 있으므로(영속성) 3시간 뒤 살아나서 밀린 메모를 처리하면 그만이다.

📢 섹션 요약 비유: 각 부서 직원을 일일이 찾아가 말로 지시하는 대신, 휴게소에 있는 거대한 '알림판(카프카)'에 "나 방금 계약 따왔음!"이라고 포스트잇을 딱 붙여놓고 영업사원은 쿨하게 퇴근합니다. 그럼 재무팀, 배송팀이 휴게소에 커피 마시러 왔다가 포스트잇을 보고 "어, 계약 따왔네? 내가 계산서 끊고 배송 준비해야지"라며 알아서 일하는 완벽한 자율 분업(비동기) 시스템입니다.


Ⅲ. EDA의 트레이드오프: 추적의 늪과 최종 일관성

우아한 아키텍처지만, 디버깅을 하려면 지옥을 맛보게 된다.

  1. 최종 일관성 (Eventual Consistency)의 수용:
    • 동기 방식은 즉각적으로 모든 DB가 일치하지만, EDA는 비동기로 처리되므로 사용자가 주문 버튼을 누른 직후에는 '주문됨' 상태인데 정작 '재고'는 아직 안 깎여 있을 수 있다. 수 초 뒤에 늦게 깎여 비로소 데이터가 일치하게 되는 시차(Eventual Consistency)를 비즈니스적으로 수용해야 한다.
  2. 트랜잭션 롤백의 불가능 (Saga 패턴 필요):
    • 주문이 게시판에 메모를 붙였고, 재고 서비스가 메모를 보고 물건을 뺐는데, 뒤늦게 결제 서비스가 메모를 보고 카드를 긁으려다 "한도 초과"가 났다.
    • 일반 DB처럼 ROLLBACK 명령어 하나로 엎을 수가 없으므로, 결제 서비스가 다시 [이벤트: 결제실패됨] 메모를 게시판에 붙이고, 재고 서비스가 그걸 보고 "아까 뺀 물건 다시 채워 넣어야지"라며 보상 트랜잭션(Saga Pattern)을 치는 복잡한 코드를 다 짜주어야 한다.
  3. 분산 추적의 난해함:
    • 이벤트가 여러 큐를 타고 이리저리 날아다니므로, "사용자의 주문 1건이 도대체 어디서 막혔나?"를 추적하려면 집킨(Zipkin) 같은 분산 추적(Distributed Tracing) 인프라가 100% 필수적이다.

📢 섹션 요약 비유: 포스트잇(이벤트) 방식은 너무 빠르고 좋지만, 포스트잇이 중간에 바람에 날아가거나(메시지 유실), "아까 그 계약 취소됐대!"라는 두 번째 포스트잇(보상 트랜잭션)을 배송팀이 못 보고 물건을 보내버리는 대형 사고가 날 수 있습니다. 이를 막기 위해 포스트잇마다 고유 번호(Trace ID)를 적고 누가 언제 떼갔는지 cctv(분산 추적)로 꼼꼼히 감시하는 깐깐한 관리가 없으면 난장판이 됩니다.