[COGO] SQS+SES+Lambda로 비동기 처리 안정성 보완

hwee·2024년 8월 26일
1

COGO개발과정

목록 보기
12/12
post-thumbnail

결과 요약


1. 메일 전송 비동기 처리를 AWS 아키텍처로 위임했다.
2. SQS로 전송된 요청 메시지는, 람다를 통해 SES로 전달된 후 클라이언트에 메일이 전송된다.
3. 오류 발생시 SQS에 연결된 DLQ에 메시지가 전송되고, 4회 이상 이런 상황이 발생하면 나에게 알림이 오게 로직을 구성하였다.

문제 상황

JavaMailSender를 통해 유저에게 인증 메일을 보내는데, 메인 스레드의 대기 시간이 너무 길어 비동기 처리로 전환한 바 있다. (이전 게시물)
하지만 메일 전송 결과를 확인하지 않고 응답을 반환하기 때문에, 안정성 차원에서 이슈가 생길 수 있다는 피드백을 받았다.
따라서 해당 상황이 발생할 수 있는지, 테스트 코드를 작성했다.

@Test
    void testSendAuthenticationEmailReturnsCodeAndHandlesException() throws MessagingException, InterruptedException {
        // given
        String receiver = "test@example.com";
        // sendMail이 호출될 때 예외를 던지도록 설정(런타임)
        doThrow(new RuntimeException("Failed to send email"))
                .when(javaMailSender).send(any(MimeMessage.class));
        // when
        String resultCode = null;
        try {
            resultCode = emailUtil.sendAuthenticationEmail(receiver);
        } catch (RuntimeException e) {
            // 예외가 발생해도 메서드가 종료되지 않고 정상적으로 처리되었는지 확인
            System.out.println("예외 발생: " + e.getMessage());
        }
        // then
        //assertNotNull(resultCode);  // 반환된 코드가 null이 아닌지 확인
        assertEquals(6, resultCode.length());  // 반환된 코드의 길이가 6자리인지 확인
        assertTrue(resultCode.matches("\\d{6}"));  // 반환된 코드가 6자리 숫자인지 확인
        System.out.println("resultCode = " + resultCode);
        verify(javaMailSender, times(1)).send(any(MimeMessage.class));  // sendMail이 한 번만 호출되었는지 확인
    }

내 메일전송 기능은
1. 인증메일 전송 메서드 호출
2. 해당 메소드에서 메일 전송 메서드에 메일 문구를 파라미터로 포함해 호출
로 구성되어 있는데, 2번에서 메일 전송 메서드가 런타임에러를 던지는 경우를 가정하고, 1번에서 정상적으로 코드를 반환하며 2번 메서드는 재실행이 되지 않는다면 해당 비즈니스 로직에 문제점이 존재한다고 판단하였다.

늘 좋지 않은 예감은 적중한다.
현재 로직은 메일 전송에 실패하거나 오류가 발생해도, 어떠한 핸들링 과정 없이 생성한 인증 코드를 반환한다.
따라서 오류 발생시 메일 전송을 재시도할 수 있도록 로직을 추가할 예정이다.

해결방법

고안한 해결방법은 3가지다.
1. 오류 발생시 로그에 남기고, 메일을 n회(임의의 수) 재전송하도록 설정
2. AWS의 SQS+SES로 비동기 메일 전송 기능의 책임을 AWS로 넘기고, 메일 전송 실패시 재전송 로직 구성
3. 그냥 SES로 메일 전송 기능의 책임만 넘기기
내가 선택한 방법은 2번인데, 1번은 간단하지만 메일 재전송 기능을 2번에 비하여 확실하게 책임지지 못할 것이라 판단하였고, 3번은 2번에 비하여 간단하지만, 마찬가지로 재전송 기능을 구축하는 데 어려움을 겪을 듯했다.
즉, 메시지 큐에 메일 전송 요청을 넣고, SQS에서 이 메시지를 읽으면 SES로 메일이 보내지는 식으로 구성한 것이다. 만약 메일 전송에 실패하면 Dead Letter Queue(DLQ)에서 별도의 처리를 할 예정이다.
메시지 큐는 다양하지만, 메일 전송 자체의 기능을 SES로 구현하기로 했고, 이와 호환성이 좋은 SQS를 연결하는 것이 하나의 아키텍처를 구축하는 데 깔끔하다고 생각하여 SQS+SES로 구성하였다.

해결 과정

참고 reference
메시지 큐란?
SQS와 Springboot
SES란?

1. SQS 설정


먼저 SQS(WAS에서 이메일 전송 요청 메시지를 받는다)를 생성하고, SQS에서 SES로 전송하는데 실패한 메시지들을 저장하는 DLQ를 연결해주었다.

이렇게 연결된 DLQ에 SES로 전송하는데 실패한 메시지들이 있는지 Lambda를 고의로 수정해 확인해보았다.

제대로 들어오는 것이 확인된다.
이로써 SES에 전송이 실패한 메시지가 DLQ에 들어오게 된다.
DLQ에서 메시지 처리는 고민해본 결과, 3번만 SQS에 재전송하고, 4번 이상 DLQ에 반환되면 내 개인 메일로 알림이 와서 직접 처리할 수 있게 하였다.
해당 로직은 SQS와 마찬가지로 람다를 연결해 주었다.

2. 트리거 Lambda 적용


SQS에 메시지가 들어오면 바로 SES로 전달하기 위해 Lambda를 트리거로 적용해주었다.
적용 후, 테스트를 진행해보니 CloudWatch에서 성공 로그를 발견할 수 있었다.

Lambda는 SQS에서 트리거로 작동해 SES로 메시지를 보내므로, IAM에서 람다에 SQS와 SES의 access를 설정해주어야 한다.

3. SES 설정

SES에서 메일 발송자의 도메인 인증을 하고, 해당 도메인을 통해 사용자에게 메일을 보내도록 설정해주었다.

마찬가지로, CloudWatch에서 요청이 들어왔고, 해당 요청을 처리했다는 내역들을 볼 수 있었다.
하지만 SES를 제대로 사용하기 위해서는 '샌드박스 모드'를 풀어야 하는데, 이 정보는 reference에서 확인할 수 있다.
샌드박스 모드를 풀기 위해 AWS측에 Support창에서 웹메일을 보냈고, 15시간가량 후에 샌드박스 모드가 풀렸다.

메일은 위와 같이 SES사용 이유와, 메일을 보낼 대상, 메일 내용들을 첨부했다.

4. 테스트

먼저 스웨거로 메일 전송을 요청해보았다.


제대로 발송되는 것을 확인했고, 이제 에러 발생 상황을 가정하기 위해 SQS에 연결된 람다를 살짝 만지고 테스트 해보았다.

이렇게 4번이상 DLQ로 들어온 메시지는 나에게 직접 메일이 날라오는 알림 로직을 구축해두었다.

profile
https://fuzzy-hose-356.notion.site/1ee34212ee2d42bdbb3c4a258a672612

0개의 댓글