Notice
Recent Posts
Recent Comments
«   2025/08   »
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 29 30
31
Archives
Today
Total
관리 메뉴

개발지식 먹는 하마 님의 블로그

[내일배움캠프 76일차] 문제 풀이 링크 발송 테스트 본문

TIL

[내일배움캠프 76일차] 문제 풀이 링크 발송 테스트

devhippo 2025. 6. 11. 21:01

MVP 방식 기반 1차 개발 완료

MVP 기반으로 진행되는 프로젝트 개발 일정에 따라,
이메일 발송 기능에 대한 초기 구현을 마쳤다.

이제는 여러 부분을 테스트해 본 후, 성능 개선을 목적으로 2차 개발을 진행해야 하였다.
실제 개선에 앞서 현재 어디서 병목 현상이 발생하고,  원인은 무엇이며
어떻게 개선하면 좋을지 확인하기 위해 테스트를 진행하였다.

 

💡 잘 동작하는가?

 

문제 출제부터 이메일 발송까지 프로젝트의 일부 흐름을 테스트하기 위해
부분 통합 테스트로 테스트 코드를 작성하였다.

@TestConfiguration
public class TestMailConfig {

    @Bean
    public JavaMailSender mailSender() {

        JavaMailSender mockSender = Mockito.mock(JavaMailSender.class);
        Mockito.when(mockSender.createMimeMessage())
            .thenReturn(new MimeMessage((Session) null));
        return mockSender;
    }

    @Bean
    public MailService mailService(JavaMailSender mailSender,
        SpringTemplateEngine templateEngine,
        StringRedisTemplate redisTemplate) {
        MailService target = new MailService(mailSender, templateEngine, redisTemplate);
        return Mockito.spy(target);
    }
}

실제 메일 발송을 막기 위해 JavaMailSender를 Mock 객체로 하는 테스트용 Config를 설정하였다.
Mock 객체이기 때문에 실제로 요청이 가지 않는다.

@SpringBootTest
@Import(TestMailConfig.class) //제거하면 실제 발송, 주석 처리 시 테스트만
class DailyMailSendJobTest


TestMailConfig 클래스를 Import 또는 주석처리하여 테스트 코드를 실행해 성능 측정 및 실제 발송 여부를 테스트하였다.

 

✅ 실제 발송 여부

TestMailConfig를 잠시 주석처리하고 Job을 실행했을 때 실제로 send 요청이 실행되며,
정상적으로 이메일이 발송되는 것을 확인할 수 있다.

 


성능 테스트

🔎 어디서 병목 현상이 일어나는가

문제 풀이 링크를 발송하기까지의 과정에 대해서 각 메서드들의 실행 시간을 측정하여
어디서 병목 현상이 일어나는지 확인하고자 하였다.

[테스트 조건]

발송할 데이터 개수 : 1개
측정 시간 단위 : ms
테스트 횟수 : 3회

메서드 실행 시간(ms)
메일 발송 대상 조회 327
실제 객체 조회 103
문제 출제 316
메일 내용 구성 1160
메일 발송 3500
합계 5406

Message Queue 적용으로 Queue에 데이터를 넣고, 데이터를 꺼내는데 소요되는 시간은 평균 약 100ms가 소요되었다.

테스트 결과, 메일 발송까지의 흐름에 걸리는 시간이 건 당 약 5~6 TPS
그 중 메일 발송이 건 당 약 3~4 TPS로 가장 큰 비중을 차지하였다.

 

🔎 이메일 실제 발송 테스트

테스트 코드를 실행할 때, JavaMailSender는 Mock 객체이다.
데이터 개수에 따른 실행 시간을 측정하고자 할 때,
실제로 메일 발송 요청에 걸리는 시간은 측정할 수 없다.

따라서, 현실적인 부분을 고려하여 4개의 데이터에 대한 실제 메일 발송 시간을 측정해 보았다.
(Gmail은 하루에 500건까지만 발송 가능하고, 수신자가 받는 메일의 양이 너무 많을 경우 발송되지 않는다.)

 

메일 발송 요청에 걸리는 시간은, 데이터 개수와 상관없이 유사하다.

 

💡 원인

  • 외부 SMTP 서버로 네트워크를 통해 전송하는 과정에서의 네트워크 지연 + 서버 응답 시간
  • 응답 시, 프로토콜에 따른 교환 과정 필요

 

