메시지 큐(MQ) — 서비스를 직접 부르지 않고 메시지로 잇기
주문이 들어오면 알림을 보내고, 통계를 집계하고, 추천을 갱신한다. 이 일들을 주문 서비스가 직접 하나씩 호출하면 어떻게 될까. 알림 서비스가 느려지면 주문 응답도 같이 느려지고, 통계 서비스가 죽으면 주문까지 실패한다. 주문은 끝났는데, 부수적인 일들에 발목이 잡힌다.
메시지 큐는 서비스 사이에 중간 우체통을 둬서 이걸 끊는다. 주문 서비스는 "주문 생김"이라는 메시지를 큐에 던지고 곧장 자기 일을 끝낸다. 알림·통계·추천 서비스는 그 큐를 각자 자기 속도로 꺼내 처리한다. 보내는 쪽(producer)과 받는 쪽(consumer)이 직접 만나지 않고, 시간도 속도도 따로 노는 비동기 통신이다.
지금까지의 fetch·REST는 동기 호출 — 부르고 응답을
기다린다. 메시지 큐는 비동기 — 던지고 떠나며, 응답을 그 자리에서
기다리지 않는다. "당장 결과가 필요한 일"은 동기로, "곧 처리되면 되는 일"은
비동기로 가르는 게 핵심이다.
1. 직접 호출의 세 가지 약점
왜 굳이 사이에 큐를 끼울까? 서비스가 서로를 직접 부를 때 생기는 문제를 보면 큐의 값어치가 보인다.
- 1강한 결합 — 상대가 죽으면 나도 실패
동기 호출은 상대의 생사·속도에 내 운명이 묶인다.
주문이 통계 서비스를 직접 부르면, 통계가 죽었을 때 주문도 에러가 난다. 본질적으로 부수적인 일이 핵심 흐름을 끌어내린다.
- 2트래픽 폭주를 그대로 떠안는다
갑자기 요청이 10배면 받는 쪽이 그대로 터진다.
직접 호출은 들어온 만큼 즉시 처리해야 한다. 큐가 있으면 밀린 일을 쌓아 두고 받는 쪽이 감당 가능한 속도로 빼낼 수 있다 — 충격을 흡수하는 완충재(buffer) 역할이다.
- 3수신자를 늘리기 번거롭다
새 기능이 같은 이벤트를 듣고 싶을 때마다 호출부 수정.
"주문 생김"에 반응할 서비스가 하나 더 생기면, 직접 호출 방식은 주문 코드를 또 고쳐야 한다. 큐는 발행자는 그대로 두고 구독자만 추가하면 돼, 확장이 깔끔하다.
세 약점의 뿌리는 "보내는 쪽과 받는 쪽이 직접 묶여 있다"는 것이다. 큐는 그 사이에 끼어들어 둘을 떼어 놓는다(decoupling) — 이게 메시징의 본질이다.
2. 메시지가 큐를 지나는 흐름
큐를 사이에 둔 통신은 "던진다 — 보관된다 — 꺼내 처리한다"의 세 박자다. 보내는 쪽은 받는 쪽이 누구인지, 지금 살아 있는지조차 몰라도 된다.
큐에 던지고 즉시 응답 반환
소비자가 가져갈 때까지 안전 보관
자기 속도로 꺼내 처리
확인 신호를 줘야 큐에서 제거됨
- 1발행 후 즉시 반환
Producer는 큐에 넣자마자 자기 일을 끝낸다.
받는 쪽 처리를 기다리지 않으니 주문 응답이 빨라진다. 부수 작업의 지연·실패가 주문에 전파되지 않는다.
- 2큐가 메시지를 안전 보관
소비자가 가져갈 때까지, 죽어 있어도 메시지는 남는다.
통계 서비스가 잠깐 죽어도 메시지는 큐에 쌓여 있다가, 살아나면 밀린 걸 처리한다. 일이 유실되지 않는 게 직접 호출과 결정적으로 다른 점이다.
- 3ack — 처리 확인 후 제거
소비자가 '처리 끝'을 알려야 큐가 메시지를 지운다.
소비자가 처리 도중 죽으면 ack가 안 와, 큐는 그 메시지를 다시 전달한다. 덕분에 "처리하다 말고 사라진" 일이 누락되지 않는다. 대신 같은 메시지가 두 번 올 수 있어, 소비자는 멱등성을 갖춰야 한다(아래).
대부분의 큐는 "메시지를 적어도 한 번(at-least-once) 전달"을 보장한다. 이는 중복 전달이 가능하다는 뜻이다. 그래서 소비자는 "같은 메시지를 두 번 처리해도 결과가 같도록"(멱등성) 만들어야 한다 — 예: 주문 ID로 이미 처리했는지 확인 후 건너뛰기.
3. 큐의 두 모델: 작업 분배 vs 이벤트 구독
"메시지를 받는 쪽이 누구냐"에 따라 큐를 쓰는 결이 갈린다. 한 일을 한 명에게 나눠 시키는 모델과, 한 사건을 여러 구독자에게 알리는 모델이다.
- ·한 메시지를 여러 워커 중 하나가 가져가 처리
- ·일을 나눠 병렬로 빨리 처리하는 데 적합
- ·예: 이미지 변환·메일 발송 같은 작업 분산
- ·워커를 늘리면 처리량이 비례해 오른다
- ·한 이벤트를 구독한 모든 소비자가 각자 받음
- ·같은 '주문 생김'을 알림·통계·추천이 다 받음
- ·구독자 추가가 발행자와 무관해 확장 쉬움
- ·이벤트 기반 아키텍처(EDA)의 기반
정리하면 메시지 큐는 서비스를 직접 잇는 대신 사이에 우체통을 둬, 보내는 쪽과 받는 쪽을 시간·속도·생사로부터 떼어 놓는다. 충격을 흡수하고, 일을 잃지 않고, 구독자를 자유롭게 늘린다. 그 대가로 비동기 특유의 중복·순서·지연을 다뤄야 한다. 이제 이 모델을 대규모로 구현한 대표 주자를 보자.
Kafka — 큐를 넘어 '이벤트 로그'로
Kafka는 메시지 큐 중에서도 가장 널리 쓰이는 구현이다. 그런데 Kafka를 그냥 "빠른 큐"로만 보면 핵심을 놓친다. Kafka는 메시지를 꺼내면 사라지는 우편물이 아니라, **계속 쌓이는 로그(기록)**로 다룬다. 이 한 끗 차이가 Kafka의 성격을 거의 다 설명한다.
- 1토픽 — 메시지를 담는 분류함
'orders', 'payments'처럼 주제별로 메시지를 모은다.
Producer는 토픽에 메시지를 추가하고, Consumer는 토픽을 구독한다. 토픽은 삭제되며 비워지는 큐가 아니라, 이어 붙는 로그에 가깝다.
- 2파티션 — 토픽을 쪼개 병렬 처리
한 토픽을 여러 파티션으로 나눠 처리량을 키운다.
파티션마다 소비자를 붙여 병렬로 읽을 수 있어 처리량이 크게 오른다. 같은 키(예: 같은 주문 ID)는 같은 파티션으로 보내, 그 키 안에서는 순서를 보장한다.
- 3오프셋 — '어디까지 읽었나'를 소비자가 기억
메시지를 지우지 않고, 각자 읽은 위치만 기록한다.
메시지는 일정 기간 보관되고, 소비자는 자기 오프셋(읽은 지점) 만 들고 있다. 그래서 오프셋을 되감으면 지난 기록을 다시 재생할 수 있다 — 새 소비자가 처음부터 전체 이력을 읽는 것도 가능하다.
꺼내도 메시지가 남으니, Kafka는 같은 이벤트 스트림을 여러 용도가 각자 재생할 수 있다. 실시간 처리와 일·월 단위 재집계가 같은 로그를 공유하고, 버그를 고친 뒤 과거 이벤트를 다시 흘려 상태를 재구성할 수도 있다. 그래서 Kafka는 단순 메시징을 넘어 데이터 파이프라인의 중추로 쓰인다.
초당 수십만 건의 스트림·이벤트 소싱·재생이 필요하면 Kafka가 강하다. 반대로 "메일 발송 작업을 워커에 분배" 정도라면 RabbitMQ 같은 전통 큐가 더 간단하고 충분하다. Kafka의 파티션·오프셋·운영 복잡성은 규모가 받쳐줄 때 값을 한다.
한 장 요약
- 1문제
서비스를 직접 호출하면 상대의 지연·장애·폭주가 그대로 전파된다.
- 2큐
사이에 우체통을 둬 보내는 쪽과 받는 쪽을 시간·속도·생사로부터 분리.
- 3흐름
발행 후 즉시 반환 → 큐가 보관 → 소비자가 자기 속도로 처리 → ack로 제거.
- 4중복 대비
최소 한 번 전달이라 중복 가능 → 소비자는 멱등하게 만든다.
- 5두 모델
작업 큐(1:1 분배)와 발행/구독(1:N 브로드캐스트).
- 6Kafka
메시지를 지우지 않는 로그로 다뤄 토픽·파티션·오프셋으로 대규모 스트림·재생을 지원.