부하테스트 시나리오 4: 채널방 – 메세지 수신 - 100-hours-a-week/2-hertz-wiki GitHub Wiki

1. 사용자 시나리오 설명

시나리오 설명: 사용자가 개인채널에서 상대방이 전송한 메세지를 수신

사용자가 개인채널에 들어와 상대방의 메세지를 수신합니다. 3초마다 메세지를 수신하는 /api/v1/channel-rooms/{channelRoomId}?page={페이지번호}&size={페이지크기} API를 호출함(Polling). 백엔드에서는 DB를 검색하여 해당 채팅방의 대화내용을 응답합니다.

2. 테스트 개요

항목 내용
테스트 목적 사용자가 상대방과 대화를 나누는 과정에서의 성능 및 부하 검증
테스트 환경 - Monolithic: e2-custom-2-6144 (vCPU 2개, 메모리 6GB)
대상 시스템 백엔드(SpringBoot + MySQL)
테스트 도구 k6
테스트 일시 2025년 5월 27일

2.1 테스트 목적

사용자가 시그널을 받은 상대방과 대화를 나누는 과정에서의 성능 및 부하를 검증하기 위함입니다.

2.2 테스트 API 및 호출 흐름

어플리케이션 API 엔드포인트 메서드 설명
SpringBoot /api/v1/channel-rooms/{channelRoomId}?page={페이지번호}&size={페이지크기} GET 채널방의 메세지 내역 반환 API (Authorization 필요)

3. 테스트 조건 및 부하 프로파일

차수 1차 2차 3차
동시 사용자 수 50명 100명 500명
총 요청 시간 30초 30초 30초
요청 간격 3초 3초 3초
부하 생성 패턴 shared-iterations (고정 개수 요청 배분) shared-iterations (고정 개수 요청 배분) shared-iterations (고정 개수 요청 배분)
배치 처리 방식 멀티스레드 활용, 동시 요청 처리 멀티스레드 활용, 동시 요청 처리 멀티스레드 활용, 동시 요청 처리

4. 테스트 결과 요약

🔹 1차 테스트 결과

K6

항목 정의
최대 VU 수 (vus_max) 50 동시에 실행된 최대 가상 사용자 수 (Virtual Users)
총 요청 수 (http_reqs) 501 전체 테스트 동안 실행된 HTTP 요청 수
평균 응답 시간 (avg) 129.8ms 전체 요청의 평균 응답 시간
최대 응답 시간 (max) 909.84ms 가장 느린 요청의 응답 시간
90% 응답 시간 (p90) 270ms 90%의 요청이 이 시간 이하로 응답됨
95% 응답 시간 (p95) 541.65ms 95%의 요청이 이 시간 이하로 응답됨
에러율 (http_req_failed) 0.00% 실패한 HTTP 요청의 비율
성공률 (checks_succeeded) 100.00% 성공 조건(응답 200, 유효 JSON)을 만족한 요청 비율
요청 완료 시간 (iteration_duration.avg) 3.13s 1회 요청당 평균 전체 실행 시간
총 테스트 시간 약 37.3초 전체 부하 테스트가 완료되기까지 걸린 시간

전체 리소스 사용량

image image 1

어플리케이션별 리소스 사용량

image 2 image 3

APM 결과(API 지연 시간, 호출 수, 에러율)

image 4
🔹 2차 테스트 결과

K6

최대 VU 수 (vus_max) 100 동시에 실행된 최대 가상 사용자 수 (Virtual Users)
총 요청 수 (http_reqs) 955 전체 테스트 동안 실행된 HTTP 요청 수
평균 응답 시간 (avg) 294.63ms 전체 요청의 평균 응답 시간
최대 응답 시간 (max) 2.26s 가장 느린 요청의 응답 시간
90% 응답 시간 (p90) 609.03ms 90%의 요청이 이 시간 이하로 응답됨
95% 응답 시간 (p95) 1.34s 95%의 요청이 이 시간 이하로 응답됨
에러율 (http_req_failed) 0.00% 실패한 HTTP 요청의 비율
성공률 (checks_succeeded) 100.00% 성공 조건(응답 200, 유효 JSON)을 만족한 요청 비율
요청 완료 시간 (iteration_duration.avg) 3.3s 1회 요청당 평균 전체 실행 시간
총 테스트 시간 약 38.2초 전체 부하 테스트가 완료되기까지 걸린 시간 (running 기준)

