본문 바로가기
백엔드 개발

AWS SQS SDK 기반 Consumer 실무 적용기

by collenkim 2026. 2. 6.
반응형
이 글은 AWS SQS 기반 메시지 소비 구조를 실제 서비스 운영 환경에서 직접 구현 및 사용해본 경험을 바탕으로 기록한
메모입니다.
단순한 코드 스니펫이나 샘플 예제가 아니라, 실무에서 마주치고 해결했던 문제점, 선택 기준, 보완 포인트를 함께 정리합니다.

 

소스 코드는 아래 GitHub 레포지토리에서 확인할 수 있다.
https://github.com/collenkim/aws


1. 전체 구조 요약

아래 Consumer 구조는 AWS SDK v2 기반으로 직접 구현한 코드 흐름입니다.

while (isNotThreadTerminate) {
   ReceiveMessageRequest...
   List<Message> messages = sqsClient.receiveMessage(...).messages();
   messages.forEach(msg -> executor.submit(() -> {
       processMessage(msg);
       deleteMessage(msg);
   }));
}

 

이 구조의 핵심은 다음과 같습니다:

  • SQS Long Polling 기반 메시지 수신
  • ExecutorService 기반 멀티스레드 처리
  • 처리 완료 후 명시적인 메시지 삭제
  • isNotThreadTerminate 플래그를 활용한 graceful shutdown

실무에서는 단순하게 처리하는 것보다, 에러 처리·스레드 관리·종료 제어 등 여러 측면을 고려해야 했습니다.


2. Spring @JmsListener와의 비교

 

AWS 환경에서 SQS Consumer를 구현하는 방법은 여러 가지가 있습니다.
가장 흔한 방법 중 하나는 Spring의 @JmsListener를 이용하는 것입니다.

 

주요 차이점 정리

항목 SDK 직접 구현 (AwsSqsConsumer) Spring @JmsListener
코드 제어 Full control abstraction
스레드 관리 직접 설정 Spring 관리
Polling 전략 직접 설계 가능 Spring 기본
Graceful Shutdown 명시적인 flag Spring lifecycle에 종속
AWS SDK 특정 기능 자유롭게 적용 제한적/추상화됨
배치 처리 & 커스텀 로직 용이 확장 제한적
프레임워크 종속성 없음/경량 Spring 의존

 

이 구조를 선택한 이유
운영 환경에서 높은 처리량과 세밀한 제어가 필요했기 때문입니다.

대안으로는 @JmsListener 방식과 Lambda 기반 처리 등이 고려될 수 있지만,
운영편의성과 제어 측면에서 직접 SDK 기반 구현이 더 적합했습니다.


3. SDK 직접 구현이 실무에서 선택된 이유

  •  명확한 처리 흐름과 종료 제어
직접 구현한 방식에서는 Polling → 처리 → 삭제까지의 흐름이 명확합니다.
특히 graceful shutdown 구조를 통해, 메시지 처리 중에도 안전하게 종료할 수 있는 패턴을 확보할 수 있었습니다.
이 방식은 Consumer의 생명주기를 프레임워크에 맡기는 방식보다 의도가 분명하고 예측 가능하다는 장점이 있다.
또한 AWS JMS Messaging Client와 달리 메시지 수신부터 삭제까지의 전 과정을 직접 설계할 수 있어
SQS의 고유 기능을 그대로 활용할 수 있다.

 

 

  • 멀티스레드 처리에 대한 유연성
Executor 기반으로 병렬 처리함으로써, 대량 메시지 처리 시 동시성 및 처리량 조절이 훨씬 유연합니다.
Spring 기본 설정보다는 세밀한 튜닝이 가능했고, 운영 상황에 따라 유리했습니다.

4. 실 실무 운영 시 보완 및 고려 사항

아래는 직접 운영하면서 느낀 보완 포인트입니다.
단순 동작만 하고 끝나는 구조보다, 다음 항목들을 고려하는 것이 실제 운영 안전에 중요합니다.

 

  •   Visibility Timeout 및 재시도 전략
