V1 부하테스트 - 100-hours-a-week/20-real-wiki GitHub Wiki

V1 API 부하테스트

1. 테스트 개요

항목 내용
테스트 목적 API 시나리오 기반의 성능 및 정합성 검증
테스트 대상 시스템 Spring Boot + MySQL 기반 백엔드 API
테스트 도구 Apache JMeter 5.6.3
환경 조건 부하테스트용 서버 (t3.medium): dev 인스턴스 복제
인증 방식 테스트용 토큰 (User 수: 1000명)

1.1. 테스트 준비

  1. Python Script를 통해 실제 부하테스트 서버에 회원가입 진행 (단일성)
  2. Python Script를 통해 실제 부하테스트 서버에 1에서 회원가입한 user를 찾아 실제 로그인 진행 → accessToken 추출
  3. 추출한 accessTokencsv 파일로 준비
  4. JMeter에 추출한 csv 파일을 불러와 토큰 헤더 설정

1.1.1. Python Script

import requests
import csv

BASE_URL = "http://{부하테스트 인스턴스 IP}:8080"
NUM_USERS = 1000
PASSWORD = "securePassword123!"

tokens = []

# 회원가입 (이미 가입되어 있다면 예외 무시)
for i in range(1, NUM_USERS + 1):
    email = f"user{i}@example.com"
    nickname = f"user{i}"
    try:
        requests.post(f"http://{부하테스트 인스턴스 IP}/auth/signup", json={
            "email": email,
            "nickname": nickname,
            "password": PASSWORD
        })
    except Exception:
        pass  # 이미 존재하는 유저일 경우 무시
        
# 로그인 및 토큰 저장
for i in range(1, NUM_USERS + 1):
    email = f"user{i}@example.com"
    resp = requests.post(f"http://{부하테스트 인스턴스 IP}:8080/login", json={
        "email": email,
        "password": PASSWORD
    })
    print(i + "번: " + resp)
    if resp.status_code == 200:
        token = resp.json().get("data", {}).get("accessToken", "")
        if token:
            tokens.append([token])
    else:
        print(f"Login failed for user{i}")

# tokens.csv 저장
with open("tokens.csv", "w", newline='') as f:
    writer = csv.writer(f)
    writer.writerow(["token"])  # CSV 헤더
    writer.writerows(tokens)

from google.colab import files
files.download("tokens.csv")

1.1.2. JMeter 설정

API Core Test Plan (Test Plan)

→ Test Plan 내부에서 쓰일 기본적인 변수 정의

  • ${BASE_URL} = load.kakaotech.com: 부하테스트용 인스턴스의 도메인 이름
  • ${BASE_PATH} = /api/v1: 부하테스트에서 사용할 공통 Path
  • ${fixedNoticeId} = 110: 부하테스트에서 사용할 공지 Id (in 무결성 테스트)
  • ${fixedNewsId} = 6: 부하테스트에서 사용할 뉴스 Id (in 무결성 테스트)

CSV Data Set Config

→ Python Script를 통해 생성한 tokens.csv를 가져와 각 스레드에 토큰을 주입하는 Config Element

HTTP Header Manager

→ API 요청을 보낼 때, HTTP Header에 위에서 가져온 token을 넣기 위한 Config Element

HTTP Request Defaults

→ API 요청을 보낼 때, 공통된 프로토콜과 URL을 설정하는 Config Element

2. 테스트 시나리오

시나리오 ID 시나리오 이름 설명
S-1 공지 열람 공지 목록·상세·댓글 읽기
S-2 뉴스 열람 뉴스 목록·상세·댓글 읽기
S-3 공지 상호작용 공지 목록→상세→댓글 작성→좋아요 토글
S-4 뉴스 상호작용 뉴스 목록→상세→댓글 작성→좋아요 토글
S-5 챗봇 Q&A 질문 생성 → AI 답변 수신 시간·정합성 확인
S-6 댓글 CRUD 무결성 댓글 작성 → 삭제 → 사라졌는지 확인
S-7 공지 조회수 무결성 동일 공지에 50VU 동시 GET → 조회수 무결성 검사
S-8 뉴스 조회수 무결성 동일 뉴스에 50VU 동시 GET → 조회수 무결성 검사
S-9 공지 동시 좋아요 경쟁 동일 공지에 50VU 동시 PUT → 카운트 무결성 검사
S-10 뉴스 동시 좋아요 경쟁 동일 뉴스에 50VU 동시 PUT → 카운트 무결성 검사

2.1. 성능 테스트

2.1.1. S-1 (공지 열람)

순서 Method Endpoint
1 GET /api/v1/notices
2 GET /api/v1/notices/${noticeId}
3 GET /api/v1/notices/${noticeId}/comments

2.1.2. S-2 (뉴스 열람)

순서 Method Endpoint
1 GET /api/v1/news
2 GET /api/v1/news/${newsId}
3 GET /api/v1/news/${newsId}/comments

2.1.3. S-3 (공지 상호작용)

