-
[AWS] 이메일 대량 발송 SA 회고카테고리 없음 2024. 7. 21. 16:52
우리 Pillars팀은 완성도있는 아키텍처를 구성하기 위해 많은 디벨롭을 거쳤고 아래와 같은 아키텍처를 구축해냈다.
왜 우리팀이 이런 구성의 아키텍처를 생각했는지 오늘 포스팅에서 말해보고자 한다.
주어진 요구사항에 핏한 아키텍처를 구상했다.
이메일 대량 발송 시스템 설계 요구사항
- 한꺼번에 대량으로 이메일을 보내는 기능을 구현
- 이미지를 첨부해서 이메일을 보낼 수 있도록 구현
인프라 요구 조건
- 매일 최대 500건의 이메일 대량 발송 필요
- 이메일당 평균 크기는 100KB 예상
- 이메일 발송 지연 시간은 5분 이내
심화 조건
- 발송 실패 처리: 실패한 이메일에 대한 자동 재시도 로직 (최대 3회)
- 구독 관리: 사용자별 이메일 수신 설정 및 구독 해지 기능
- 스팸 방지: SES의 SPF를 통한 도메인 인증 (예시 참조)
서버리스 환경을 선택한 이유
AWS EC2와 같은 서비스를 사용할 수 있었지만,
서버리스 서비스인 Lambda를 사용한 이유는 빠른 시작, 비용 감소, 고가용성 등의 이점을 살리기 위함이었다.
매일 최대로 보내는 이메일이 500건이라 Lambda의 최대 실행 가능 시간인 15분을 초과하지 않겠다는 생각을 했지만,
추후 1000건, 100000건... 까지 늘어날 상황을 감안하여 확장 가능한 아키텍처를 구상하고자 했다.
따라서, 15분 이상의 작업을 Lambda를 사용해 해결할 수 있는 방법을 찾다가 AWS Step Function이 눈에 들어왔다.
AWS Step Function을 선택한 이유
AWS Step Function은 추출, 변환, 적재(ETL) 프로세스 자동화, 대규모 병렬 워크플로 오케스트레이션 등 긴 작업을 처리할 수 있는 서비스이다. 이 중 아래와 같은 장점덕분에 AWS Step Function을 고민없이 선택했다.
- Map을 사용해 동일한 워크플로우(예: Lambda 함수 호출)를 병렬로 실행
- 전체 실행시간을 단축 & 복잡한 반복 로직을 간결하게 처리
- 이메일이 늘어날수록 처리속도 상승
- 디버깅 및 모니터링 쉬움
- 시각적 편집 도구인 Workflow Studio를 사용하여 쉽게 아키텍처 개발 가능
Step Function Map Flow
각 Lambda의 기능
Lambda(1)에서 dynamoDB 로부터 10명의 유저 정보를 가져와 Lambda(2)를 호출하면 10명의 유저 payload와 템플릿 정보를 SQS로 전달(*SQS를 거치는 이유는 이메일 누락을 막기 위함)한다.
그 후 Lambda(3)은 SQS에서 10명의 유저 payload를 가져와 템플릿에 삽입하여 이메일을 완성한 후, SES로 이메일을 전송한다. 200개의 이메일을 전송학 위해 위 과정을 map을 통해 20번 반복한다. (10*20 = 200개의 이메일)
요구사항은 500건인데 200건의 이메일만 전송하는 이유
실제 이메일은 sandbox를 해제하지 않는 이상 AWS 계정당 최대 메일 200개만을 보낼 수 있기에, 실행 예시는 200개를 기준으로 작업했다.
이메일을 10개씩 보내는 이유
이메일이 누락되는 상황을 막기위해(by dead letter queue) SQS를 사용했다.
하지만 Amazon SQS는 단일 메시지 배치 요청에 최대 10개 메시지 제한을 두고 있었고 이는 아키텍처 확장성에 문제를 가져왔다.
해커톤 동안은 빠른 이메일 전송보다 안전한 이메일 전송이 더 중요하다 생각했기에 SQS를 포기할 수 없었고,
이메일 전송 개수를 10개로 제한해버리는 멍청한 결정을 내렸다.
해커톤 심사에서도 이 부분은 (-)로 비추어졌고 추후 어떻게 했으면 안전함 + 빠른 전송을 모두 챙길 수 있었을지 생각했다.
SQS를 제거하여 10개 이메일 전송 제한을 없애고,
Map 안에서 Choice State(IsSuccess)를 사용해 이메일 성공여부에 따른 재시도 로직을 추가했다면 안전하면서도 빠른 이메일 전송이 가능했을 것 같다.
DynamoDB를 선택한 이유
그 당시 key-value 형태이므로 READ 속도가 빠른 장점을 누리기 위해 DynamoDB에 유저 정보를 저장해뒀다.
S3에 CSV 파일로 유저정보를 저장하여 사용하는 방법은 원하는 유저정보만을 뽑아올 수 없다는 단점에 선택하지 않았었다.
하지만, 해커톤 심사에서 비용적인 부분을 고려했다면 RDS를 쓰는 것이 나았을 것이라는 평가를 받고 다시 어떻게 구성했다면 좋았을지 생각해보았다.
Lambda ⮕ RDS ⮕ S3 로 구성하여 이벤트 종류에 따른 SQL을 실행하여 유저를 식별해 가져온 후 이를 CSV 파일로 S3에 저장한 뒤, Stepfunction에서 배치작업을 돌렸다면 더 좋았을 것 같다.
그 외 기능
이메일을 대량 발송하는 것 외에도 추가적인 개발 기능을 생각해보았다.
1) 맞춤서비스 2) 스팸문자 3) 이메일 템플릿 제공 등의 기능이 회의를 통해 나왔고 그 중 선택된 기능은 '유저가 이메일을 열람하는지의 유무, 얼마나 지속적으로 열람하는지 비율을 따져 맞춤형 이메일을 제공 또는 유저리스트에서 삭제하여 마케팅 비용을 줄임' 이었다.
Cloudwatch가 지표기반으로 모니터링하는 용도로 쓰이는 것은 잘 알려져 있을 것이다.
Cloudwatch는 위 기능 외에도 Cloudwatch logs 기능을 제공하는데, 이를 통해 유저가 이메일을 열람했는지 여부를 확인 할 수 있다. 따라서 우리팀은 '본인의 전체 이메일 중 몇개를 열람했는지 비율'을 산정해 lambda로 이메일을 지속적으로 열람하지 않는 유저는 수신자리스트에서 제거했다.
회고
우리팀이 처음부터 Lambda + Step Function의 조합을 생각해낸 건 아니었다.
확장성을 고려하면서도 오버엔지니어링을 하지 않기 위해 긴 시간동안 레퍼런스를 찾고 취합하는 과정을 거쳤다.
팀플을 할 때마다 느끼는 것이지만, 모두의 의견이 같은 곳을 바라보기란 쉽지 않다. 하지만, 우리팀은 각자 의견을 피력하면서도 양보했기 때문에 3주라는 시간 내에 꽤 괜찮은 결과물을 낼 수 있었다고 생각한다.
아쉽게도 앞서 말한 아키텍처의 한계때문에 수상을 하지는 못했지만, 더 큰 걸 얻어냈다.
AWS 자격증을 딴 뒤로 실제 시스템의 특성을 파악해 아키텍처를 짜보는 것은 처음인데, 이 과정에서 정말 내가 잘할 수 있고 즐거워하는 것이 무엇인지 깨달았기 때문이다. 따라서, 앞으로는 클라우드에 대한 포스팅이 많아질 것 같다.
p.s 해커톤을 주최하느라 고생해주신 AWS Cloud Club 캡틴분들 감사합니다.
자세한 내용은 아래 깃허브에서 확인하실 수 있습니다.