서론
SQS의 메세지 중복되지 않아야한다면, 어떤 방법을 사용할 수 있을까요?
멱등성을 지킨 코드 하나만으로 과연 대응할 수 있을까요?
면접에서 해당 질문을 듣고난 후, 답변을 하기 위해 정말 순간적으로 뇌의 저장공간들을 빠르게 훑었던 것 같습니다.
당시 SQS를 생성할 때 'FIFO'라는 방식이 있다는 것이 스치듯 생각이 나서, FIFO로 메세지를 설정해놓을 것 같다고 재빠르게 대답했지만,
왜 이 방식이 중복을 방해하는지에 대해 알지 못했습니다. 더불어 해당 방법 외에도 어떤 방법들이 중복을 막을 수 있는지 궁금해졌습니다.
본론
FIFO
AWS SQS에서 제공하는 FIFO 설정은 중복 제거 ID를 사용하여 해당 대기열의 메세지를 고유하게 식별하고 중복을 방지하는 것을 도와줍니다.
생산자가 메시지를 전송할 때 제공한 중복 제거 ID가 동일한 메시지가 특정 시간(기본 5분) 이내에 다시 전송되면 SQS는 이를 중복으로 간주하고, 첫 번째 메시지만 처리합니다. 만약 중복 제거 ID를 명시적으로 지정하지 않고 콘텐츠 기반 중복 제거를 활성화하면, SQS는 메세지 본문을 해싱하여 해당 값을 기반으로 중복 제거 ID를 생성하여 중복을 관리합니다.
해당 매커니즘은 지정된 기간 내에 중복 메시지를 제거하여 정확하게 한 번만 전송될 수 있도록 돕습니다.
FIFO 큐는 메시지가 전송된 순서대로 정확히 한 번 전달되는 것을 보장하려고 노력합니다. 메시지는 메시지 그룹 ID를 기반으로 순서를 정렬하며, 여러 생산자 또는 스레드가 동일한 메시지 그룹 ID로 메시지를 전송하는 경우, 메시지가 도착한 순서대로 저장되고 처리합니다.
즉, 메시지 그룹 ID내에서는 모든 메세지가 엄격한 순서로 전송 및 수신되기 때문에, 순서가 지켜져야 하는 메시지의 경우 그룹 ID를 통일하면 됩니다.
더불어 메시지 처리 실패 시 재전송 매커니즘을 고려하여 적절한 Visibility Timeout 시간을 설정하고, 반복 실패한 메시지를 별도 큐에 저장하여, 재처리하거나 원인을 분석할 수 있도록 돕는 DLQ를 구성하여 처리 불가능한 메시지에 대한 정책을 구성하는 것도 중요합니다.
Database를 사용한 사례
FIFO뿐 아니라 다양한 방법을 고려해야할 수 있습니다. Database를 활용한 방법도 그 중 하나가 될 것 같습니다.
Producer는 메시지를 발행하기 전에 관련된 주요 데이터를 Database에 영속화 합니다.
Consumer는 메시지 수신 후, 메시지 내 고유 ID를 기준으로 DB에서의 처리 내역을 조회합니다. 이미 '완료' 상태라면 무시하고 메시지를 삭제합니다. 아직 처리되지 않았거나 '재시도 가능 실패' 상태라면, 처리를 시도하기 전에 DB에 '처리 중' 상태로 업데이트(선점)합니다. 실제 작업(예: 이메일 발송)이 성공하면 DB 상태를 '완료'로 업데이트하고, 실패하면 '실패(재시도 가능/불가능)' 상태로 업데이트합니다. 최종적으로 SQS 메시지를 적절히 처리(삭제 또는 가시성 유지)합니다
데이터베이스를 활용한 중복 방지 전략을 고려할 때, Producer와 Consumer가 공유하는 데이터베이스의 존재 여부가 중요할 것 같습니다. 발행하고, 소비하는 두 곳에서 공유하는 DB가 존재한다면 해당 방법을 사용할 수 있지만, 존재하지 않는다면 소비되는 쪽에서 모두 기록하는 형태로 설계할 수 있을 것 같습니다.
더불어 Transaction 관리에 대해서도 고민을 더 해야합니다. (해당 질문을 받았을 때, 조금 더 현명하게 고민하고 답변했으면 좋았을텐데..)
대부분의 Message는 비동기로 처리되기 떄문에, 이후 해당 메시지가 잘 수행되었는지, 수행되지 않았는지는 발급하는 트랜잭션에서는 알기 어렵습니다.
그러므로 메시지 발행 전 관련된 주요 데이터의 커밋이 완료된 후 SQS에 메시지를 발행하는 것이 데이터 정합성과 안정성을 높이는 데 중요합니다. 만약 데이터베이스 저장에 실패하면, 발행 전에 롤백하게 되어 데이터 불일치를 방지할 수 있습니다.
더불어 동시에 많은 메시지가 들어오는 케이스도 고려하여, 여러 Consumer 인스턴스가 동시에 동일한 메시지를 처리하려고 시도할 수 있으므로, 데이터베이스 레벨에서 락 등의 매커니즘을 사용하여 동시성을 제어하는 부분에 대한 고민도 함께 이루어져야 할 것 같습니다.
결론
중복 방지를 위해 어플리케이션 레벨 뿐 아니라 시스템 레벨의 전략을 적절히 조합하여 안정성을 높일 수 있을 것 같습니다. 시스템적으로 FIFO 큐를 메시지 전달 단계에서의 중복 가능성을 최소화합니다. 하지만 완벽하게 시스템적으로 보장하기는 어려울 수 있으므로, 어플리케이션 레벨에서도 대응이 필요합니다.
메시지 처리 로직을 멱등성을 갖도록 설계하고, 재시도 메커니즘을 고려하고, 메시지 처리 과정을 상세하고 로깅하고 모니터링하여 문제 발생에 대한 대응이 이루어져야 할 것 같습니다.
더불어 데이터베이스와같은 외부 저장소를 활용하여 메세지의 처리 상태를 추적하고 관리함으로서 어플리케이션 레벨에서 안정성을 추가로 높일 수 있을 것 같습니다.
덧붙이는 말
정확히 1회를 처리한다는 FIFO가 완벽하지 않다는 것 또한 고려해야합니다. FIFO는 표준 큐에 비해 상대적으로 낮은 처리량을 가지고 있습니다.문서를 확인해보면 초당 최대 3,000개의 메시지를 지원한다고 적혀 있습니다. 초당 많은 메시지를 발행하는 경우, 해당 설정이 병목을 일으킬 수 있습니다.
정확히 한 번 전송 기능에 대한 트레이드오프라고 볼 수 있습니다.
레퍼런스
Amazon SQS의 정확히 1회 처리 - Amazon Simple Queue Service
이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.
docs.aws.amazon.com
Amazon SQS FIFO 대기열 - Amazon Simple Queue Service
이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.
docs.aws.amazon.com
Amazon Simple Queue Service란 무엇인가요? - Amazon Simple Queue Service
Amazon SQS는 최대 메시지 보존 기간 넘게 대기열에 유지된 메시지를 자동으로 삭제합니다. 기본 메시지 보존 기간은 4일입니다. 그러나 SetQueueAttributes 작업을 사용하면 메시지 보존 기간을 60초에
docs.aws.amazon.com