Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- islowercase()
- isuppercase()
- 스프링
- 베주계수
- GithubActions
- cicd
- 스프링환경설정
- addDoc
- Github Actions
- 동일성과 동등성
- sql 데이터형 변환
- git 컨벤션
- string
- 최대공약수
- stringbuilder의 reverse()
- 래퍼타입
- 자바 스트링
- ineer join
- Git사용법
- 프로그래머스 레벨1
- string과 stringbuilder
- 모던자바
- toLowerCase()
- 스프링뼈대
- 자바 유클리드
- 최소공배수
- while과 two-pointer
- 자바 최소공배수
- StringBuilder
- 유클리드호제법
Archives
- Today
- Total
주노 님의 블로그
[트러블 슈팅] 이메일 발송을 비동기적으로 전환한 이유 본문
요약
이메일 인증을 동기 > 비동기로 전환하며
4.69s > 100ms로 97.87% 개선했다.
바꾸기전 속도는 4.69s였다
public HashMap<String, Object> sendMailAndStoreCode(String mail) {
HashMap<String, Object> responseMap = new HashMap<>();
//유저의 이메일 체크 로직.
User user = userService.findUserEmail(mail);
//소셜로그인일시, 비밀번호 변경을위한 이메일인증 불가.
if (user.isSocialLogin()) {
throw new ApplicationException(SOCIAL_LOGIN_UPDATE_NOT_ALLOWED);
}
try {
int number = createRandomNumber();
MimeMessage message = createMail(mail, number);
javaMailSender.send(message);
// Redis에 저장
String key = "find:password:" + mail;
redisTemplate.opsForValue().set(key, String.valueOf(number), TTL, TimeUnit.SECONDS);
responseMap.put("success", Boolean.TRUE);
responseMap.put("number", number);
}
catch (MessagingException exception) {
log.error("메일 전송 실패 : {}", exception.getMessage());
responseMap.put("success", Boolean.FALSE);
responseMap.put("error", "메일 전송에 실패했습니다.");
}
return responseMap;
}
위 로직은 이메일을 전송하는 로직인데
외부 이메일 전송 서비스의 응답시간을 기다리는것이 큰 병목임을 확인했다.
이메일 전송은 이메일 전송을위해 smtp 서버로 요청을 보낸다
서버의 역할은 거기서 끝이지만
응답을 받을때까지 계속 물고 있는 것이 문제가 되었다.
서버가 이걸 물지 않고도, 다른작업을 할 수 있게 하면 어떨까?
그래서 비동기 작업으로 변경하였다
package com.spotlightspace.core.auth.email;
import static com.spotlightspace.common.exception.ErrorCode.INVALID_EMAIL_MATCH;
import static com.spotlightspace.common.exception.ErrorCode.SOCIAL_LOGIN_UPDATE_NOT_ALLOWED;
import com.spotlightspace.common.exception.ApplicationException;
import com.spotlightspace.core.auth.email.dto.MatchMailRequestDto;
import com.spotlightspace.core.user.domain.User;
import com.spotlightspace.core.user.service.UserService;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
@Slf4j
public class MailService {
private final JavaMailSender javaMailSender;
private final StringRedisTemplate redisTemplate;
private final UserService userService;
@Value("${spring.mail.username}")
private String senderEmail;
//redis의 ttl을 설정하는 변수입니다.
private static final long TTL = 5 * 60;
public void emailUserCehck(String mail) {
// 유저의 이메일 체크 로직
User user = userService.findUserEmail(mail);
// 소셜 로그인일 시, 비밀번호 변경을 위한 이메일 인증 불가
if (user.isSocialLogin()) {
throw new ApplicationException(SOCIAL_LOGIN_UPDATE_NOT_ALLOWED);
}
}
@Async
public CompletableFuture<HashMap<String, Object>> sendMail(String mail) throws MessagingException {
HashMap<String, Object> responseMap = new HashMap<>();
int number = createRandomNumber();
MimeMessage message = createMail(mail, number);
javaMailSender.send(message);
// Redis에 저장
String key = "find:password:" + mail;
redisTemplate.opsForValue().set(key, String.valueOf(number), TTL, TimeUnit.SECONDS);
responseMap.put("success", Boolean.TRUE);
responseMap.put("number", number);
return CompletableFuture.completedFuture(responseMap);
}
private int createRandomNumber() {
return (int) (Math.random() * (900000)) + 100000; // 6자리 랜덤 숫자
}
// 메일 메시지 생성
private MimeMessage createMail(String mail, int number) throws MessagingException {
mail = mail.trim();
MimeMessage message = javaMailSender.createMimeMessage();
// 이메일 형식 체크
InternetAddress emailAddr = new InternetAddress(mail);
emailAddr.validate();
//발신자 이메일 주소를 설정함 (보내는사람 - 혜미.)
message.setFrom(senderEmail);
//수신자 이메일을 설정함 (받는사람)
message.setRecipients(MimeMessage.RecipientType.TO, mail);
//메일 본문
message.setSubject("이메일 인증");
String body = "<h3>요청하신 인증 번호입니다.</h3>"
+ "<h1>" + number + "</h1>"
+ "<h3>감사합니다.</h3>";
message.setText(body, "UTF-8", "html");
return message;
}
public boolean mailCheck(MatchMailRequestDto machMailRequestDto) {
//저장된 값을 redis에서 가져옵니다.
String key = "find:password:" + machMailRequestDto.getEmail();
String storedNumber = redisTemplate.opsForValue().get(key);
//값이 있다면 true로 없다면 false로 반환합니다.
boolean isMatch = storedNumber != null && storedNumber.equals(machMailRequestDto.getUserNumber());
if (!isMatch) {
throw new ApplicationException(INVALID_EMAIL_MATCH);
}
return true;
}
}
비동기로 변경을 하며
서버는 전송을 하게되면 쓰레드가 따로 생성이되어서 이메일 전송로직을 작동하게 된다.
비동기로 변경되며 100ms로 개선이 된것을 볼 수 있다.
물론 실패 성공 상태는 클라이언트 측에서 확인 할 수 없지만.
이메일 전송 성공 여부는 비즈니스 로직에 큰 영향을 미치지 않았다.
사용자 경험이 우선 순위가 될것이라 생각하여 고려하였다.