Notice
Recent Posts
Recent Comments
Link
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

채움의 길

[DeepDive] 시스템이 이벤트를 이해하는 방식 본문

지식 채우기/동아리

[DeepDive] 시스템이 이벤트를 이해하는 방식

chae-um 2025. 10. 28. 16:21
더보기

시스템이 이벤트를 이해하는 방식

  • EventListener의 동작 원리 : 이벤트 발생 시 트리거되는 비동기 콜백구조 및 이를 통한 결합도 감소
  • MessageQueue와의 연계 개념 : Listener가 이벤트를 "구독(consume)"하는 역할을, Queue가 "전달" 역할을 수행
  • 요청-응답 vs 이벤트 기반 구조 비교 : 시스템 간 통신 방식의 차이와 그로 인한 확장성/유연성 변화
  • 이벤트 처리의 신뢰성 포인트 : Listener의 중복 이벤트 처리(idempotency)와 순서 보장 문제 

EventListener의 동작 원리

EventListener는 이벤트가 발생했을 때 실행되는 비동기 콜백 함수로, 주체(이벤트 발생자)와 반응자(Listener)가 직접 연결되지 않아 결합도가 낮아진다는 이점이 있다.

동작 흐름

  1. 이벤트 흐름 (Subscribe) : Listener가 "이런 이벤트가 생기면 알려줘"라고 시스템에 등록
  2. 이벤트 발생 (Publish / Emit) : 특정 동작이 일어나면 시스템이 이벤트를 발생
  3. 리스너 트리거 (Trigger Listener) : 이벤트가 발생하면 해당 이벤트를 구독한 Listener들이 자동으로 호출 (이 호출은 비동기로 처리되어 메인 로직의 흐름을 방해하지 않음)

비동기 콜백 구조

해당 콜백 구조는 '동작 흐름'의 3번 내에서 일어나게 된다.

  1. 등록 (Register) : Listener가 "콜백 함수" 형태로 시스템에 등록
  2. 대기 (Listen) : 이벤트 발생을 기다림 (Blocking하지 않음)
  3. 호출 (Invoke) : 이벤트가 발생하면 시스템이 콜백을 자동 호출
  4. 후처리 (Async) : 대부분의 Listener는 별도 스레드에서 실행되어 메인 로직과 분리

→ 이 구조 덕분에 이벤트 발생자(Producer)는 Listener의 존재를 몰라도 되고, Listener는 이벤트의 "발생 시점"을 직접 제어할 필요가 없음

 

결합도 감소의 원리

항목 기존(보편적인) 구조 이벤트 기반 구조
호출 방식 직접 메서드 호출 이벤트 발생 후 Listener가 처리
의존 관계 발생자(Producer)가 처리 로직을 알아야 함 Listener만 등록하면 됨
확장성 새로운 기능 추가 시 코드 수정 필요 새로운 Listener만 추가하면 됨

 

즉, 시스템이 '이벤트'만 주고받는 구조이기 때문에 누가 받는지 몰라도 되고, 이로 인해 결합도가 감소하여 확장성이 향상됨

 

MessageQueue와의 연계

이전 소주제에서 "Producer는 Listener의 존재를 몰라도 된다"라고 언급했다.

MessageQueue는 이 둘의 중간 역할을 해주며 해당 과정을 보다 깔끔하게 처리할 수 있도록 도와준다.

  • Producer(이벤트 발행자) : 이벤트를 만들어 Queue에 "보낸다" (publish, emit)
    • 비동기화, Producer의 역할은 여기서 끝남
    • 결합도 감소, Producer와 Listener가 서로를 몰라도 동작 가능
  • Message Queue(전달자) : 이벤트를 "저장하고 전달"한다 (buffer, broker, decoupling)
    • 신뢰성, 중간에서 메세지를 보관하므로 장애에도 데이터 유실 방지
  • Listener / Consumer(이벤트 수신자) : Queue에 쌓인 메세지를 "구독(consume)"하고 처리한다 (subscribe, process)
    • 확장성, Listener를 여러 개 띄워서 부하 분산 가능

