본문 바로가기
N8N 워크플로우 N8N 워크플로우

Event-Driven Architecture의 숨겨진 병목 지점

읽는 시간 약 7분
event driven architectureec9d98 ec88a8eab2a8eca784 ebb391ebaaa9 eca780eca090 1791292795

Event-Driven Architecture는 왜 느려진 서비스 하나를 숨기지 못했을까

Event-Driven Architecture의 숨겨진 병목 지점

수많은 엔지니어는 Event-Driven Architecture(EDA)를 도입하면 서비스 간 결합도가 낮아지고 장애 전파도 줄어든다고 생각한다. 실제로 주문 서비스가 장애를 일으켜도 결제 서비스나 알림 서비스가 직접 영향을 받지 않는 구조를 만들 수 있다. 그래서 EDA는 흔히 “느슨하게 결합된 아키텍처”라고 불린다.

하지만 실제 운영 환경에서는 예상과 다른 상황이 자주 발생한다. 특정 컨슈머 하나가 느려졌을 뿐인데 전체 이벤트 흐름이 밀리고, 결국 여러 서비스가 동시에 지연되는 현상이 나타난다. 서비스는 분리되어 있는데 왜 성능 문제는 분리되지 않았을까?

필자가 여러 이벤트 기반 시스템을 운영하면서 경험한 공통점은 하나였다. EDA는 장애를 숨길 수는 있어도 병목까지 숨기지는 못한다는 것이다. 오히려 비동기 구조 때문에 병목이 늦게 발견되고 더 넓게 확산되는 경우가 많다.


서비스는 분리됐지만 데이터 흐름은 연결되어 있다

EDA에서 가장 흔한 착각은 서비스 독립성과 처리 독립성을 같은 개념으로 보는 것이다.

예를 들어 주문 이벤트가 발생하면 다음과 같은 흐름이 만들어진다.

  • 주문 서비스 → 주문 생성 이벤트 발행
  • 재고 서비스 → 재고 차감
  • 결제 서비스 → 결제 승인
  • 알림 서비스 → 문자 발송
  • 분석 서비스 → 통계 적재

서비스들은 서로 직접 호출하지 않는다.

겉으로 보면 완벽하게 독립적이다.

하지만 실제로는 모두 동일한 이벤트 스트림을 공유한다.

만약 재고 서비스가 이벤트를 처리하지 못하고 적체되기 시작하면 해당 파티션의 Consumer Lag이 증가한다.

처음에는 재고 서비스만 느려진 것처럼 보인다.

하지만 시간이 지나면 이벤트 저장 공간이 증가하고, 브로커 디스크 사용량이 상승하며, 복제 트래픽이 늘어나고, 결국 다른 컨슈머들의 처리 성능까지 영향을 받기 시작한다.

즉 서비스는 독립적이지만 이벤트 흐름은 하나의 생태계처럼 연결되어 있는 것이다.


병목은 이벤트 브로커에서 증폭된다

많은 팀이 Kafka나 RabbitMQ를 도입하면 트래픽 흡수가 가능하다고 생각한다.

실제로 브로커는 순간적인 부하를 흡수하는 완충 장치 역할을 한다.

문제는 완충 장치가 영구적인 해결책은 아니라는 점이다.

컨슈머가 처리하는 속도보다 이벤트가 빠르게 유입되면 브로커 내부에서는 다음 현상이 발생한다.

  1. Queue Depth 증가
  2. Consumer Lag 증가
  3. 디스크 사용량 증가
  4. 복제 트래픽 증가
  5. 브로커 응답시간 증가

이 시점부터는 단순히 특정 서비스가 느린 것이 아니라 메시지 브로커 자체가 병목 지점으로 변한다.

EDA에서 자주 발생하는 장애는 서비스가 아니라 브로커가 과부하 상태에 진입하면서 시작된다.


가장 위험한 병목은 데이터베이스다

실무에서 가장 많이 보았던 병목은 메시지 브로커가 아니었다.

오히려 컨슈머 뒤에 있는 데이터베이스였다.

예를 들어 재고 서비스가 이벤트를 받아 데이터베이스를 업데이트한다고 가정해보자.