SQS는 메시지를 수신하면 Visibility Timeout이 적용되고
이 시간 동안 다른 Consumer에게 보이지 않게 됩니다.
이 시간이 처리 시간보다 짧으면 메시지가 다시 보이게 되고,
재시도/중복 처리가 발생할 수 있으므로 전략 수립이 필요합니다.

 

 

  •   에러 처리 및 DLQ 연계
현재 구조에서는 메시지 처리 중 에러가 발생하더라도
해당 메시지를 별도 DLQ로 보낸 후 재처리하거나 통계 수집이 가능한 구조가 없습니다.
실무에서는 아래 항목을 추가로 고려하는 것이 안전합니다.

 

  • 스레드 풀 및 자원 관리
무한 루프 내부에서 Executor에 지속적으로 작업을 제출하는 구조이기 때문에,
스레드 풀 크기 및 종료 시 자원 정리가 중요합니다.

 4) 모니터링 및 메트릭 수집

메시지를 소비하는 것 자체보다,
지속적으로 운영 지표를 모니터링하는 구조가 필요합니다.

 


 5) 메시지 중복 처리 (Standard Queue 특성)

SQS Standard Queue를 사용하는 경우,
메시지 전달 방식은 at-least-once delivery이기 때문에
동일한 메시지가 중복 수신될 수 있음을 전제로 설계해야 한다.

 

즉, Consumer 로직에서는
“메시지는 중복될 수 있다”는 가정 하에
처리 흐름을 구성하는 것이 실무적으로 중요하다.

 

운영 환경에서는 이를 보완하기 위해
메시지에 포함된 발송 키(idempotency key) 또는 비즈니스 키를 기준으로
Redis와 같은 외부 저장소에 처리 이력을 저장하고,
이미 처리된 메시지인 경우에는
실제 비즈니스 로직을 수행하지 않고 바로 처리 종료하는 방식이 자주 사용된다.

 

이와 같은 중복 방지 방식은

  • 메시지 중복 처리로 인한 부작용을 최소화할 수 있고
  • Consumer 로직을 단순하게 유지할 수 있으며
  • FIFO Queue로 전환하지 않고도 일정 수준의 멱등성을 확보할 수 있다는 장점이 있다.

따라서 Standard Queue를 사용하는 환경이라면
Consumer 구현 시 중복 메시지를 전제로 한 설계
이에 대한 보완 로직을 함께 고려하는 것이 바람직하다.


5. 정리

AWS SQS SDK를 사용해 직접 구현한 Consumer 구조는
단순히 “동작하는 코드”를 넘어서, 실무 환경을 전제로 한 설계라는 점에서 의미가 있다.

이 구조는

  • 메시지 수신부터 처리, 삭제까지의 polling 흐름이 명확하고
  • Executor 기반의 멀티스레드 처리로 처리량 확장이 가능하며
  • 종료 시점에 메시지 수신을 중단하고 작업을 마무리하는 graceful shutdown 구조를 갖추고 있고
  • SQS의 long polling, visibility timeout 등 AWS SQS 고유 기능을 직접 활용할 수 있다는 점에서

실제 운영 환경에서도 충분히 사용 가능한 Consumer 패턴이라고 볼 수 있다.

반면, Spring의 @JmsListener는
설정과 구현이 간단하고 빠르게 도입할 수 있다는 장점이 있지만,

  • 프레임워크에 강하게 의존하게 되고
  • 메시지 수신·처리·삭제 흐름에 대한 세밀한 제어가 어렵고
  • 대량 메시지 처리나 운영 환경별 튜닝에는 한계가 존재한다.

SDK를 직접 사용하는 방식은 초기 구현 비용은 다소 높지만,
그만큼

  • 메시지 처리 흐름을 코드 레벨에서 명확히 통제할 수 있고
  • 서비스 특성에 맞게 성능 및 동시성 튜닝이 가능하며
  • 장애, 재시작, 배포 등 운영 상황에 유연하게 대응할 수 있다는 장점이 있다.

결국 선택의 기준은 “편의성”이 아니라
처리량, 운영 복잡도, 그리고 제어가 필요한 수준이며,
실무에서 대규모 메시지를 안정적으로 처리해야 한다면
SDK 기반 Consumer는 충분히 고려할 가치가 있는 선택지다.

반응형