본문 바로가기

Yonsei Golf

Email 성능 개선기

1. 문제 정의

현재 연세골프 서비스는, 싱글 스레드로 지원 대기자, 합격 불합격 메일을 전송하고 있습니다.

대기자 명단이나, 지원자 수가 적을 경우 문제가 되지 않지만, 매 학기 100여명이 지원하는 동아리를 운영하기에는 큰 부담이 될 수 있습니다.

이번 포스팅에서는 이러한 문제를 해결하는 방법에 대해서 알아보도록 하겠습니다.

2. 해결 방법

2 - 1 멀티 스레드

가장 먼저 생각한 방법은 멀티 스레드로 메일을 전송하는 것이었습니다.

Java에서 스레드 풀을 사용하여 이메일 전송을 빠르게 처리하려면 ExecutorService를 사용하여 병렬 처리를 구현할 수 있습니다.

private ExecutorService emailExecutorService = Executors.newFixedThreadPool(10);

그러나 이 해결 방법은 큰 문제점을 가지고 있습니다.

이메일 서비스는 자주 발생하지 않고, 사용할 일이 있더라도 특정 주기 후에는 사용하지 않는다는 것입니다.

그러나 위의 방식은, 스레드를 생성하고 해제하지 않고 있습니다.

따라서 위의 방법은 적합하지 않은 방법이라고 볼 수 있습니다.

2 - 2 다른 해결 방법

이를 해결하기 위한 방법으로는 크게 두 가지가 있습니다.

  1. CachedThreadPool
  2. On-Demand Resource Allocation

2 - 3 Cached Thread Pool

Executors.newCachedThreadPool()을 사용하면 필요할 때만 스레드를 생성하고, 일정 시간 동안 사용되지 않는 스레드는 제거됩니다. 이 방식은 빈번하지 않은 작업에 적합할 수 있습니다.

이 스레드 풀의 특징은 다음과 같습니다.

  • 동적 스레드 관리: 새로운 작업이 들어올 때, 사용 가능한 스레드가 없으면 새로운 스레드를 생성합니다. 반면, 기존 스레드가 사용 가능하면 재사용합니다.
  • 유휴 스레드 제거: 유휴 상태(즉, 작업을 수행하지 않는 상태)인 스레드는 기본적으로 60초 후에 풀에서 제거됩니다.
  • 작업량에 따른 확장성: 스레드 풀은 작업량에 따라 스레드 수를 동적으로 조정합니다. 이는 작업량이 변동적인 애플리케이션에 적합합니다.

2 - 4 On-Demand Resource Allocation

이 방식은 스레드 풀을 필요할 때만 할당하고, 작업이 완료되면 즉시 해제하는 접근 방법입니다. 이 방식의 핵심은 스레드 풀을 상시적으로 유지하는 것이 아니라, 작업이 요청될 때만 생성하고, 작업 완료 후에는 해제하는 것입니다.

3. Cached Thread Pool

연세 골프는 다양한 시나리오 속에서 이메일 전송이 간헐적으로 발생하기 때문에 Cached Thread Pool을 도입하기로 결정하였습니다.

하지만 직접 cacherThreadPool을 사용할 경우, 서버의 스펙에 맞지 않을 수 있기 때문에 직접 cached thread pool 을 구현해보도록 하겠습니다.

3 - 1 Cached Thread Pool 적용

@Service
public class EmailService {

    private final JavaMailSender mailSender;
    private final EmailRepository emailRepository;
    private ThreadPoolExecutor emailExecutor;

    @Autowired
    public EmailService(JavaMailSender mailSender, EmailRepository emailRepository) {

        this.mailSender = mailSender;
        this.emailRepository = emailRepository;
    }

    @PostConstruct
    public void init() {
        emailExecutor = new ThreadPoolExecutor(
                0,  // 코어 스레드 수
                20, // 최대 스레드 수
                60L, TimeUnit.SECONDS, // 유휴 시간
                new LinkedBlockingQueue<>()
        );
    }

    @PreDestroy
    public void destroy() {
        emailExecutor.shutdown();
    }

    public void sendApplyStartAlert() {
        List<EmailAlarm> allAlert = findAllAlert();
        List<Future<?>> futures = new ArrayList<>();

        allAlert.forEach(alert -> {
            Future<?> future = emailExecutor.submit(() -> sendEmail(alert.getEmail(),
                    "연세대학교 골프동아리입니다.",
                    NotificationType.CLUB_RECRUITMENT.generateMessage(null))
            );
            futures.add(future);
        });

        // 모든 이메일 전송 작업이 완료될 때까지 기다립니다.
        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("이메일 전송 작업 중 오류 발생", e);
            }
        }

        // 모든 이메일 전송 작업이 완료된 후, 데이터베이스에서 이메일 알림 삭제
        emailRepository.deleteAll();
    }
}

4. 네트워크 지연 시간

멀티스레드를 통해 이메일 전송을 해결하려고 하였으나, 네트워크 지연시간 때문에 시간이 해결되지 않았습니다.

즉, 각각의 스레드가 이메일을 전송하려고 하였으나 한 번에 하나의 이메일만을 보낼 수 있기 때문에 실질적인 시간의 차이는 발생하지 않았습니다.

5. 최종 해결

이 문제를 해결하기 위해 이메일 전송 방식을 변경하도록 하였습니다.

기존에는 한 번에 한명에게만 이메일을 전송하였지만, 한 번에 다수의 사람에게 이메일을 전송하도록 변경해보겠습니다.

이를 위한 방법으로는 CC(Carbon Copy), BCC(Blind Carbon Copy)가 있습니다.

CC의 경우, 수신자가 다른 수신자의 이메일을 볼 수 있기 때문에 개인정보 노출의 위험이 있어서 BCC를 통해 이메일을 전송하도록 하겠습니다.

public void sendApplyStartAlert() {
        List<EmailAlarm> allAlert = findAllAlert();

        String[] bccAddresses = allAlert.stream()
                .map(EmailAlarm::getEmail)
                .toArray(String[]::new);

        sendEmail(bccAddresses,
                "연세대학교 골프동아리입니다.",
                NotificationType.CLUB_RECRUITMENT.generateMessage(null));

        emailRepository.deleteAll();
    }

private void sendEmail(String[] bcc, String subject, String text) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setBcc(bcc);
        message.setSubject(subject);
        message.setText(text);
        sendEmailMessage(message);
    }

위와 같이 BCC를 설정해줌으로써 한 번의 여러명의 사람에게 이메일을 더욱 빨리 보낼 수 있게 되었습니다.

'Yonsei Golf' 카테고리의 다른 글

쿠폰 발급 동시성 제어  (0) 2024.01.04
Spring 처리율 제한 장치 (Rate Limiter)  (0) 2023.12.30
모니터링 with Docker  (0) 2023.12.09
JWT Token + Refresh Token  (1) 2023.11.27
CloudFront, S3 배포 자동화  (2) 2023.11.26