평소에는 초당 1,000건을 처리하던 서비스가 특정 인덱스 문제로 인해 초당 100건밖에 처리하지 못하게 된다.

이 순간 이벤트 적체가 시작된다.

컨슈머는 느려지고 Lag은 증가한다.

브로커는 이벤트를 계속 저장한다.

서비스는 살아있지만 데이터 흐름은 사실상 멈춘 상태가 된다.

EDA 환경에서는 데이터베이스 병목이 브로커 병목으로 전환되고, 다시 전체 이벤트 시스템의 병목으로 확산되는 경우가 매우 많다.


재시도가 문제를 더 크게 만든다

느려진 서비스 하나가 전체 시스템을 흔드는 이유 중 하나는 재시도 로직 때문이다.

처리에 실패한 이벤트가 발생하면 대부분의 시스템은 자동 재시도를 수행한다.

문제는 이미 과부하 상태인 서비스에 동일한 요청이 반복적으로 유입된다는 점이다.

예를 들어 1,000건이 실패하면:

  • 최초 요청 1,000건
  • 1차 재시도 1,000건
  • 2차 재시도 1,000건
  • 3차 재시도 1,000건

총 4,000건의 이벤트가 생성된다.

장애가 발생했는데 트래픽은 오히려 증가하는 것이다.

이 현상을 Retry Storm이라고 부른다.

EDA 환경에서 재시도 폭풍은 브로커 적체를 가속하고 Consumer Lag을 폭발적으로 증가시키는 주요 원인이다.


Consumer Lag은 가장 중요한 건강 지표다

EDA 운영에서 가장 먼저 확인해야 하는 지표는 CPU도 아니고 메모리도 아니다.

바로 Consumer Lag이다.

Consumer Lag은 생산된 이벤트와 처리된 이벤트의 차이를 의미한다.

이 수치가 계속 증가한다면 시스템은 이미 처리 능력을 초과한 상태다.

특히 다음과 같은 패턴이 보인다면 위험 신호다.

  • 이벤트 생산량은 일정함
  • 컨슈머 CPU는 낮음
  • Lag은 지속적으로 증가함

이 경우 대부분 데이터베이스 락, 외부 API 지연, 네트워크 문제 같은 숨겨진 병목이 존재한다.

실제로 많은 장애는 CPU 사용률이 정상인데도 발생한다.


EDA가 자동으로 확장성을 제공하지 않는 이유

EDA를 도입하면 흔히 “자동으로 확장 가능한 구조”가 된다고 생각한다.

하지만 현실은 다르다.

브로커는 확장할 수 있다.

컨슈머도 늘릴 수 있다.

하지만 다음 요소들은 쉽게 확장되지 않는다.

  • 데이터베이스 쓰기 처리량
  • 외부 API 호출 속도
  • 스토리지 IOPS
  • 트랜잭션 락 경합

결국 이벤트를 처리하는 최종 목적지가 병목이면 브로커만 확장하는 것은 큰 의미가 없다.

EDA는 병목을 제거하는 기술이 아니라 병목을 관찰하기 쉽게 만드는 기술에 가깝다.


결론

Event-Driven Architecture는 서비스 간 결합도를 낮추고 장애 격리를 가능하게 만든다. 하지만 느려진 서비스 하나가 전체 시스템에 영향을 주지 않는다는 의미는 아니다.

실제 운영 환경에서는 데이터베이스 지연, Consumer Lag 증가, 재시도 폭풍, 브로커 적체가 서로 연결되면서 작은 병목이 전체 이벤트 흐름으로 확산된다.

그래서 EDA를 설계할 때 가장 중요한 질문은 “어떻게 이벤트를 발행할 것인가”가 아니다.

“어디에서 이벤트가 막힐 수 있는가”를 먼저 찾는 것이다.

결국 안정적인 이벤트 기반 시스템은 이벤트를 많이 처리하는 시스템이 아니라, 병목이 발생했을 때 그 영향을 제한할 수 있는 시스템이다. 그것이 EDA 운영 경험이 쌓일수록 많은 엔지니어가 도달하게 되는 결론이다.

sounghoo
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.