부하분산을 위해 Fan-Out Pattern 작게 적용해보기
더블유클럽 재직 중 작성한 경험 기록
인스턴스 부하 분산을 위해 서비스에 Fan-Out Pattern을 작게 적용해본 기록입니다.
Fan-Out Pattern?
원래 Fan-Out이라는 용어는 논리 회로에서 하나의 논리 게이트의 출력이 얼마나 많은 논리게이트의 입력으로 사용되었는지를 나타내는 말입니다.
Fan-Out Pattern이란, Fan-Out의 동작 방식을 빗대어 하나의 정보가 생성되었을때 그 정보를 필요로 하는 모든 Task에게 전파해주는 패턴입니다. 해당 패턴은 데이터 중심 애플리케이션 설계의 1장에서 소개하는 Twitter의 사용 사례가 유명합니다.
왜 적용했는가?
개발 중인 매칭 서비스에는 '시간 만료 취소'라는 스케줄러가 매일 실행됩니다. 남녀 회원이 만남 약속 시간과 장소를 확정하지 못했을때, 해당 약속을 취소시키고 누구의 귀책인지 판별하여 관련 로직을 실행시킵니다. 흐름은 아래와 같으며 세부적인 표현은 어렵기에 굵직한 흐름으로만 표현했습니다.

기존에는 큰 문제가 없었지만, 서비스가 성장함에 따라 시간 만료 취소 대상도 정량적으로 증가하였습니다. 요청을 받은 단 하나의 인스턴스에서 해당 로직을 처리하기에 점점 더 버거워지는 상황이 생겼습니다. 따라서 안전하게 부하 분산하기 위한 방법으로 Fan-Out 패턴 적용을 고민했습니다.
어떻게 적용했는가?
1. AWS SNS + SQS로 Fan-Out 구조 생성
AWS SNS, SQS 조합을 이용하면 Fan-Out 구조를 생각보다 쉽게 구현할 수 있습니다. 아래는 시간만료 취소 처리를 위해 생성한 SNS topic과 SQS 목록이며, YouTube에 공개된 setup 영상을 참고하여 구성했습니다.


2. 시간 만료 취소 대상 정보를 SNS로 발행하도록 로직 구현
시간 만료 취소 스케줄러 실행 요청을 받은 인스턴스에서 대상탐지 후 SNS 메시지 발행을 진행합니다. 이때 귀책 판단을 통해 발행 메시지에 성별 정보를 추가하며, SNS는 filtering을 통해 남성 귀책처리 SQS와 여성 귀책처리 SQS로 구분하여 전파되도록 합니다.
SNS 발행 Flow

SNS 측에 설정한 SQS filtering


발행 코드
private async snsPublishForTimeoutMatchingByFemale(matching: IMatching & Document<IMatching>) {
await snsClient.publish({
Message: JSON.stringify({
matchingId: matching.id,
dateTime: new Date(),
gender: 'female',
}),
TopicArn: '.......',
MessageAttributes: {
gender: {
DataType: 'String',
StringValue: 'female',
},
},
});
}
private async snsPublishForTimeoutMatchingByMale(matching: IMatching & Document<IMatching>) {
await snsClient.publish({
Message: JSON.stringify({
matchingId: matching.id,
dateTime: new Date(),
gender: 'male',
}),
TopicArn: '.......',
MessageAttributes: {
gender: {
DataType: 'String',
StringValue: 'male',
},
},
});
}
3. 전파받은 남성/여성 SQS Queue에서 정보 수신 및 귀책 처리 로직 실행
Server 인스턴스 배포 및 실행시 SQS consumer를 실행하도록 합니다. 각 인스턴스는 대기하다가 SQS 정보를 수신하면 귀책 처리 로직을 진행합니다.
각 SQS의 Consume Flow

Server 배포시 app.ts에서 전체 SQS consumer 실행
// app.ts
sqsConsumerController.consumerAllStart();
// sqs-consumer.controller.ts
async consumerAllStart() {
if (env.isLocal()) {
return;
}
// app 실행 후, 일정 시간 뒤에 consumer 실행하도록 유휴시간을 둠.
setTimeout(() => {
this.sqsConsumerService.timeoutMatchingByFemaleConsumer();
this.sqsConsumerService.timeoutMatchingByMaleConsumer();
this.consumerStartNoti();
}, 10000);
}
ETC
22년 8월에 적용한 이후 기대한 대로 동작 및 운영하였으나, 상정하지 못한 이슈가 발생하여 이를 해결한 내용도 간략히 첨부합니다.
AWS SNS Region 정보 미기입으로 인한 메시지 발행 실패
적용 시점(22년 8월)에는 문제가 없었으나, AWS SDK 업데이트 이후(22년 12월) AWS에서 SNS의 Region 정보를 필수로 입력받기 시작하면서 해당 스케줄러 실행시 AWS SNS로 메시지 발행이 되지 않는 장애가 발생. 문제 파악 후 Region 정보 추가 및 Server 재배포하여 장애 해소.