도입: 왜 큐를 고려하게 되었는가?
메시지 서버를 설계하면서 처음에는 HTTP 기반의 요청 처리를 고려했습니다. 하지만 HTTP는 클라이언트의 요청 수에 따라 서버가 직접 응답해야 하므로, 서버의 처리 능력과 상관없이 부하가 한순간에 몰릴 수 있는 위험이 있습니다.
이에 따라 대안으로 떠오른 것이 큐 기반 아키텍처입니다.
Queue를 사용하면 메시지를 먼저 받아 저장한 후, 서버의 처리 능력에 맞게 정량적으로 메시지를 소비할 수 있어 전체 시스템의 안정성과 확장성을 높일 수 있습니다. 특히 AWS ECS와 같은 컨테이너 오케스트레이션 환경에서는, 컨테이너 수를 조절하면서 손쉽게 수평 확장이 가능하다는 점도 큰 장점이었습니다.
Spring을 사용하는 백엔드 시스템에서 메시지 큐를 도입할 때 흔히 비교하는 두 가지 기술은 RabbitMQ와 Amazon SQS입니다. 각각의 특징과 처리 방식, 재시도, 중복 처리, 성능에 대해 자세히 정리해봅니다.
| 항목 | RabbitMQ | Amazon SQS (Standard / FIFO) |
|---|---|---|
| 비용 | 인프라 운영 필요 (비쌈) | 요청 수 기반 과금 (저렴), 무료 티어 있음 |
| 가독성 | 설정 복잡 (Exchange, Queue 등) | Spring Cloud AWS 사용 시 설정 단순 |
| 성능 | 낮은 지연, 높은 성능 | 지연 있음 (HTTP 기반, long polling) |
| 호환성 | 다양한 프로토콜 및 브로커 간 연동 가능 | AWS 생태계 중심 |
| 순차 처리 | 가능 (설계 필요) | FIFO 큐 사용 시 보장 |
nack 및 requeue로 직접 제어 가능.| 항목 | RabbitMQ | Amazon SQS |
|---|---|---|
| 메시지 전달 방식 | Push (브로커가 Consumer에게 전송) | Pull (Consumer가 직접 요청) |
| 처리 흐름 | basic.consume → 메시지 전달 → ack | receiveMessage() → 처리 → deleteMessage() |
| 실시간성 | 매우 우수 | 상대적으로 지연 존재 |
| 병렬 처리 | prefetchCount, concurrency로 제어 | concurrency, maxMessages, waitTime 설정 가능 |
prefetchCount: Consumer가 동시에 받을 수 있는 메시지 수 제한ackMode: 수동 ack 설정으로 처리 후 메시지 제거concurrency: 동시 처리 스레드 수 조절@RabbitListener(queues = ["my.queue"], ackMode = "MANUAL")
fun handleMessage(message: Message, channel: Channel) {
try {
// 처리 로직
channel.basicAck(message.messageProperties.deliveryTag, false)
} catch (e: Exception) {
channel.basicNack(message.messageProperties.deliveryTag, false, true)
}
}
제대로 구성하면 Push 방식도 안정적으로 처리량 제어 가능.
| 상황 | 추천 큐 시스템 |
|---|---|
| 실시간 이벤트, 빠른 응답 필요 | RabbitMQ |
| 서버리스, 저비용 비동기 처리 | Amazon SQS |
| 순서 보장 필요, 중복 없어야 함 | Amazon SQS FIFO |
| 복잡한 메시지 라우팅, 고성능 처리 | RabbitMQ |
RabbitMQ는 Push 기반이지만,
prefetch,ack,concurrency설정을 통해 안정적으로 처리량 제어 가능.
SQS는 Pull 기반으로 구조가 단순하고 서버리스에 적합.
두 방식 모두 idempotent 처리 필수!