지금까지 시나리오 1~3은 업로드, 조회, 공간쿼리의 개별 테스트였다면, 이번엔은 전반적인 실제 사용 패턴을 테스트 진행해보겠습니다.
테스트 시나리오 4 - 혼합 시나리오 테스트
목적 : 실제 사용자 행동 패턴 시뮬레이션
참고
- math.random()으로 0~1사이 숫자를 뽑아서 0.7미만이면 조회, 0.7 < num < 0.9면 기타 기능, 나머지는 업로드기능을 테스트 합니다.
- 70%는 조회
- 20%는 글 좋아요같은 기타 기능
- 10%는 업로드
- 시스템 한계 확인 및 스파이크 테스트를 위해 300VUser까지 점진적으로 부하를 올립니다.
테스트 결과
"metrics": {
"total_requests": 62537,
"requests_per_second": "104.11",
"avg_response_time_ms": 298,
"p95_response_time_ms": 1488,
"read_success_rate": "100.00%",
"write_success_rate": "100.00%",
"upload_success_rate": "100.00%",
"concurrent_likes": 7297
},총 62537 요청에 테스트 시간 10분 => 평균 RPS는 104 정도가 나옵니다.
max VUser수가 300까지 올라가버린 이유도 있지만, 시스템 메트릭을 확인해보면 cpu 사용량이 95%, DB Connection Usage 또한 100%로 병목이 발생한 것을 확인할 수 있었습니다.
원인 파악
혼합 테스트에서 호출하는 엔드포인트들은 아래와 같습니다.
- 시나리오 1에서 사용된 업로드 API
- 시나리오 2에서 사용된 조회 API (공간 쿼리 포함되어 있음)
- 좋아요 같은 API
여기서 조회나 기타 API는 평균 응답시간이 목표한 성능보다 우수하지만, 업로드 로직은 평균 응답시간이 시나리오 1에서 1491ms가 걸립니다.
실제 업로드 로직에서 진행되는 과정은 아래와 같습니다.
- MIME 타입 감지
- JPG 변환
- 썸네일 생성
- 파일 3개 저장 (원본, 웹용 JPG, 썸네일)
- S3 외부 저장소에 저장됩니다. 이 과정도 동기라서 작업들이 순차적으로 로딩됩니다.
특히 S3에 저장하고 응답받는 것을 AmazonS3.putObject()로 각각의 파일을 저장했는데, 이는 원본 업로드 하고, 완료할 때 까지 대기, 웹용 JPG와 썸네일도 마찬가지였습니다.
해결 과정
1. Redis를 이용한 캐싱
캐싱을 활용하여 자주 사용하는 데이터를 매번 DB에서 찾는 횟수를 줄여 시스템 성능 향상을 할 수 있습니다.
Redis는 독립적인 메모리 저장소입니다.
Redis 특징
- 메모리 기반 → 빠름 (ms 단위)
- Key-Value 저장 → 간단한 구조
- TTL 지원 → 자동으로 오래된 데이터 삭제
- 독립 서버 → 여러 애플리케이션이 공유 가능
Spring은 AOP를 사용해 캐싱을 자동화 합니다. @Cacheable 어노테이션을 이용하여 적용할 수 있습니다.
cache TTL은 우선 임의로 30분 설정하였습니다.
Nearby API를 이용한 Guide 조회 서비스에 캐싱을 적용하였습니다.
GuideService.java:228
@Cacheable(
value = "nearbyGuides",
key = "T(Math).round(#lat * 100) / 100.0 + ':' + T(Math).round(#lng * 100) / 100.0 + ':' +
#radius",
unless = "#result.isEmpty()"
)
public List<GuideResponseDto> findGuidesNear(double lat, double lng, double radius) {
// 생략
} 캐싱 적용 후 공간 쿼리 테스트 (시나리오 3) 진행
해당 테스트 시나리오에서 캐싱을 적용한 API를 사용하기도 하고, 무엇보다 서울, 부산 같은 고정 위치를 기반으로 쿼리하기떄문에 캐시 히트율이 높아 캐싱 효과를 명확하게 확인할 수 있는 테스트입니다. 여기서 먼저 변화를 확인해보겠습니다.
우선 기존 캐싱 적용x 결과입니다
{
"metrics": {
"avg_duration_ms": 10,
"p95_duration_ms": 27,
"success_rate": 1,
"slow_queries_count": 0,
"slow_queries_percentage": "0.00%",
"requests_per_second": "46.27"
},캐싱 적용 후 (테스트를 미리 돌려 캐시에 데이터를 넣은 hot cache기준입니다)
"metrics": {
"avg_duration_ms": 6,
"p95_duration_ms": 19,
"success_rate": 1,
"slow_queries_count": 0,
"slow_queries_percentage": "0.00%",
"requests_per_second": "46.72"
},평균 응답 시간이 10ms -> 6ms 로 1.6배 개선 되었습니다.
P95 응답 시간 또한 27ms -> 19로 1.42배 개선된것을 확인할 수 있습니다.

쿼리가 전부 DB에서 조회해오던 상황과 대비해서, 캐시가 히트하여 Redis에서 응답하게 되어 전체적인 DB Query Run Time이 감소한 것을 확인할 수 있습니다.
2. 비동기 처리
이렇게 오래 걸리는 외부 API 동작을 병렬처리를 하거나 비동기로 만드는것이 필요합니다.
비동기로 하려면
- WebFlux로 스레드가 대기하지 않게 하기
- 메시지 큐를 활용하기
- Presigned URL (클라이언트가 S3에 직접 업로드)
저는 현재 상황에서 가장 빠르게 적용할 수 있는 @Async를 사용하였습니다.
추후 해당 로직을 서버에서 분리하거나, 클라이언트는 이미지 데이터를 직접 업로드 하도록 하고, 필요한 MetaData만 서버에 가져오도록 하는 방식을 적용하면 더욱 최적화 할 수 있다고 생각합니다.
우선 원본파일만 먼저 동기로 처리를 한 후 응답을 해주고, 나머지 웹용 JPG와 썸네일은 비동기로 처리하도록 변경하였습니다.
원본파일을 먼저 동기로 처리하게 된 이유로는, 클라이언트가 이미지만 올리면 로직이 끝이 아닌, 이미지가 들어있는 게시글인 Guide가 Media ID를 받아서 연결되기 때문입니다.
참고
- 현재 비동기 처리중 실패하거나 서버가 다운되면, 해당 작업을 복구할 수 있는 로직이 갖추어져 있지 않습니다. 우선 성능 확인을 위한 기능만을 구현하였습니다.
S3FileStorageService.java -> 원본만 동기로 업로드
public UploadFileDto uploadOriginalOnly(MultipartFile file) throws IOException {
// 원본 파일만 S3에 업로드 (동기)
amazonS3.putObject(bucketName, originalKey,
new ByteArrayInputStream(fileBytes), metadata);
return UploadFileDto.builder()
.originalKey(originalKey)
.webDir(null) // 비동기로 처리할꺼라 우선 빈 값 반환
.thumbnailDir(null)
.build();
}
AsyncFileProcessingService.java -> 웹용 JPG & 썸네일 비동기 업로드
@Async("fileProcessingExecutor")
public void generateDerivativesAsync(Long mediaId, String baseFileName, byte[] originalBytes) {
// 웹용 JPG 생성
Thumbnails.of(new ByteArrayInputStream(originalBytes))
.scale(1.0).outputFormat("jpg").toOutputStream(webOutputStream);
// 썸네일 생성
Thumbnails.of(new ByteArrayInputStream(webBytes))
.size(1080, 1080).outputQuality(0.7).toOutputStream(thumbOutputStream);
// S3 업로드
amazonS3.putObject(bucketName, webKey, new ByteArrayInputStream(webBytes), webMeta);
amazonS3.putObject(bucketName, thumbnailKey, new ByteArrayInputStream(thumbBytes),
thumbMeta);
}
비동기 적용 후 업로드 API인 시나리오 1 테스트와 비교
기존 동기식 업로드 테스트 결과는 아래와 같습니다.
- 평균 업로드 시간 : 1,491ms
- P95 업로드 시간 : 2,022ms비동기 적용 후 테스트 결과
{
"metrics": {
"upload_duration_avg": 449.8200290275762,
"upload_duration_p95": 574.8,
"upload_success_rate": 1,
"http_req_failed": 0,
"http_req_duration_p95": 574.3093999999999
}평균 업로드 시간이 1491ms -> 450ms 로 3.31배 개선 되었습니다.
P95 업로드 시간 또한 2022ms -> 574.8로 3.52배 개선된것을 확인할 수 있습니다.
비동기 및 캐싱 적용 후 시나리오 4 테스트 결과
"metrics": {
"total_requests": 76928,
"requests_per_second": "127.98",
"avg_response_time_ms": 64,
"p95_response_time_ms": 428,
"read_success_rate": "100.00%",
"write_success_rate": "100.00%",
"upload_success_rate": "100.00%",
"concurrent_likes": 8985
},

매트릭을 확인해보면 Vuser가 200이 넘어가면서부터 DB 커넥션이 꽉 차서 대기가 생기는 사실을 확인할 수 있습니다.
cpu 사용율 또한 스파이크 테스트 (300VUser) 정도까지 가면 80%이상 사용하며 꽤 위험한 수치까지 가는것을 확인할 수 있습니다.
추후 4코어 4스레드 cpu를 지닌 서버 컴퓨터로 옮겨 운영하게 되면 스트레스 상황에서는 서버 병목 현상이 커질 수 있을 것 같다는 우려가 있습니다.
그래도 전반적인 응답 속도 및 처리량은 목표한 성능까지 달성했습니다.
일반적 운영 사항을 가정한 테스트
위와 같은 배포 환경에서 성능 이슈 우려가 있어, 우선 일반적인 부하환경을 가정하고 성능을 확인해보겠습니다.
- VUser이 150까지 점진적으로 상승합니다
"metrics": {
"total_requests": 40298,
"requests_per_second": "67.04",
"avg_response_time_ms": 45,
"p95_response_time_ms": 412,
"read_success_rate": "100.00%",
"write_success_rate": "100.00%",
"upload_success_rate": "100.00%",
"concurrent_likes": 4615
},위에서 알 수 있는 응답 시간이나, cpu 사용량 같은 메트릭은 대부분 동일했지만
DB Connection Pool은 고부하 환경과 다르게 여유가 있는것을 확인할 수 있었습니다.
그렇다면 또 다른 병목은 다른곳에 있다는 사실을 알 수 있습니다. 여전히 높은 cpu 사용량을 통해 이미지 처리 작업이 cpu에 부하가 크다는 사실을 짐작할 수 있습니다. 서비스 안정성을 위해서는 이미지 처리를 별도의 서버로 분리할 수 있을 것입니다.
결론
혼합 시나리오 (실제 사용자 행동 패턴 시뮬레이션)을 아무것도 건들이지 않은 초반 테스트를 하지않아 전체적인 성능 개선 수치는 확인하기 힘들지만,
시나리오 1에서 테스트한 업로드 API의 평균 업로드 시간이 1491ms -> 450ms 로 3.31배 개선 하였으며,
시나리오 3 (공간 쿼리, 조회 API)의 평균 응답 시간은 1393ms -> 152ms -> 10ms -> 6ms로 232.2배 개선하였습니다.
(인덱스 적용하여 full table scane 해결 -> 과도한 로그 해결 -> 캐싱(콜드캐시) -> 캐싱(핫캐시))
병목은
- 업로드 동기 처리 -> 비동기로
- 캐싱으로 조회 성능 개선
- N + 1및 로직 오류 해결
이렇게 목표한 성능까지는 개선했으나, 사진 처리 및 업로드 서비스 분리와 같은 추가적인 개선사항들이 여전히 남아 있습니다.
이는 추후 서비스를 운영하며 병목에 가까워 질 조짐이 보일 때 개선할 예정입니다.
추가적으로 PageSpeed Insight에서 페이지 성능 테스트를 해보려 했으나 현재 배포 서버의 기능들은 로그인 해야 사용할 수 있고, 서비스 데이터들도 갖춰져 있지 않기 때문에, 추후 인증 로직 정리하고 데이터가 쌓였을 때 추가하도록 하겠습니다!
'Dev > Testing & Monitoring' 카테고리의 다른 글
| 성능 테스트 (4) - 시나리오 3 (0) | 2026.02.05 |
|---|---|
| 성능 테스트 (3) - 시나리오 2 (0) | 2026.02.04 |
| 성능 테스트 (2) - 시나리오 1 (0) | 2026.02.03 |
| 성능 테스트 (1) - 목표 설정 (0) | 2026.02.02 |