1. Ordered Topic — 순서가 중요한 업무 흐름
RabbitMQ는 큐 단위로만 순서를 보장한다.
즉, 메시지를 하나의 큐에서만 소비하면 순서가 유지되고, 여러 큐나 병렬 소비가 일어나면 순서가 깨진다.
이 구조는 결제, 정산, 배송처럼 순차적 업무 흐름이 있는 서비스에서 사용된다.
conn := rb.MustConn("amqp://guest:guest@localhost:5672/")
defer conn.Close()
ch := rb.MustChan(conn)
defer ch.Close()
_ = ch.ExchangeDeclare("order.events", "topic", true, false, false, false, nil)
q, _ := ch.QueueDeclare("order-seq-1001", true, false, false, false, nil)
_ = ch.QueueBind(q.Name, "order.#.1001", "order.events", false, nil)
_ = ch.Qos(1, 0, false)
msgs, _ := ch.Consume(q.Name, "", false, false, false, false, nil)
for m := range msgs {
log.Printf("Processing: %s", m.Body)
m.Ack(false)
}
적용 사례
- 결제 → 송장 → 정산 순차 처리
- 주문 단위 트랜잭션 관리
- 재고 감소 → 재고 확정 순서 보존
비즈니스 로직의 순서성이 데이터 정합성과 직접 연결되는 경우에 사용된다.
2. Unordered Topic — 처리량이 우선인 구조
여러 소비자가 동일 큐를 병렬로 구독하면 메시지의 순서는 보장되지 않는다.
이 대신 처리량이 크게 증가한다.
이 구조는 로그, 이벤트, 실시간 트래픽 분석처럼 순서보다 처리 속도와 확장성이 중요한 서비스에서 사용된다.
_ = ch.ExchangeDeclare("log.events", "topic", true, false, false, false, nil)
q, _ := ch.QueueDeclare("all-logs", true, false, false, false, nil)
_ = ch.QueueBind(q.Name, "#", "log.events", false, nil)
for i := 0; i < 8; i++ {
go func(id int) {
msgs, _ := ch.Consume(q.Name, "", true, false, false, false, nil)
for m := range msgs {
log.Printf("[C%d] %s", id, m.Body)
}
}(i)
}
select {}
적용 사례
- 로그 수집 시스템
- 행동 로그 기반 추천 시스템
- IoT 데이터 적재
비즈니스 로직이 대규모 데이터를 병렬로 처리하는 구조일 때 사용된다.
3. Generic Routing Topic — 유연한 라우팅 구조
Topic Exchange의 가장 큰 장점은 유연한 라우팅이다..으로 구분된 계층 구조와 *, # 패턴 매칭을 통해 서비스 단위로 메시지를 분배할 수 있다.
_ = ch.ExchangeDeclare("app.events", "topic", true, false, false, false, nil)
userQ, _ := ch.QueueDeclare("user-service", true, false, false, false, nil)
orderQ, _ := ch.QueueDeclare("order-service", true, false, false, false, nil)
anaQ, _ := ch.QueueDeclare("analytics", true, false, false, false, nil)
_ = ch.QueueBind(userQ.Name, "user.#", "app.events", false, nil)
_ = ch.QueueBind(orderQ.Name, "order.#", "app.events", false, nil)
_ = ch.QueueBind(anaQ.Name, "#", "app.events", false, nil)
rb.PublishJSON(ch, "app.events", "user.created", []byte(`{"id":1}`))
rb.PublishJSON(ch, "app.events", "order.shipped.us", []byte(`{"orderId":77}`))
적용 사례
- 마이크로서비스 간 이벤트 버스
- IoT 센서 데이터 라우팅 (
sensor.region.device) - 멀티리전 동기화 (
db.updated.us,db.updated.eu)
비즈니스 로직이 서비스 단위로 분리되어 있고, 구독 구조가 유연해야 하는 경우에 사용된다.
4. Static Routing — 정확 일치 기반의 안정적 전달
Direct Exchange는 라우팅 키가 정확히 일치할 때만 메시지를 전달한다.
정해진 채널로만 메시지를 보내야 하는 경우에 사용된다.
_ = ch.ExchangeDeclare("system.direct", "direct", true, false, false, false, nil)
errQ, _ := ch.QueueDeclare("error-log", true, false, false, false, nil)
_ = ch.QueueBind(errQ.Name, "error", "system.direct", false, nil)
rb.PublishJSON(ch, "system.direct", "error", []byte(`{"msg":"fatal"}`))
적용 사례
- 운영팀 전용 알림
- 보안 이벤트 로그
- 시스템 단일 목적 메시징
비즈니스 로직이 정확히 지정된 경로로만 전달되어야 하는 경우에 사용된다.
5. Hybrid (Partitioned Topic) — 순서와 확장성의 절충
Kafka의 파티션 개념과 비슷한 방식이다.
ID 기반으로 라우팅 키를 구성하면, 각 ID 단위로는 순서가 보장되면서 시스템 전체는 병렬로 확장된다.
for _, id := range []int{1001, 1002, 1003} {
rk := fmt.Sprintf("order.created.%d", id)
rb.PublishJSON(ch, "order.events", rk, []byte(fmt.Sprintf(`{"orderId":%d}`, id)))
}
적용 사례
- 주문 단위 이벤트 처리
- 사용자 단위 데이터 파이프라인
- 배송 추적 및 상태 변경
비즈니스 로직이 엔티티 단위(주문, 사용자, 배송 등)로 분리 가능할 때 사용된다.
6. 토픽 설계 기준 정리
| 비즈니스 로직 | 권장 패턴 | 이유 |
|---|---|---|
| 결제, 정산, 배송 | Ordered | 순서성이 정합성에 직접 영향 |
| 로그, 이벤트 수집 | Unordered | 순서보다 처리량 우선 |
| 마이크로서비스 이벤트 버스 | Generic | 라우팅 유연성 필요 |
| 운영, 보안 알림 | Static | 정확한 라우팅 필요 |
| 주문별 순서 + 전체 통계 | Hybrid | ID 단위 순서 + 병렬 확장 |
RabbitMQ에서 토픽 설계는 단순히 키를 어떻게 자르느냐의 문제가 아니다.
토픽의 종류는 시스템이 추구하는 목표,
즉 순서의 정합성인지, 처리량의 극대화인지, 라우팅의 유연성인지에 따라 달라진다.
결국 토픽 설계는
비즈니스 로직의 성격에 따라 달라진다.

댓글 남기기