SQS → Consumer 파이프라인에서 메시지 수신이 실패한 상황에서 그 원인과 대응 전략을 회고하기 위한 글이다.
무슨 일이 있었나?
SNS → SQS → Consumer 구조는 AWS에서 흔하게 쓰이는 패턴이다. 우리도 이 시스템을 채택하고 있었고 한 서비스가 하나의 큐를 리스닝하고 있는 구조이다.
그러나 운영 중에 서비스가 동작하지 않는다는 제보가 생겼고 큐를 확인했더니 메인 큐에 여러 메시지가 쌓여있는 것을 보았다. 🤯
하지만 DLQ에는 쌓여있는 메시지가 0개였다. 실패된 메시지에 대해서는 3번 재시도 후 DLQ로 빠지고 DLQ의 메시지 수가 증가하면 알림이 트리거 되어야 한다.
하지만 메시지가 DLQ에도 없고 메인 큐에서도 지속적으로 증가하지 않고 메시지가 증가했다가 사라지는 패턴을 반복하고 있었다.
왜 실패된 메시지가 재시도 되어 DLQ로 빠지지 못하고 메인 큐에 계속 쌓여있었을까?
큐가 소비되지 못하고 메시지에 쌓인 이유
우선 가장 근본적인 원인은 설정 오류로 인해 Consumer가 큐를 찾지 못하고 메시지를 소비하지 못한 것이 원인이었다. 그래서 일정 기간 메시지가 큐에 쌓여있었고, Retention Period (1일) 이후에 자동으로 큐에서 삭제된 것이다.

SQS 메인 큐에 있는 메시지는 Consumer가 Polling 방식으로 ReceiveMessage API를 전송해 가져간다. Consumer가 메시지를 잘 받았다고 DeleteMessage API를 보내야 SQS는 '아 적어도 하나의 Consumer가 메세지를 잘 받았구나' 하고 안전하게 삭제할 수 있다는 것이다.
DLQ로 메시지가 전달되지 않은 이유?

DLQ는 메인 큐에서 일정 횟수 이상 리트라이된 메시지가 이동하는 곳이다.
메시지가 Consumer로 전달될 때마다 메시지의 RecieveCount가 1 씩 증가한다. Consumer에서 메시지를 처리하지 못하면 다시 재시도하는데, 이때 MaxRecieveCount에 도달하게 되면 해당 메시지는 DLQ로 빠진다.
메시지가 리트라이가 되기 위해서는 Consumer가 메시지를 소비해야만 가능하다. 하지만 SQS와 Consumer의 파이프라인이 고장나있었기 때문에 리트라이가 되지 않았고, DLQ로 이동하지 못한 채 메인 큐에 쌓여있었던 것이다.
대응을 위한 CloudWatch 지표 알아보기
1. NumberOfMessagesReceived / MessagesDeleted
비정상 구간에서 MessagesReceived가 0에 수렴하고, MessagesDeleted도 0에 가까웠다.
이건 Consumer가 처리를 시도했는데 실패했다는 뜻이 아니라, 아예 가져가질(Receive) 않았고, 따라서 지울(Delete) 수도 없었다는 의미다. 즉 Consumer가 정상적으로 큐를 읽고 있지 않았다.
2. NumberOfEmptyReceives
정상적으로 polling하는 Consumer는 메시지가 없을 때도 ReceiveMessage를 날리고, 그 결과가 empty일 수밖에 없다. 그래서 평소에는 이 지표가 어느 정도 나온다. 근데 비정상 구간에서는 EmptyReceives가 0이었다.
이건 메시지가 없어서 empty가 아니라, 폴링 자체가 멈췄다는 뜻이었다. SQS-Consumer 파이프라인이 고장났음을 알아챌 수 있었던 중요한 지표임을 알게 되었다.
3. ApproximateAgeOfOldestMessage ⭐️⭐️⭐️
메인 큐 내에서 가장 오래된 메시지의 나이 (큐에 들어온 이후로 유지된 기간) 에 대한 지표이다. 비정상 구간에서 ApproximateAgeOfOldestMessage가 86,400초(즉, 1일) 근처에서 평평하게 유지되는 구간이 있었다.

현재 큐 세팅에서 Retention Period가 1일이기 때문에, 가장 오래된 메시지는 24시간이 되는 순간 만료로 삭제된다.
그래서 oldest age가 계속 올라가서 며칠이 되는 게 아니라, 1일 근처에서 더 이상 못 올라가고 유지되는 모양이 나온다.
그렇기 때문에 이 지표는 Retention Period 보다 높아질 수 없고 이미 이 지표가 Retention Period와 같게 표시된다? 그러면 이미 여러 개의 메시지가 삭제된 상태일 것이다. 😵 이렇게 삭제된 메시지는 DLQ에도 남지 않기 때문에, 조기에 발견하기 위해서 Retention Period보다 낮은 Threshold로 알림을 걸어놓고 모니터링하는 것이 중요하다.
알게된 점
- ApproximateAgeOfOldestMessage라는 지표에 대해 잘 몰랐으며 메인 큐의 Retention Period가 존재함도 모르고 있었다. 하지만 이번 기회에 SQS에 대해서 이해할 수 있었고, 이 지표를 잘 모니터링하는 게 중요하다고 느꼈다.
- DLQ의 동작 원리에 대해 이해할 수 있었다. Spring Boot에서는 SQSTextMessage.acknowledge()를 사용하여 '메시지를 잘 받았고 재시도 처리를 하지 않아도 된다'라는 신호를 보낸다. 만약 어느 Consumer에서도 이 신호를 받지 못하면 그 메세지는 DLQ로 넘어가지도 않고 메인 큐에 저장되어있다가 Retention Period가 지나면 삭제된다.

- 즉, DLQ로 넘어가기 위해서는 적어도 Consumer가 Receive할 수 있어야 가능한 것이다. Consumer가 Receive 해서 여러 번 재시도 했을 때 실패하면 그제서야 DLQ 로 넘어갈 수 있는 것이다.
'Server > MQ' 카테고리의 다른 글
| [kafka] Window OS에 Kafka 설치하고 실행하기 (0) | 2023.03.21 |
|---|