뒤로 가기

부하 테스트

문제

Jmeter를 통해 50명의 유저가 1초마다 100번의 요청을 보내는 스트레스 테스트를 진행하였습니다.

스트레스 테스트 결과, 초반에는 처리량이 높았지만, 시간이 지나면서 처리량이 점점 감소하면서 오류가 발생한 요청이 생겼습니다.

부하 테스트 결과처리량

원인

처리량이 감소하는 이유를 찾고, 오류율 0%를 만들기 위해 다음 3가지 방법을 시도하였습니다.

  • CloudWatch 확인
  • Server Log 확인
  • 로컬과의 비교교

해결책

1. CloudWatch 확인CloudWatch

부하 테스트가 진행된 15시에는 75.4%의 메모리 사용률이 보였습니다. 하지만, 그 이후 메모리 사용률이 계속 증가하여 80%까지 증가하여 메모리 누수 발생 문제가 있을 수 있습니다.


VisualVm

메모리 누수 문제를 확인하기 위해, VisualVm을 통해 JVM의 힙 메모리 사용량을 확인하고, 부하 테스트를 다시 진행하였습니다.
부하 테스트를 다시 진행하였지만, GC가 동작하여 정상적인 메모리 사용 패턴이 보였습니다.
따라서, 메모리 누수 문제는 아니라고 판단하였습니다.


2. Server Log 확인Nginx Log

Nginx Error Log를 통해 요청이 무엇 떄문에 실패했는지 확인하였습니다. 그 결과, time out 오류가 발생하였습니다.


Server Log

그렇다면, API 응답 시간은 어느 정도 소요되었는지 확인하였습니다.
기본적으로 290ms ~ 400ms 정도의 시간이 소요되었습니다.


2025-09-20T19:24:00.873+09:00 INFO 33784 --- [Globa] [.0-8080-exec-35] o.y.g.c.filter.RequestLoggingFilter : [b14a7c56] GET /user - IP: 127.0.0.1
- Device: Desktop - Status: 200 - Duration: 326ms 2025-09-20T19:24:00.878+09:00 INFO 33784 --- 
[Globa] [.0-8080-exec-26] o.y.g.c.filter.RequestLoggingFilter : [af6af8e1] GET /user - IP: 127.0.0.1 - Device: Desktop - Status: 200 - Duration: 463ms 

Hibernate: select ue1_0.user_id, ue1_0.code, ue1_0.created_time, ue1_0.deleted_time, ue1_0.event_nofi, ue1_0.is_deleted, ue1_0.name,
ue1_0.notification_token, ue1_0.notification_token_time, ue1_0.primary_nofi, ue1_0.profile_path, ue1_0.profile_size, ue1_0.profile_type, ue1_0.share_nofi,
ue1_0.sns_id, ue1_0.sns_kind, ue1_0.upload_nofi from app_user ue1_0 where ue1_0.user_id=?
@Cacheable(
  value = "user",
  key = "#userId",
  condition = "#userId != null",
  unless = "#result == null"
)
public ResponseUserDto getUser(Long userId) {
  ...
}

또한, Log에서 캐싱되어야 할 데이터가 캐싱되지 않아 데이터베이스에서 조회하는 시간이 발생하였습니다.


private Authentication getAuthentication(String accessToken) {
  Long userId = provider.getUserIdByAccessToken(accessToken);
  UserEntity user = findUserUseCase.execute(userId);
  CustomUserDetails customUser = new CustomUserDetails(
          user.getUserId(),
          user.getName(),
          user.getNotificationToken()
          userId
  );
    
    // 프로덕션에서는 삭제 필요
  log.info("Authenticated token: {}", accessToken);

  return new UsernamePasswordAuthenticationToken(customUser, null, customUser.getAuthorities());
}

그 문제는 JWT 토큰을 통해 사용자 정보를 조회하는 과정에서 발생하였습니다.

하지만, JWT는 Stateless 토큰이기 때문에 매번 DB에서 조회하는 것은 비효율적입니다.
또한, 중요한 권한 검증인 경우 Usecase 단에서 처리하도록 하였기 때문에 불필요한 조회라고 생각되어 제거하였습니다.


3. 로컬과의 비교Jmeter Local

로컬에서 부하 테스트를 진행하였을 때는 준수한 성능을 보였습니다.


Jmeter Error

실패 케이스를 보는 도중, 도메인을 접속하는 데 time out 오류가 발생하였습니다.
이는 도메인 접속 시간이 길어지는 것을 의미하며, 이는 네트워크 문제로 인해 발생한 것으로 판단됩니다. 또한, 2번에서 확인한 바와 같이 Gateway Timeout이 발생하는 것을 확인할 수 있습니다.

이를 해결하고자 무료 DNS 서비스인 DuckDNS에서 가비아 도메인을 결제하여 도메인을 변경하였습니다. Region 간의 네트워크 경유 소요 시간이 길었기 때문에 다음 Region들을 서울로 변경하였습니다.

  • AWS EC2
  • AWS RDS
  • Redis Cloud → Elasticache 이전
  • AWS SQS

결과Jmeter ResultThroughput Result

위 3가지 방법을 제외하고, 여러 테스트를 진행하였으며, 쿼리 최적화 등을 수행했습니다.

환경Cache Hit
(Spring)
Cache Hit
(Postman)
Cache No Hit
(Spring)
Cache No Hit
(Postman)
EC2 (Sydney)199ms337ms595ms734ms
EC2 (Seoul)188ms198ms558ms567ms
DNS 변경 + EC2 (Seoul)185ms249ms558ms593ms
DNS 변경 + EC2 (Seoul) + Query 개선195ms249ms417ms441ms
DNS 변경 + Medium EC2 (Seoul) + Query 개선183ms210ms379ms393ms
DNS 변경 + Medium EC2 (Seoul) + Query 개선 + Local Redis12ms22ms14ms35ms
DNS 변경 + Medium EC2 (Seoul) + Query 개선 + ElastiCache8ms17ms18ms27ms
최종 개선율94.0% ↑93.5% ↑97.6% ↑95.2% ↑