✅ 해결 방법

  • 비동기 처리 도입
  • JavaMailSender 대신 AWS SES와 같이 대규모 메일 발송을 지원하는 서비스 이용

 


 

🔎 데이터 개수에 따른 실행 시간 측정

 

앞선 테스트 결과를 기반으로 실제 발송 요청에 걸리는 시간은 건 당 3500ms로 가정하고
이를 제외한 나머지 과정에 대하여 데이터 개수에 따른 실행 시간을 측정하였다.

데이터 개수 실행시간 (sec)
건당 평균 시간 (sec)
1 7 7
10 39 3.9
100 358 3.58
1000 3533 3.53
10000 35242 3.52

100개의 데이터 까지는 약 6분이 걸리지만,
그 이상으로 데이터가 늘어날수록 실행 시간도 매우 길어진다.
데이터 개수가 1000인 경우 1시간이 걸리고 10000개인 경우 약 10시간이 소요된다.

비동기 처리가 필요성을 다시 한번 느끼게 하는 수치이다.

 

흥미로운 점은 데이터가 1개일 때보다 개수가 늘어날수록 건당 평균 시간이 감소한다는 것이었다.

메서드 실행 시간(ms)   실행 시간(ms)
문제 출제 316 ➡️ 5
메일 내용 구성 1160 ➡️ 22

 

 

💡 원인 1) JVM은 Warm-up이 필요해요

JVM은 클래스를 최초로 사용할 때 .class 파일을 메모리에 로드하고 바이트코드 검증, 메타데이터 등록 등을 수행한다.
이후에는 메모리 기반으로 이를 재사용한다.

또한, JIT 컴파일러는 자주 사용되는 특정 메서드를 기계어로 변환한다.

이러한 과정으로 인해 초기 데이터에서만 실행 시간이 비교적 오래 걸리고 그 후에 효과적으로 단축된 것이다.
초기 데이터의 실행 시간을 단축하기 위해서는 JVM Warm-up 과정이 필요하다.

💡 원인 2) 고마워요 JPA의 Entity Manager

문제를 출제하는 메서드에서는 DB에 저장되어 있는 문제를 전부 조회하고 그중에서 문제를 선정한다.

메서드를 호출할 때마다 전체를 조회하기 때문에 성능 개선이 필요하겠다고 짐작하고 있던 부분이었는데
실제 테스트 결과, 초기 실행에만 300ms가 소요되고
그 이후에는 평균 약 5ms라는 굉장히 짧은 시간만 소요되는 것을 확인할 수 있었다.

메서드를 호출할 때마다 DB의 데이터를 전체 조회해서 가져오는데 왜 5ms 밖에 걸리지 않을까?

바로 JPA의 EntityManager 때문이다.

JPA 기반으로 DB의 데이터를 조회하면 EntityManager가 해당 데이터를 1차 캐싱한다.
따라서, 매번 DB에서 실제 조회를 하지 않고 메모리에서 값이 반환되기 때문에
메서드의 실행 시간이 짧아진 것이다.

 


성능 개선 방향성

테스트 결과를 바탕으로 성능 개선의 방향성을 정할 수 있었다.

  • 비동기 처리 적용
  • AWS 연결

 

트레이드 오프 - Warm Up

장점 단점
초반 지연 감소 시스템 복잡도 증가
대량 트래픽 처리 전 사전 안정화 외부 시스템 부하 위험 (DDoS 유사)
  배포/구동 시간 증가

단점들을 감수하고 초반 지연 속도를 감소시키는 경우, 단축되는 시간은 약 1400ms이다.

프로젝트를 언제든지 사용할 수 있도록 여러 서버로 수평확장한다고 가정했을 때,
Warm-up의 빈도 수가 증가할 수 있다는 점을 고려하면
장점에 비해 단점이 더 크다.

따라서, JVM Warm Up을 통한 성능 개선은 적용하지 않기로 하였다. 

 

https://junuuu.tistory.com/830

 

JVM Warm-up 이란

JVM Warm-up 이란?warm-up은 흔히 워밍업으로 우리가 알고 있으며 몸풀기 준비운동이라는 뜻입니다. 그렇다면 JVM의 준비운동은 어떤것을 의미할까요? 이를 이해하기 위해서는 자바언어의 컴파일 과

junuuu.tistory.com