전체 리소스 사용량

image 5 image 6

어플리케이션별 리소스 사용량

image 7 image 8

APM 결과(API 지연 시간, 호출 수, 에러율)

image 9 image 10
🔹 3차 테스트 결과

K6

항목 정의
최대 VU 수 (vus_max) 500 동시에 실행된 최대 가상 사용자 수 (Virtual Users)
총 요청 수 (http_reqs) 1456 전체 테스트 동안 실행된 HTTP 요청 수
평균 응답 시간 (avg) 6.86s 전체 요청의 평균 응답 시간
최대 응답 시간 (max) 20.76s 가장 느린 요청의 응답 시간
90% 응답 시간 (p90) 10.48s 90%의 요청이 이 시간 이하로 응답됨
95% 응답 시간 (p95) 11.9s 95%의 요청이 이 시간 이하로 응답됨
에러율 (http_req_failed) 0.00% 실패한 HTTP 요청의 비율
성공률 (checks_succeeded) 100.00% 성공 조건(응답 200, 유효 JSON)을 만족한 요청 비율
요청 완료 시간 (iteration_duration.avg) 10.04s 1회 요청당 평균 전체 실행 시간
총 테스트 시간 약 1분 5.2초 전체 부하 테스트가 완료되기까지 걸린 시간 (running 기준)

❗리소스 부족으로 서버가 다운됨

전체 리소스 사용량

image 11 image 12

어플리케이션별 리소스 사용량

image 13 image 14

APM 결과(API 지연 시간, 호출 수, 에러율)

image 14

5. 결과 분석

응답시간 분석

  • 동시 요청 100건에서는 평균 1.4초, 최대 4.71초의 응답 시간이 측정됨. 이는 다소 높은 수준으로, 대기 시간이 사용자에게 인지될 수 있음.
  • 동시 요청 500건에서는 서버가 일시적으로 다운되는 현상이 발생하였고, 평균 응답 시간은 7.4초, 최대 응답 시간은 22.52초로 서비스 품질에 심각한 영향을 미칠 수 있는 수준임.
  • 반면 10명 동시 요청 환경에서는 1000건의 요청이 있었음에도 평균 24.93ms, 최대 107.31ms로 매우 빠르고 안정적인 응답을 보임.
  • 또한 APM Trace 결과에 따르면, DB 쿼리 처리 속도는 빠르지만, 어플리케이션 처리 속도에 지연이 있음을 알 수 있음. 이는 서버의 스레드가 부족하거나 JVM의 잦은 GC 수행으로 인한 문제일 가능성이 있으며, 사용자 경험에 심각한 영향을 줄 수 있는 지점임.

리소스 사용률 분석

  • 100명 동시 요청 시, CPU 사용률이 90%에 육박하고, 메모리 역시 4GB 전체를 소진하는 등 자원 사용률이 매우 높음.
  • 500명 동시 요청 시, CPU와 메모리 사용량이 급증하면서 시스템이 응답 불가 상태에 도달함.
    • 이 때 평균 응답 시간은 6.86초, 최대 20.76초, p90 10.48초, p95 11.9초로 사용자 경험에 부정적인 영향을 미치는 수준임.
  • 반면 10명 동시 요청 시에는 CPU 사용률이 일시적으로 증가하더라도 전반적으로 안정적인 사용률과 빠른 응답 시간을 유지함.
  • 모든 테스트에서 HTTP 요청 실패율은 0%, 기능적 오류 없이 정상 응답이 반환되었음.

