28강
이벤트 기반 아키텍처
기다릴 일과 기다리지 않아도 될 일을 나누면, 느린 부품이 전체를 끌어내리지 않는다
단계별로 보기
스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.
대기 중1 / 4 단계 — 사슬로 묶인 상태
주문 서버가 느린 저장소를 동기로 호출합니다. 사용자는 그 저장소가 끝날 때까지 기다립니다. 저장소는 초당 20건밖에 처리하지 못하는데 초당 60건이 들어옵니다. 사용자의 대기 시간이 저장소의 속도에 그대로 묶여 있다는 점을 보세요.
이 강의를 마치면
- 동기 호출 사슬에서 가장 느린 부품이 전체 응답 시간을 결정하는 이유를 설명한다.
- 사용자 응답에 꼭 필요한 일과 나중에 해도 되는 일을 구분하는 기준을 세운다.
- 동기 사슬을 이벤트 기반으로 바꿀 때 좋아지는 것과 나빠지는 것을 함께 설명한다.
- 느린 하류 때문에 무너지는 시스템을 직접 이벤트 기반으로 바꿔 살려낸다.
사슬은 가장 약한 고리만큼만 강하다
주문 서버가 이렇게 일한다고 해봅시다. 사용자의 요청을 받아서
- 주문을 데이터베이스에 저장하고 (20밀리초)
- 결제사에 승인을 요청하고 (200밀리초)
- 메일 서버에 확인 메일을 보내고 (300밀리초)
- 창고 시스템에 출고를 지시하고 (150밀리초)
- 그제서야 사용자에게 "완료"를 돌려줍니다.
사용자가 기다리는 시간은 얼마일까요? 670밀리초입니다. 각 단계가 앞 단계를 기다리므로 전부 더해집니다.
이게 끝이 아닙니다. 더 나쁜 것이 있습니다.
남의 고장이 내 고장이 된다
메일 서버가 죽었습니다. 3번에서 호출이 실패합니다. 그러면 어떻게 될까요? 주문 전체가 실패합니다. 사용자는 오류 화면을 봅니다.
잠깐 생각해 봅시다. 메일이 안 갔다고 주문을 취소하는 게 맞습니까? 주문은 이미 저장됐고 결제도 승인됐습니다. 확인 메일 하나 때문에 멀쩡한 거래를 버리는 것입니다.
이것이 동기 사슬의 본질적 약점입니다. 사슬로 묶으면 가장 약한 고리가 전체의 강도를 결정합니다. 부품 하나의 가용성이 99%면, 다섯 개를 사슬로 묶은 전체는 약 95%가 됩니다. 부품을 붙일수록 시스템은 더 자주 고장 납니다.
질문 하나로 나눈다
해법은 기술이 아니라 질문에서 시작합니다.
"이 일이 끝나야만 사용자에게 완료라고 말할 수 있는가?"
이 질문에 각 단계를 넣어봅니다.
| 단계 | 끝나야 완료인가? | 판단 |
|---|---|---|
| 주문 저장 | 예. 저장 안 됐으면 주문이 아니다 | 동기 |
| 결제 승인 | 예. 돈을 못 받으면 파는 게 아니다 | 동기 |
| 확인 메일 | 아니오. 1분 뒤에 가도 아무 문제 없다 | 비동기 |
| 출고 지시 | 아니오. 창고는 오늘 안에만 알면 된다 | 비동기 |
| 통계 갱신 | 아니오. 내일 집계해도 된다 | 비동기 |
나눠 보면 대부분의 일이 기다릴 필요가 없습니다. 그런데 우리는 습관적으로 전부 사슬로 묶어버립니다. 순서대로 쓰는 게 가장 쉽기 때문입니다.
나눈 뒤의 모습
동기 구간 (사용자가 기다린다) 비동기 구간 (사용자는 떠났다)
┌──────────────────────┐ ┌────────────────────────┐
주문 저장 → 결제 승인 → 응답 ─발행→ 메일 / 출고 / 통계
220밀리초 각자 알아서사용자가 기다리는 시간이 670밀리초에서 220밀리초로 줄었습니다. 더 중요한 건 이겁니다. 메일 서버가 죽어도 주문은 성공합니다. 메일 큐에 일이 쌓일 뿐이고, 메일 서버가 살아나면 밀린 것을 보냅니다.
느린 부품과 약한 부품이 사용자가 기다리는 경로 밖으로 나갔습니다. 이것이 이벤트 기반 구조가 하는 일의 전부입니다.
무엇을 잃는가
정직하게 말해야 할 부분입니다. 공짜가 아닙니다.
"완료"의 뜻이 약해집니다. 사용자가 본 "주문 완료"는 이제 "주문이 접수되고 결제가 승인됐다"는 뜻이지 "메일이 발송됐다"는 뜻이 아닙니다. 이건 기술 결정이 아니라 사업 결정입니다. 무엇을 약속할지 정하는 일이니까요.
추적이 어려워집니다. 동기 사슬에서는 로그 한 줄에 전부 있었습니다. 이제는 여러 큐와 워커에 흩어져 있습니다. "이 주문의 메일이 갔나?"를 답하려면 여러 곳을 봐야 합니다.
눈에 안 보이게 고장 납니다. 앞선 강의에서 본 그대로입니다. 사용자 화면은 멀쩡한데 큐만 부풀어 오릅니다. 그래서 이벤트 기반 시스템에서는 적체를 감시하는 일이 선택이 아니라 필수입니다.
직접 해보세요
아래 과제는 느린 데이터베이스에 동기로 묶여 무너지고 있습니다. 유실률을 먼저 확인한 다음, 사용자가 기다리는 경로에서 그 느린 작업을 빼내 보세요.
이 강의가 단순화한 것
교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.
전제한 것
- 이 과제에서 '느린 하류 작업'은 처리 시간이 긴 데이터베이스 하나로 단순화했다. 실제로는 결제사·메일·외부 API 등 여러 대상이 섞여 있다.
- 큐에 넣는 순간 사용자에게는 성공으로 응답한다. 즉 여기서의 '완료'는 '접수 완료'라는 뜻이다.
- 이 시뮬레이터에서 큐에 들어간 작업은 유실되지 않는다고 본다. 실제로는 발행 실패와 워커 실패를 각각 따로 다뤄야 한다.
- 각 단계의 처리 시간은 설명을 위해 고른 대표값이며 실제 측정값이 아니다.
다루지 않은 것
- 이벤트 소싱과 CQRS 같은 상위 패턴
- 사가(saga)와 보상 트랜잭션 — 비동기 구간에서 실패했을 때 되돌리는 방법
- 이벤트 스키마 버전 관리
- 분산 추적(distributed tracing)
이제 직접 만들어 보세요
읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.
이 강의에 물어보기
하루 5번이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.