MessageQueue와의 연계가 추가되면, Producer와 Listener 사이의 결합도가 더욱 낮아지고 시스템의 확장성이 한층 강화된다.

이벤트 기반 구조는 Producer → Message Queue → Listener(Consumer) 로 이어지는 비동기 이벤트 파이프 라인이며, 이 덕분에 서비스 간 의존성을 낮추고 장애에 확장 가능한 시스템을 만들 수 있다.

 

요청-응답 vs 이벤트 기반 구조 비교

이번엔 요청-응답 구조와 위에서 다뤄왔던 이벤트 기반 구조를 비교해볼 차례이다.

여기서 중점을 두고 봐야할 부분은 아래 두 가지.

  1. 서로를 어떻게 인식하는가? (공간적 결합)
  2. 언제 반응하는가? (시간적 결합)
관점 요청-응답 구조 이벤트 기반 구조 변화의 의미
인식 방식 (공간적 결합) 호출자는 "대상(Server)"의 위치(API, URL, IP 등)를 직접 알아야 함  Producer는 Listener의 존재나 위치를 몰라도 됨, 이벤트를 발행할 뿐 호출 대상에 대한 의존성 제거 -> 결합도 감소
반응 시점 (시간적 결합) 요청과 응답이 동시에 일어나야 함, 둘 다 활성 상태 필요 이벤트는 나중에 처리 가능, Queue를 통해 시간적 분리 비동기 처리 -> 유연성, 복원력 향상
통신 매개체 네트워크 호출 (HTTP, RPC 등) Message Queue (Kafka, RabbitMQ 등) 버퍼링, 재시도, 순서 보장 가능
장애 대응력 상대 서비스가 죽으면 요청 실패 Queue가 중간 역할을 하여 Listener 복구 시 재처리 가능 신뢰성 확보
확장성 요청량 증가 시 서버 부하 급증 Listener를 여러 개 띄워 소비 병렬화 가능 수평 확장 용이
변화 대응력 기능 추가 시 기존 API 수정 필요 새로운 Listener 추가만으로 이벤트 확장 가능 변화에 유연

 

이벤트 처리의 신뢰성 포인트

그렇다면 비동기로 이루어진, 결합도가 낮아져 느슨하게 연결된 시스템이 "한 번만, 올바른 순서"로 이벤트를 처리하려면 어떻게 해야하는가?

 

멱등성

: 같은 이벤트가 여러 번 와도 결과는 한 번 처리한 것과 동일해야 한다.

  • 해결 방법
    • 이벤트 ID 추적 : 이벤트마다 고유 ID 부여 -> 이미 처리한 ID는 무시
    • DB 레벨 멱등성 : INSERT IGNORE, ON DUPPLICATE KEY UPDATE 등으로 중복 삽입 방지
    • 트랜잭션 내 체크 로직 : 이벤트 처리 전 상태 점검 (조건문 등)

멱등성을 유지하기 위한 핵심 포인트는 "Listener는 이벤트를 받는 순간, 이게 새로운 이벤트인지를 먼저 검증해야 한다는 것"

 

순서 보장

: 이벤트는 반드시 '의도한 순서대로' 처리되어야 한다.

  • 해결 방법
    • Partition Key 사용 (Kafka) : 동일한 엔티티 ID 기준으로 같은 파티션에 보내 순서 유지
    • Sequence Number 부여 : 이벤트에 시퀀스 번호를 붙여 Listener가 순서 검증 후 처리
    • Event Store 기반 재정렬 : 이벤트를 일단 저장 후, 순서대로 처리하는 별도 스케줄러 운영

순서를 보장하기 위한 핵심 포인트는 "애플리케이션 레벨에서 직접 관리해야 할 것"

단, 순서가 달라도 최종 상태만 일관되면 되는 프로세스에서는 "결과적 일관성"을 확보하면 됨