LabHub

마이크로서비스 아키텍처 · 비동기 통신과 이벤트 · 이론

명령이 아니라 사실을 보낸다

LabHub 에서 이어서 보기

한 줄 요약

동기 호출은 "이걸 해 줘"이고 이벤트는 "이런 일이 있었다"이다. 후자는 상대가 지금 살아 있지 않아도 된다.

왜 이게 필요했나

주문이 만들어지면 재고를 줄이고, 포인트를 적립하고, 알림을 보내고, 분석 이벤트를 쌓아야 한다고 합시다. 이것을 동기 호출로 엮으면 주문 API 의 응답 시간은 네 서비스의 응답 시간의 합이 되고, 가용성은 네 서비스 가용성의 곱이 됩니다. 각각 99.9% 라면 합쳐서 99.6% 입니다. 그리고 알림 서비스가 죽으면 주문을 받지 못합니다. 알림 때문에 매출이 멈추는 설계입니다.

이벤트로 바꾸면 주문 서비스는 "주문이 생성됨"이라는 사실 하나만 발행하고 끝납니다. 나머지 넷은 각자의 속도로 그것을 소비합니다. 알림 서비스가 10분 죽어 있어도 주문은 계속 받히고, 살아나면 밀린 이벤트를 처리합니다.

어떻게 동작하나

여기서 중요한 것은 이름입니다. SendNotification 은 명령이고 OrderCreated 는 사실입니다. 명령을 큐로 보내면 그것은 그냥 비동기 RPC 입니다. 발신자가 수신자를 알고 있고, 수신자가 늘어나면 발신자 코드가 바뀝니다. 사실을 발행하면 발신자는 누가 듣는지 모릅니다. 새 소비자가 붙어도 발행자는 그대로입니다. 이것이 결합을 실제로 줄이는 지점입니다.

이벤트 페이로드에는 소비자가 자기 일을 마치는 데 필요한 정보를 충분히 담습니다. 소비자가 발행자의 DB 를 되짚어 조회하면 결합이 되살아납니다. 다만 너무 많이 담으면 이벤트가 스키마가 되고, 스키마는 곧 계약이라 바꾸기 어려워집니다. 실무의 타협은 식별자와 함께 변하지 않을 핵심 필드만 담는 것입니다.

대가도 분명합니다. 최종 일관성입니다. 주문은 생성됐는데 재고는 아직 안 줄었을 수 있는 시간 창이 생깁니다. 그 창이 200ms 인지 30초인지, 그 사이에 사용자가 무엇을 볼지를 제품 차원에서 정해야 합니다. "곧 반영됩니다"라는 화면 문구가 사실은 아키텍처 결정입니다.

현장에서 만나는 모습

비동기로 바꾸면 디버깅이 어려워집니다. 동기 호출은 스택트레이스 하나로 끝나지만, 이벤트는 발행 시점과 소비 시점이 떨어져 있습니다. 그래서 이벤트에 상관관계 ID 를 반드시 실어야 합니다. 이것 없이 이벤트 아키텍처를 운영하는 것은 눈을 감고 운전하는 것과 같습니다.

또 하나. 이벤트는 순서가 보장되지 않을 수 있습니다. 같은 주문에 대한 OrderCreatedOrderPaid 가 뒤집혀 도착하면 소비자가 깨집니다. 파티션 키를 집합체 ID(예: 주문 ID)로 잡으면 같은 집합체 안에서는 순서가 보장됩니다. 전역 순서는 포기하는 게 보통입니다.

다음 퀴즈에서 확인할 것

이 모듈은 개념만 다룹니다. 이벤트를 실제로 안전하게 발행하는 문제는 바로 다음 모듈의 아웃박스 실습에서 손으로 다룹니다.