순서 Method Endpoint Response Body
1 GET /api/v1/notices x
2 GET /api/v1/notices/${noticesId} x
3 POST /api/v1/notices/${noticesId}/comments { “content” : “성능 테스트 댓글 ${__RandomString(5,abcdef)}”
4 PUT /api/v1/notices/${noticesId}/likes x
5 GET /api/v1/notices/${noticesId} x

2.1.4. S-4 (뉴스 상호작용)

순서 Method Endpoint Response Body
1 GET /api/v1/news x
2 GET /api/v1/news/${newsId} x
3 POST /api/v1/news/${newsId}/comments { “content” : “성능 테스트 댓글 ${__RandomString(5,abcdef)}”
4 PUT /api/v1/news/${newsId}/likes x
5 GET /api/v1/news/${newsId} x

2.1.5. S-5 (챗봇 Q&A)

순서 Method Endpoint Response Body
1 POST /api/v1/chatbots {"question":"멘토링 데이 날짜를 알려줘."}

2.2. 무결성 검증 테스트

무결성 검증 테스트는 다음과 같은 세 Thread Group으로 진행됩니다.

  1. Integrity SetUp (setUp Thread Group): 무결성 테스트 시작 전, 데이터가 안전하게 가져와지고 저장되는지 판단하기 위해 초기 값을 불러오기 위한 Thread Group
  2. Integrity Test (Test Thread Group): 실제 여러 사용자가 동시에 같은 요청을 보냈을 상황을 진행하는 Thread Group
  3. Integrity TearDown (tearDown Thread Group): 여러 사용자가 모든 요청을 끝내고, 데이터가 무결한 지 파악하기 위한 테스트 이후 값을 불러오기 위한 Thread Group

2.2.1. S-6 (댓글 CRUD 무결성)

Integrity Test (Test Thread group)

순서 Method Endpoint Response Body 추가 설명
1 POST /api/v1/notices/${noticeId}/comments { "content": "댓글 CRUD 테스트 from ${__threadNum}"} 공지 댓글 작성
2 GET /api/v1/notices/${noticeId}/comments x 1번에서 작성한 commentId 가져오기
3 DELETE /api/v1/notices/${noticeId}/comments/${commentId} x 2번에서 가져온 commentId 삭제
5 POST /api/v1/news/${newsId}/comments { "content": "댓글 CRUD 테스트 from ${__threadNum}"} 공지 댓글 작성
6 GET /api/v1/news/${newsId}/comments x 5번에서 작성한 commentId 가져오기
7 DELETE /api/v1/news/${newsId}/comments/${commentId} x 6번에서 가져온 commentId 삭제

2.2.2. S-7, S-8 (공지/뉴스 조회수 무결성)

Integrity SetUp (setUp Thread group)

순서 Method Endpoint Response Body 추가 설명
1 GET /api/v1/notices/${noticeId} x 공지 조회 수/좋아요 수 초기 데이터 가져오기
2 GET /api/v1/news/${newsId} x 뉴스 조회 수/좋아요 수 초기 데이터 가져오기

Integrity Test (Test Thread group)

순서 Method Endpoint Response Body 추가 설명
1 GET /api/v1/notices/${noticeId} x 공지 조회수 무결성 테스트
2 GET /api/v1/news/${newsId} x 뉴스 조회수 무결성 테스트

Integrity TearDown (tearDown Thread Group)

순서 Method Endpoint Response Body 추가 설명
1 GET /api/v1/notices/${noticeId} x 테스트 이후 공지 조회 수/ 좋아요 수 가져오기
2 GET /api/v1/news/${newsId} x 테스트 이후 뉴스 조회 수/ 좋아요 수 가져오기

2.2.4. S-9, S-10 (공지/뉴스 동시 좋아요 경쟁)

Integrity SetUp (setUp Thread group)

순서 Method Endpoint Response Body 추가 설명
1 GET /api/v1/notices/${noticeId} x 공지 조회 수/좋아요 수 초기 데이터 가져오기
2 GET /api/v1/news/${newsId} x 뉴스 조회 수/좋아요 수 초기 데이터 가져오기

Integrity Test (Test Thread group)

순서 Method Endpoint Response Body 추가 설명
1 PUT /api/v1/notices/${noticeId}/likes x 공지 좋아요 무결성 테스트
2 PUT /api/v1/news/${newsId}/likes x 뉴스 좋아요 무결성 테스트

Integrity TearDown (tearDown Thread Group)

순서 Method Endpoint Response Body 추가 설명
1 GET /api/v1/notices/${noticeId} x 테스트 이후 공지 조회 수/ 좋아요 수 가져오기
2 GET /api/v1/news/${newsId} x 테스트 이후 뉴스 조회 수/ 좋아요 수 가져오기

3. 테스트 종류 및 구성

3.1. Smoke Test (기능 검증)

목적: 배포 직후에 시스템의 핵심 기능이 정상적으로 동작하는지, 빠르게 확인하기 위한 테스트

항목 구성 내용 구성 이유
Threads 1명 단일 사용자로 기능 정상 여부만 확인
Loop 1회 기능 흐름 1회만 수행하여 최소 동작 검증
Ramp-Up 0초 즉시 실행하여 빠른 피드백 확보
기능 흐름 구성 사용자가 사용하는 핵심 시나리오 흐름 위주로 구성 실제 사용자가 수행하는 핵심 기능의 정상 동작 여부 확인
Assertion 응답 코드 200 최소한의 성공 조건 확인 및 기능 실패 조기 감지
Listener View Results Tree 즉시 결과 확인 + 오류 시 원인 파악 가능

3.2. Baseline Test (기본 부하 테스트)

목적: 시스템이 부하 없이 정상 상태에서 제공할 수 있는 기준 성능(SLA 기준선)을 파악하기 위한 테스트 사용자가 소수로 행동할 때의 평균 응답 속도, 오류율, 시스템 처리 능력을 측정

항목 구성 내용 구성 이유
Threads 10명 소수 사용자 기준 성능 측정
Loop 5회 요청 반복으로 안정된 평균 확보
Ramp-Up 2초 사용자들이 2초 뒤에 행동하는 것을 가정
Think Time 1~3초 (Uniform Random Timer) 실 사용자 행동 간 시간 반영
SLA Response Code 200
오류율 ≤ 1%
평균 응답 시간 ≤ 700ms 성공 여부와 서비스 사용자 이용 경험성 증대
Listener Summary Report
Aggregate Reports Tree 실시간 SLA 확인 및 지표 수집

3.3. Load Test (실제 운영 테스트)

목적: 실사용자와 유사한 트래픽 환경에서 시스템의 확장성, 응답 시간 변화, 오류율 증가 여부 등을 파악 평균 응답 속도뿐 아니라 TPS, 에러율, Percentile 응답 시간(P95, P99) 등을 통해 SLA 충족 여부를 측정

항목 구성 내용 구성 이유
Threads 40명 실제 운영 사용자 수 시뮬레이션
Loop Forever (with duration) 일정 시간 동안 반복 유지
Ramp-Up 60초 갑작스런 트래픽 유입이 아닌 점진적 유입 시뮬레이션
Think Time 1~3초 (Uniform Random Timer) 사용자의 입력·대기 시간 반영 (RPS 과장 방지)
Assertion Response Code 200
오류율 ≤ 2%
평균 응답 시간 ≤ 1200ms SLA 기준 만족 여부 확인
Listener Summary Report
Aggregate Reports Tree Throughput, Percentile 분석

3.4. Spike Test (과부하 테스트)

목적: 서버가 감당하기 힘든 트래픽을 보냈을 때, 실제 어느 API 호출에서 부하가 발생하고 에러가 발생하는지 확인하기 위한 테스트

항목 구성 내용 구성 이유
Threads 초기 300명 → 400명 추가 → 최종 700명 순간 사용자 증가에 따른 서버의 최대 처리 용량 및 안정성 확인
Loop 1회 사용자 수 자체가 많아 반복 없이도 충분한 부하 발생
Ramp-Up 초기 300명은 즉시이후 400명은 20초 간격, ramp-up 1초 급격한 사용자 유입 시 발생 가능한 부하와 스파이크 상황 재현
Think Time 없음 (지연 없음) 최대 부하를 걸기 위한 순수 트래픽 집중 환경 구성
SLA Response Code 200
오류율 ≤ 5%
평균 응답 시간 ≤ 2000ms 정상 응답 비율, 사용자 체감 속도 등을 기준으로 서버 임계점 판단
Hold Duration 100초 최대 부하 상태에서의 지속 처리 능력 확인
Stop Configuration 300명씩 3초 간격으로 종료 부하 완화 시점의 서버 안정성 회복 여부 측정
Listener Summary Report
Aggregate Report
View Results Tree 실시간 SLA 만족 여부 확인 및 상세 에러 응답 분석을 위한 지표 확보

3.5. 댓글 Integrity Test (댓글 무결성 테스트)

목적: 댓글 기능에 대해 동시에 여러 사용자가 CRUD 작업을 수행해도, 데이터 무결성이 보장되는지 검증 동시성 환경에서 발생할 수 있는 Race Condition, ID 중복, 삭제 누락 등을 직접 확인

항목 구성 내용 구성 이유
Threads 50명 동시 작성·수정·삭제 시 정합성 검증
Loop 1회 각 스레드는 고유한 댓글 흐름을 1회만 수행
Ramp-Up 1초 각 스레드가 동시에 API 요청 시작
요청 흐름 POST → DELETE 사용자 단위 CRUD 흐름을 순차적으로 구성
검증 방식 JSR223 Sampler: 삭제된 ID가 다시 응답에 포함되는지 확인 단순 200 응답이 아닌 실제 삭제 여부 확인
데이터 처리 댓글 내용에 __threadNum 포함 각 Thread가 자기 댓글만 추적 가능하도록 유일성 확보

3.6. 공지/뉴스 좋아요 Integrity Test (좋아요 무결성 테스트)

목적: 좋아요 기능에 대해 동시에 여러 사용자가 PUT 요청을 수행해도, 데이터 무결성이 보장되는지 검증 동시성 환경에서 발생할 수 있는 좋아요 누락, 요청 무시 등을 확인

항목 구성 내용 구성 이유
Threads 50명 동시 작성·수정·삭제 시 정합성 검증
Loop 1회 각 스레드는 고유한 좋아요 요청을 1회만 수행
Ramp-Up 1초 각 스레드가 동시에 API 요청 시작
요청 흐름 단일 PUT 요청 한 스레드가 하나의 PUT을 요청
검증 방식 JSR223 Sampler: 검증 테스트 이전 좋아요 수와 검증 테스트 이후 좋아요 수가 같은지 확인 단순 200 응답이 아닌 50개의 쓰레드가 요청을 보냈을 때, 좋아요 수가 변함이 없어야 함 (하나의 유저를 가정)

3.7. 공지/뉴스 조회수 Integrity Test (조회수 무결성 테스트)

목적: 동시에 여러 사용자가 공지/뉴스 상세 정보를 조회해도, 조회수 데이터의 무결성이 보장되는지 검증

항목 구성 내용 구성 이유
Threads 50명 실제 사용자의 동시 조회 시나리오를 시뮬레이션
Loop 1회 각 사용자가 한 번만 조회하여 정확한 증가량 추적 가능
Ramp-Up 1초 각 스레드가 동시에 API 요청 시작
초기값 저장 setUp Thread Group에서 viewCount 추출 및 props에 저장 테스트 시작 전 기준값 확보
최종값 추출 및 검증 TearDown Thread Group에서 viewCount 재조회 → JSR223로 증가량 비교 (final - initial == 50) 조회 수의 실제 증가량이 기대값과 일치하는지 정합성 검증
Assertion 응답 코드 200 + viewCount 추출 가능 여부 확인 API 자체 정상 동작 여부 확인
Listener View Results Tree 요청 성공/실패 수 및 응답 시간, 무결성 실패 시 추적 가능

4. 테스트 진행 결과

4.1. Smoke Test (기능 검증)

API Endpoint Avg. Resp (ms) Max Resp (ms) Error Rate
Notice → Detail → Comments 197 197 0.000%
News → Detail → Comments 65 65 0.000%
Notice → Detail → Comments → Write Comment → Like 174 174 0.000%
News → Detail → Comments → Write Comment → Like 159 159 0.000%
GET /notices 80 135 0.000%
GET /notices/124 32 34 0.000%
GET /notices/124/comments 26 30 0.000%
POST /notices/124/comments 41 41 0.000%
PUT /notices/124/likes 51 51 0.000%
GET /news 25 32 0.000%
GET /news/6 28 31 0.000%
PUT /news/6/likes 31 31 0.000%
POST /chatbots 9,688 9,688 0.000%
Chatbot (대화 흐름 전체) 9,688 9,688 0.000%
TOTAL (전체 평균) 934 9.688 0.000%

검증 성공 조건: 응답 상태코드 200, 오류율 = 0%

모든 핵심 API 요청에 대해 오류가 없었고(Error Rate = 0%), 챗봇을 제외한 다른 단일 API의 평균 응답 시간도 100ms 이내로 우수한 편이며, 기능적으로 정상 작동함을 확인할 수 있음

그러나 아직까지 챗봇에 대한 성능에 대한 개선은 필요

4.2. Baseline Test (기본 부하 테스트)

API Endpoint Avg. Resp (ms) Max Resp (ms) Error Rate Throughput (req/s)
Notice → Detail → Comments 6,220 8,175 0.000% 0.258
News → Detail → Comments 6,086 8,824 0.000% 0.256
Notice → Detail → Comments → Write Comment → Like 10,938 13,428 0.000% 0.250
News → Detail → Comments → Write Comment → Like 10,512 12,871 0.000% 0.248
GET /notices 81 602 0.000% 0.496
GET /notices/124 99 634 0.000% 0.496
GET /notices/124/comments 81 829 0.000% 0.704
POST /notices/124/comments 61 193 0.000% 0.264
PUT /notices/124/likes 72 470 0.000% 0.262
GET /news 69 664 0.000% 0.487
GET /news/6 96 850 0.000% 0.483
PUT /news/6/likes 80 519 0.000% 0.263
POST /chatbots 9,684 11,693 0.000% 0.251
Chatbot (대화 흐름 전체) 9,684 11,693 0.000% 0.248
TOTAL (전체 평균) 2,474 13,428 0.000% 4.719

검증 성공 조건: 응답코드 200, 평균 응답 시간 ≤ 700ms, 오류율 ≤ 1%

챗봇을 제외한 단일 API 평균 응답속도는 70~100ms 사이로 매우 안정적. 그러나 복합적인 시나리오에서 평균적으로 응답 시간이 6초 ~ 10초 이상 걸려, UX에 악영향 가능성 존재

  • 병목 구간 후보:
    • 댓글 등록, 좋아요 처리, DB I/O

그럼에도 모든 API에서 오류율을 0%로 안정적으로 전 요청 정상 처리를 하는 것을 확인

4.3. Load Test (실제 운영 부하 테스트)

API Endpoint Avg. Resp (ms) Max Resp (ms) Error Rate Throughput (req/s)
Notice → Detail → Comments 6,334 9,358 0.000% 0.85244
News → Detail → Comments 6,377 10,481 0.000% 0.85800
Notice → Detail → Comments → Write Comment → Like 10,566 14,611 0.000% 0.85284
News → Detail → Comments → Write Comment → Like 10,647 15,344 0.000% 0.85404
GET /notices 115 1,420 0.000% 1.70932
GET /notices/124 119 1,833 0.000% 1.70928
GET /notices/124/comments 105 1,686 0.000% 2.54514
POST /notices/124/comments 118 1,695 0.171% 0.86395
PUT /notices/124/likes 107 1,237 0.000% 0.86366
GET /news 100 1,708 0.000% 1.70545
GET /news/6 116 1,840 0.000% 1.70836
PUT /news/6/likes 127 1,532 0.000% 0.86012
POST /chatbots 9,897 15,409 1.751% 0.86102
Chatbot (대화 흐름 전체) 9,880 15,409 1.748% 0.85933
TOTAL (전체 평균) 2,503 15,409 0.163% 18.45090

검증 성공 조건: 응답코드 200, 평균 응답 시간 ≤ 1200ms, 오류율 ≤ 2%

대부분의 요청이 오류율 0%로 성공적으로 처리. 그러나, POST /chatbots 요청에서 오류율 1.75% 수준으로 발생. 이는 추가적으로 로그를 확인해서 결론을 내야 함

단일 시나리오가 아닌 복합 시나리오에서 전체적으로 10초 이상 소요되는 요청이 다수이며, 이는 사용자 이탈을 유발할 수 있는 성능으로 판단

  • Throughput 분석:
    • 평균 처리량은 0.85 ~ 0.86 req/s 수준으로 부하 자체는 낮지 않음
    • 이는 백엔드 응답이 지연되고 있음에도 일정 수준 이상의 요청을 지속적으로 수용 중이라는 것을 의미
    • 동시에, 대기 요청 큐가 쌓이고 있다는 신호

개선 필요 영역

우선순위 항목 조치
🔴 1 복합 시나리오 응답 시간 과다 (10~15초) 각 요청 단위로 분해하여 지연 API 식별, APM으로 트레이싱
🟠 2 Chatbot 관련 오류 발생 API 내부 로직/에러 응답 확인, 재현 시나리오 확보
🟡 3 전반적인 요청 처리량 낮음 DB 커넥션 풀, 스레드 수 조정 등 서버 설정 점검
🟢 4 단일 API 응답은 안정적 (별도 분석 필요) 단건 요청은 거의 문제 없음. 복합 시나리오 중심으로 집중 분석 필요

4.4. Spike Test (과부하 테스트)

API Endpoint Avg. Resp (ms) Max Resp (ms) Error Rate Throughput (req/s)
Notice → Detail → Comments 12,433 20,966 0.116% 13.66228
News → Detail → Comments 10,821 19,239 13.526% 11.85451
Notice → Detail → Comments → Write Comment → Like 19,820 30,742 0.0% 8.96101
News → Detail → Comments → Write Comment → Like 17,076 29,933 4.568% 10.9529
GET /notices 3,848 65,977 9.973% 17.24361
GET /notices/124 4,001 9,428 0.06% 20.20798
GET /notices/124/comments 3,948 10,563 1.326% 26.00428
POST /notices/124/comments 4,122 8,176 0.0% 9.8031
PUT /notices/124/likes 3,765 8,411 0.0% 9.74349
GET /news 3,473 8,952 0.238% 17.83628
GET /news/6 3,292 9,641 4.657% 17.75173
PUT /news/6/likes 3,335 9,728 3.57% 11.44132
POST /chatbots
Chatbot (대화 흐름 전체)
TOTAL (전체 평균) 4,929 6,5977 4.085% 188.54823

검증 성공 조건: 응답 코드 200, 오류율 ≤5%, 평균 응답 시간 ≤ 2000ms

Chatbot에 대한 API 호출은 SLA이 미충족될 것임은 자명하기에 테스트에서 제외. 평균 응답 시간 SLA는 전 항목이 미통과되었음. 오류율 SLA를 통과한 API가 존재하긴 하나, 대부분의 API 호출에서 오류율 SLA를 미통과. 결국 모든 시나리오가 SLA 미달 상태라고 판단할 수 있음.

이는 현재 시스템이 1000명 수준의 급격한 사용자 유입을 감당하기에는 성능상 한계가 있음을 의미함

세부 개선 필요 사항

  1. GET /notices
    • 병목 지점: /notices 응답에서 가장 높은 오류율(9.97%)과 응답 지연(3.8s 이상) 발생
    • 해결책:
      • Redis 캐싱: 동일 요청이 반복되는 경우, DB 접근 없이 응답 제공
      • 서버 사이드 pagination 캐싱 적용 등
  2. Spring Boot 성능 최적화
    • 전체 응답 시간이 기본적으로 수 초 이상 → 비정상적인 지연
    • 해결책:
      • 비동기 처리
      • 쿼리 최적화
      • 서버 내부 로깅 레벨 조정
      • ThreadPool 설정 조정
  3. DB 병목 최소화
    • 댓글/좋아요 관련 API도 점차 느려짐 → 누적 쿼리 부하 가능성
    • 해결책:
      • RDS 리소스로의 전환
      • Read Replica 구성 → 읽기 트래픽 분산
      • Index 최적화 및 slow query log 분석
  4. 부하 분산: Auto Scaling 적용
  5. API Gateway 또는 Rate Limiting 도입
    • 스파이크 상황에서 특정 사용자나 봇 트래픽이 시스템을 마비시킬 수 있음
    • 해결책:
      • API Gateway + Throttling 정책 설정
      • Nginx rate limiting 또는 Spring Interceptor 도입
      • 정상 사용자 우선순위 처리 정책 마련
  6. 모니터링 + Alert 시스템 구축
    • 실시간으로 병목이나 응답 실패 탐지 불가
    • 해결책:
      • GloudWath, Grafana Prometheus, Scouter, SigNoz 등 모니터링 툴 도입
      • 스파이크 상황 시 SLA 기준 초과 여부 자동 감지 및 알림

4.4. 댓글 Integrity Test (댓글 무결성 테스트)

댓글 검증 Sampler 코드

String postedId = vars.get("postedNoticeCommentId");
String deletedId = vars.get("deletedNoticeCommentId");

if (!postedId.equals(deletedId)) {
    throw new Exception("댓글 ID 정합성 실패: 작성한 ID(" + postedId + ") ≠ 삭제한 ID(" + deletedId + ")");
} else {
    log.info("댓글 ID 정합성 통과: " + postedId);
}
String postedId = vars.get("postedNewsCommentId");
String deletedId = vars.get("deletedNewsCommentId");

if (!postedId.equals(deletedId)) {
thrownew Exception("댓글 ID 정합성 실패: 작성한 ID(" + postedId + ") ≠ 삭제한 ID(" + deletedId + ")");
}else {
    log.info("댓글 ID 정합성 통과: " + postedId);
}

검증 성공 조건: postedId == deletedId

먼저 POST 요청을 할 때, postedNoticeCommentIdpostedNewsCommentId를 저장해놓고, 그 다음 DELETE 요청을 할 때, deletedNoticeCommentIddeletedNewsCommentId를 저장해, 그 둘의 값을 비교하는 검증 방식을 택함.

테스트를 진행한 결과, 모든 API 요청에서 두 변수의 값이 같은 것을 확인할 수 있었음.

4.5. 공지/뉴스 좋아요 Integrity Test (좋아요 무결성 테스트)

V2로 테스트 이전

4.6. 공지/뉴스 조회수 Integrity Test (조회수 무결성 테스트)

조회 수 검증 Sampler 코드

int initialNotice = Integer.parseInt(props.get("initialNoticeViewCount"));
int finalNotice = Integer.parseInt(props.get("finalNoticeViewCount"));
int expectedIncrease = Integer.parseInt(props.get("expectedIncrease")) + 1;

// 조회 무결성 검증
if (finalNotice - initialNotice != expectedIncrease) {
    log.error("Assertion failed: expectedIncrease " + expectedIncrease + ", but got " + (finalNotice - initialNotice));
    throw new Exception("Notice View count mismatch: expected increase " + expectedIncrease + ", actual " + (finalNotice - initialNotice));
}
int initialNews = Integer.parseInt(props.get("initialNewsViewCount"));
int finalNews = Integer.parseInt(props.get("finalNewsViewCount"));
int expectedIncrease = Integer.parseInt(props.get("expectedIncrease")) + 1;

if (finalNews - initialNews != expectedIncrease) {
    log.error("Assertion failed: expectedIncrease " + expectedIncrease + ", but got " + (finalNews - initialNews));
thrownew Exception("News View count mismatch: expected increase " + expectedIncrease + ", actual " + (finalNews - initialNews));
}

검증 성공 조건: finalNotice - initialNotice = 51 , finalNews - initialNews = 51

한번에 요청을 보내는 유저의 수는 50명이므로, expectedIncrease = 50 또한, finalViewCount를 가져올 때, GET 요청을 한 번 더 보내므로 성공 기준에 대한 수를 51으로 설정.

테스트를 진행한 결과, 모든 API 요청에서 정확히 조회 수가 51 증가한 것을 확인할 수 있었음.

5. 지표 분석

부하가 걸리는 지점을 파악하기 위해서 Smoke Test와 Baseline Test는 생략했다.

5.1. Load Test (실제 운영 테스트)

5.1.1. TPS 분석

  • TPS(Transactions Per Second)가 대부분 1819 수준에서 유지되다가, **중간 구간(약 13:4313:44)에 일시적으로 17 아래로 하락**한 뒤 다시 회복됨.
  • 이 하락은 순간적 병목 또는 응답 지연으로 인해 TPS가 떨어진 것으로 보이며, 서버 전체 처리량이 제한된 순간으로 판단됨.

✅ 결론: 대부분 안정적이지만 짧은 병목 구간 존재 → 그 시간대의 Heap/GC를 함께 봐야 함.

5.1.2. Perm Used (PermGen 영역)

  • Perm 영역(120MB)이 테스트 전반에 걸쳐 완전히 평탄함
  • 이는 클래스로딩, static 객체 증가 등의 이상 현상이 전혀 없음을 의미

✅ 결론: 안정적. 클래스 누수, 메타 정보 증가 없음

5.1.3. CPU 사용률

  • 전체적으로 20~35% 수준에서 안정적으로 유지
  • 중간에 피크가 있어도 45%를 넘지 않음
  • 따라서 CPU 병목은 없으며, CPU는 여유가 있는 상태

✅ 결론: CPU 리소스는 충분하고, 병목 원인은 CPU가 아님

5.1.4. Heap Used (GC 사이클)

  • Heap 사용량이 주기적으로 70 → 170MB로 증가했다가 GC로 감소하는 패턴 반복
  • 이는 정상적인 Minor GC 사이클로 해석됨
  • 단, GC 사이클이 상당히 자주 일어남 (10초 미만 주기)

⚠️ 결론: Memory Pressure는 없지만, GC가 너무 자주 발생하면 Throughput 저하 가능성 있음. 또한, GC tuning (Heap size 조정, G1GC 고려) 필요 가능성 있음

5.1.5. GC Count 그래프

  • GC 횟수는 0~3회 사이에서 계속 반복
  • 이는 Heap 그래프와 연동되며, 거의 10초 이하 주기로 GC 발생
  • Full GC는 발생하지 않음

⚠️ 결론: Minor GC만 반복되지만, 빈도는 다소 많음. TPS 하락 구간(13:43~44)에 GC 스파이크가 연관돼 있을 가능성 있음

5.1.6. XLog 분석 (응답시간이 늦은 개별 트랜잭션 상세)

  • 대상 요청: GET /news/${newsId}/comments
  • 특징:
    • SQL이 10회 이상 연속 실행됨
    • SELECT u0_.id, ... FROM user ... 패턴이 반복 → N+1 쿼리 강력 의심
    • 특정 쿼리 하나가 127ms → 나머지는 대부분 수 ms
  • PREPARE, RESULT, FETCH 흐름이 길게 유지됨

❗ 결론: 명확한 N+1 쿼리 현상 존재. 댓글을 불러오면서 관련된 유저 정보를 매 댓글마다 개별 쿼리로 불러오는 구조 → 해당 API는 쿼리 튜닝, fetch join, batch join 최적화 필요

5.2. Spike Test (과부하 테스트)

5.2.1. TPS 분석

  • TPS가 급격히 증가하여 최고 약 120 TPS까지 상승
  • 이후 14:17:15~14:17:30 사이에 급락 → 테스트 종료
  • 평균적으로 100 TPS 이상 처리 지속은 서버 처리 성능 기준치로 볼 수 있음

🔍 결론: 서버가 300스레드 부하를 걸었을 때 약 100~120 TPS 수준으로 응답을 유지했다는 점은 매우 좋은 결과. 급락은 부하 종료 시점으로 판단되며 병목이 아님.

5.2.2. Perm Used (PermGen 영역)

Load Test와 동일

5.2.3. CPU 사용률

  • CPU 사용률이 100% 근접하여 약 2분 이상 고정됨
  • 부하 종료 후 즉시 0~5%로 하락 → CPU 자체는 정상 회복 가능함

⚠️ 해석: CPU는 부하 처리 중 완전히 포화 상태였음 → 추가 요청이 있었다면 병목 가능성 높음. 또한, 고사양 작업(GC, SQL, 다중 연산) 등 CPU 연산비용이 높은 작업이 집중됐을 가능성 큼

5.2.4. Heap Used (GC 사이클)

Load Test와 동일

5.2.5. GC Count 그래프

  • 평균 2~4회 수준으로 매우 빈번한 Minor GC 발생
  • GC도 CPU와 마찬가지로 부하 종료 후 0으로 안정화

⚠️ 해석: GC 횟수가 과도함 → 처리량에는 큰 영향 없지만 지속되면 Throughput 저하Stop-the-world delay 위험 → Heap 크기 재조정 또는 G1GC와 같은 GC 전략 고려 필요

5.2.6. Active Service (실시간 처리 중 요청 수)

  • 최고 약 200개의 동시 요청 처리 상태
  • 일정 시간 동안 완전히 flat하게 유지됨 → 서버가 200개까지 동시 처리 가능했음을 의미
  • 부하 종료와 함께 정상 감소

✅ 해석: 서버가 동시에 200개 요청을 버티는 성능 확보됨. 이는 Thread Pool 또는 Servlet container의 처리 능력 기준선 수립에 도움 됨

5.2.7. Memory (System 전체 메모리 사용률)

  • 50% 내외로 완전히 평탄한 메모리 사용
  • Spike Test에도 메모리 부족이나 누수 없음

✅ 해석: 시스템 메모리는 여유 있음. 병목 없음. 오히려 GC가 너무 자주 발생하는 쪽이 이슈.

5.2.6. XLog 분석 (응답시간이 늦은 개별 트랜잭션 상세)

XLog 1: GET /api/v1/notices

  • 총 응답시간: 8.7초
  • 동일 SELECT 반복: user_id from user_notice_read ... 형태 20회 이상
  • N+1 쿼리 명백함
  • 유저별 읽음 여부를 개별 쿼리로 조회

✅ 개선: JOIN FETCH 또는 EXISTS, IN 절로 변경

XLog 2: GET /api/v1/news

  • 총 응답시간: 7.4초
  • 단일 SELECT, FETCH만 1회 실행
  • 하지만 응답 자체는 지연됨
  • 가능성:
    • DB 커넥션 풀 대기
    • GC or Thread Queue 대기 (병렬 상황에서 지연)

✅ 개선: 이 트랜잭션 단독 이슈는 없으나, 전체 시스템 자원 점검 필요

XLog 3: GET /api/v1/notices/{id}

  • 총 응답시간: 6.8초
  • DB write 쿼리 1회 (UPDATE view_count)
  • 이어지는 SELECT, INSERT, FETCH 반복 → 여러 테이블 조인 및 서브 쿼리

✅ 개선: view_count는 Redis와 같은 In-Memory로 처리 후 비동기 업데이트 고려

XLog 4: GET /api/v1/news/{id}

  • 총 응답시간: 7.0초
  • update + 4개 SELECT → 그 중 한 SELECT는 6,357ms
  • 해당 쿼리: select * from news_like where user_id = ? and news_id = ?

추정: news_like에 index 누락 또는 서브쿼리 복잡도 높음

✅ 개선: news_like에 (user_id, news_id) 복합 인덱스 확인 필요

XLog 5: GET /api/v1/notices/{id}/comments

  • 총 응답시간: 6.8초
  • 20회 이상 반복 SELECT (유저 정보 추정)
  • N+1 + 각각 개별 180ms 정도 → 총합 지연 유발

✅ 개선: 댓글-유저 JOIN 최적화 + DTO projection

XLog 6: GET /api/v1/news/{id}/comments

  • 총 응답시간: 6.4초
  • 20회 반복 SELECT (user 정보 또는 like 여부 추정)
  • 단일 쿼리는 1~3ms → 로직 반복 구조 문제

✅ 개선: JOIN FETCH, MapStruct DTO, @BatchSize 등 적용

6. 결론 및 개선 방향

본 테스트를 통해 Smoke / Baseline / Load / Spike / Integrity Test 전 영역에 대해 체계적인 검증을 수행하였고, 아래와 같은 핵심 인사이트를 도출할 수 있었습니다.

6.1. 주요 진단 결과 요약

구분 주요 관찰 사항
전반적인 시스템 안정성 단건 API는 대부분 응답시간 100ms 이내로 빠르고, 오류율도 0%로 매우 안정적
복합 시나리오 성능 평균 응답 시간 6~10초 이상으로 사용자 체감 성능 저하 발생
부하 대응 한계 Spike Test에서 TPS 100 이상까지 처리 가능했으나, CPU 100% 포화 상태 도달
GC 사이클 지나치게 자주 발생하며, Throughput 저하 가능성 존재
데이터 정합성 댓글/좋아요/조회수 Integrity Test 모두 무결성 통과 → 동시성 처리 문제 없음
병목 구간 News/Notice의 댓글 조회, 좋아요 여부 확인 등에서 N+1 쿼리 및 반복 SELECT 구조 확인됨
오류 발생 지점 POST /chatbots 등 일부 요청에서 SLA 초과 및 오류율 1.7% 이상 발생

6.2. 개선 방향 (기술별 제안)

6.2.1. 쿼리 최적화 및 N+1 해소

  • 문제: 대부분의 성능 병목은 단일 SELECT가 아닌 반복 SELECT → N+1 쿼리
  • 조치 방안:
    • JOIN FETCH, @BatchSize, EntityGraph, DTO Projection 사용
    • QueryDSL 또는 Native Query로 비즈니스 쿼리 단순화
    • IN (...) 서브쿼리 활용하여 한 번의 쿼리로 처리

6.2.2. 캐싱 전략 적용

  • 대상 API:
    • GET /notices, GET /news, GET /comments → 조회 반복 많고 실시간성 필요 낮음
  • 조치 방안:
    • Spring Cache, Redis, 또는 CDN 레벨에서 캐시 적용
    • @Cacheable, @CacheEvict, TTL 설계
    • 공지/뉴스 ID 기준 캐시 키 구성

6.2.3. 조회수, 좋아요 수 처리 방식 개선

  • 현재: 실시간 DB UPDATE → 잠재적인 DB Lock 및 지연 유발
  • 개선안:
    • Redis INCR, SET로 일시 저장 후 주기적 배치 저장
    • Kafka/Queue 기반 비동기 처리
    • 조회수/좋아요는 Eventually Consistent 구조로 충분함

6.2.4. GC / 메모리 튜닝

  • 현상: Heap GC가 10초 이하 주기로 지속 발생 → Minor GC 과다
  • 조치 방안:
    • JVM GC 설정 변경 (G1GC 또는 ZGC)
    • Heap Size 조정: Xms, Xmx 크기 조율
    • 객체 할당 패턴 점검: 불필요한 객체 생성 제거

6.2.5. 서버 설정 및 ThreadPool 튜닝

  • Tomcat 스레드 수, Connection Pool 설정 점검
  • maxThreads, acceptCount, maxConnections 확대
  • HikariCP 설정 점검: maximumPoolSize, minimumIdle, connectionTimeout

6.2.6. 시스템 보호: Rate Limiting, Circuit Breaker

  • Spring Cloud Gateway or Nginx Rate Limit 적용
  • 특정 API 요청 과도시 임계치 제어
  • Netflix Hystrix / Resilience4j로 실패 격리 전략 도입