종합 판단

  • 현재 응답 지연은 주로 요청 수 증가에 따른 서버 자원 부족에서 발생함.
  • 특히, Trace에서 확인된 어플리케이션 처리 지연은 리소스 부족으로 인한, 메모리 부족, 스레드 부족, 잦은 GC 수행 등의 문제를 일으켰을 가능성이 높음.

6. 개선 방안 제안

  • 현재 서버 자원으로는 100건 이상의 동시 요청을 안정적으로 처리하기 어려우며, 인프라 확장 또는 구조적 최적화가 필요하다.
  • 현재 도커 컨테이너 환경으로의 마이그레이션을 통해 nextjs, springboot, mysql을 각 각의 인스턴스로 분리 예정이므로, 마이그레이션 전에는 사용자 수 100명에 맞춰 인스턴스 리소스를 CPU 2코어, RAM 8GB까지 늘리는 것을 제안한다.

7. 부록

  • 테스트 스크립트: /home/devops/stress-testing/get-message-test.js

테스트 스크립트

import http from 'k6/http';
import { sleep, check } from 'k6';

// 테스트 구성 옵션
export const options = {
  vus: 500,          // 50명의 가상 사용자
  duration: '30s',  // 30초간 테스트
};

// API 구성 설정
const API_BASE_URL = '<https://dev.hertz-tuning.com>'; // 실제 API 도메인으로 변경 필요
const API_ENDPOINT = '/api/v1/channel-rooms';
const CHANNEL_ROOM_ID = 3;
const PAGE_SIZE = 20;
const ACCESS_TOKEN = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI3IiwiaWF0IjoxNzQ4MzI5ODUxLCJleHAiOjE3NDgzNDA2NTF9.tdUIMlG3blrZq6c8lrg74W9skqFk_h7QmKUhQ2JcduM';// 사전 테스트 실행 - 시작 전 API 가 정상 작동하는지 확인
export function setup() {
  // API URL 구성
  const url = `${API_BASE_URL}${API_ENDPOINT}/${CHANNEL_ROOM_ID}?page=0&size=${PAGE_SIZE}`;
  
  // 헤더 설정
  const params = {
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
    },
  };
  
  console.log('사전 테스트: API 연결 확인 중...');
  console.log(`요청 URL: ${url}`);
  
  // API 호출
  const res = http.get(url, params);
  
  console.log(`사전 테스트 응답 상태: ${res.status}`);
  console.log('응답 본문:');
  console.log(res.body);
  
  // 응답 검사
  const success = res.status === 200;
  
  // 테스트 실패 시
  if (!success) {
    console.error('사전 테스트 실패. API 연결을 확인하세요.');
    console.error(`응답 상태: ${res.status}`);
    console.error(`응답 본문: ${res.body}`);
    throw new Error('사전 테스트 실패로 테스트를 중단합니다.');
  }
  
  console.log('사전 테스트 성공. 5초 후 테스트를 시작합니다...');
  sleep(5);
  
  return { accessToken: ACCESS_TOKEN };
}

// 메인 테스트 함수
export default function (data) {
  // API URL 구성 (무작위 페이지)
  const page = Math.floor(Math.random() * 1); // 0-2 페이지
  const url = `${API_BASE_URL}${API_ENDPOINT}/${CHANNEL_ROOM_ID}?page=${page}&size=${PAGE_SIZE}`;
  
  // 헤더 설정
  const params = {
    headers: {
      'Authorization': `Bearer ${data.accessToken}`,
      'Content-Type': 'application/json',
    },
  };
  
  // API 호출
  const res = http.get(url, params);
  
  // 응답 검증 (선택적)
  check(res, {
    'API 응답이 성공적임 (200)': (r) => r.status === 200,
    '응답이 유효한 JSON 형식임': (r) => {
      try {
        JSON.parse(r.body);
        return true;
      } catch (e) {
        return false;
      }
    },
  });
  
  // 3초 대기 (풀링 주기)
  sleep(3);
}
⚠️ **GitHub.com Fallback** ⚠️