본문 바로가기
카테고리 없음

[Load Test] AWS + k6를 활용한 서버 부하테스트 및 병목 해결(1)

by H_THING 2026. 1. 16.

대용량 트래픽은 어떻게 경험해볼 수 있을까?

최근 개발자 채용 공고를 살펴보면 '대용량 트래픽 처리 경험' 이라는 문구를 자주 볼 수 있다.
하지만 실제로 대규모 트래픽이 발생하는 서비스를 운영하는 회사에 소속되지 않는 이상 이러한 경험을 직접 해보는 것은 쉽지 않다.


개발자로서 트래픽이 몰리는 상황에서 서버가 어떻게 반응하는지
어디서 병목이 발생하고 어떤 지표를 기준으로 개선해야 하는지를 이론이 아닌 실제로 경험해보고 싶었다.

그래서 이번에는 부하 테스트 도구인 k6를 이용해
AWS 환경에서 인위적으로 트래픽을 발생시키고 서버의 반응을 관찰해보기로 했다.
단순히 요청을 많이 보내는 데서 끝나는 것이 아니라
처리량과 지연시간을 기준으로 서버의 한계를 확인하고자 한다.

 

들어가기 전에

1) k6란 무엇인가?

Grafana Labs에서 개발한 오픈소스 부하 테스트 도구로 스크립트 기반으로 가상 사용자를 생성해 HTTP 요청을 반복적으로 발생시킬 수 있다.

  • JavaScript 문법으로 테스트 시나리오 작성 가능
  • CLI 기반으로 간단하게 실행 가능
  • 처리량, 응답 시간, 실패율 등 핵심 지표를 직관적으로 볼수있는 페이지 제공
  • 로컬 환경뿐 아니라 클라우드 환경에서도 테스트 가능

2) 처리량(Throughput)과 지연시간(Latency)이란?

아무리 많은 요청을 처리할 수 있더라도 응답 시간이 지나치게 길다면 사용자는 '느리다' 고 느끼게 된다.

결국 대용량 트래픽 환경에서 중요한 것은 '얼마나 많이 처리할 수 있는가' '얼마나 빠르게 응답하는가'의 균형'이다.

 

- 처리량

서버가 단위 시간당 처리할 수 있는 요청의 수를 의미한다.
보통 TPS(Transaction Per Second) 또는 RPS(Request Per Second)로 표현된다.

 

- 지연시간

클라이언트가 요청을 보낸 시점부터 응답을 받을 때까지 걸리는 시간이다.

 

 

테스트 구성

 

먼저 이번 부하 테스트에서 사용한 전체 구조를 살펴본다. 위와 같이 AWS 환경에서 ELB(Application Load Balancer)를 구성하여 외부 요청이 로드 밸런서를 거쳐 프로젝트로 전달되는 구조다. 

 

1) 왜 k6 서버를 따로 분리해야 할까?

부하 테스트는 '서버가 얼마나 잘 버티는지'를 보기 위한 테스트인데 만약 테스트를 발생시키는 주체(k6)와 트래픽을 받는 서버가 같은 환경에 있다면 CPU, 메모리, 네트워크 자원을 서로 경쟁하게 된다. 이 경우 다음과 같은 문제가 발생할 수 있다.

  • 실제 병목은 애플리케이션이 아닌 k6 실행 환경에서 발생
  • 서버 성능이 아닌 테스트 도구 성능에 의해 결과가 왜곡
  • 정확한 처리량(TPS), 지연시간(Latency) 측정 불가

따라서 이번 테스트에서는 '부하를 주는 쪽'과 '부하를 받는 쪽'을 완전히 분리하여 순수하게 서버의 성능만을 관찰할 수 있도록 구성했다.

 

2) 그렇다면 로컬 PC(Mac / Windows)에서 실행하면 안 될까?

단순한 테스트라면 가능하다. 하지만 트래픽을 단계적으로 크게 증가시키는 부하 테스트에서는 문제가 된다.

Mac이나 Windows와 같은 일반 사용자 OS에는 의도치 않은 트래픽 폭증을 방지하기 위한 자체 보호 메커니즘이 존재한다.

이러한 제한으로 인해 k6에서 설정한 가상 사용자 수(VU)나 요청 수가 의도한 만큼 실제로 발생하지 않는 상황이 생길 수 있다.

 

3) ELB를 구성한 이유

(1) 실제 운영 환경과 유사한 구조를 만들기 위함
실무에서 대용량 트래픽을 처리하는 서비스는 대부분 단일 서버가 아닌 로드 밸런서를 통한 진입 구조를 가지고 있다.
ELB를 포함함으로써 단순한 성능 테스트가 아니라 '운영 환경에서의 트래픽 흐름'을 기준으로 테스트할 수 있다.

 

(2) 두 번째 이유는 병목 지점을 단계적으로 해결해보기 위함이다.
초기에는 하나의 EC2 인스턴스에만 트래픽을 집중시키고 트래픽 증가에 따라 응답 지연이나 오류가 발생하는 지점을 확인한다.

이후 같은 프로젝트를 실행하는 EC2 인스턴스를 추가로 생성하고 ELB 뒤에 붙여 병렬로 요청을 분산 처리했을 때
병목이 실제로 완화되는지를 직접 확인하고자 했다.

즉 병목이 발생했을 때 인프라 확장을 통해 얼마나 효과적으로 대응할 수 있는가까지 확인해보고자 한다

 

 

실습

1. EC2 인스턴스 생성 (API 서버용)

> EC2 생성 - 인스턴스명 입력

> OS - ubuntu

> 인스턴스 유형 - t3.small (free tier)

> 키페어 - 키페어 없이 (권장X - 이번 단순 테스트 후 삭제 예정)

> 네트워크 설정 - 인터넷에서 http 트래픽 허용 (80포트)

 

2. RDS 생성

> 데이터 베이스 생성 방식 - 표준 생성

> 엔진 옵션 - Mysql

> 템플릿 - 프리 티어

> DB 인스턴스 식별자 : 자유 입력 ex) test-db

> 암호입력

> 인스턴스 구성 - 프리티어 최소

> 연결 - 퍼블릭 엑세스 : 예

> 기존 VPC 보안 그룹 - EC2 페이지에서 보안그룹 생성 후 해당 VPC 선택

보안그룹 생성
> EC2 -네트워크 및 보안 - 보안그룹 - 보안그룹 생성
> 이름, 설명 입력 ex) db
> 인바운드 규칙 - 유형 : MYSQL/Aurora, 소스 : Anywhere-Ipv4

> 워크벤치등 접속 테스트 

호스트네임(엔드포인트), 포트

 

 

3. ELB 생성

> EC2 - 로드벨런싱 - 로드벨런서 - 로드벨런서 생성

> 로드 밸런서 유형 - Application Load Balancer(ALB)

> 기본구성 - 이름 입력

> 네트워크 매핑 - 가용영역 4개 전부 선택

> 보안 그룹 생성 및 등록 (80포트)

보안그룹 생성
> EC2 -네트워크 및 보안 - 보안그룹 - 보안그룹 생성
> 이름, 설명 입력 ex) test-alb-security-group
> 인바운드 규칙 - 유형 : HTTP, 소스 : Anywhere-Ipv4

 

> 리스너 및 라우팅 - 대상 그룹 생성 및 등록

(ELB로 들어온 요청을 어떤 EC2 인스턴스에 전달할 건지를 설정)

대상그룹 생성
> 기본구성 - 인스턴스
> 대상 그룹 이름 입력
> 상태 검사 - 상태 검사 경로 - '/health' 입력
> 다음 > 사용 가능한 인스턴스 - 서버로 사용될 API 인스턴스 선택 - 아래에 보류 중인것으로 포함 클릭

 

4. EC2에 Spring boot api 서버 생성

> JDK 설치

$ sudo apt update
$ sudo apt install openjdk-17-jdk

> git clone & api 프로젝트 가져오기 (예시를 위한 js-code server)

$ git clone https://github.com/JSCODE-COURSE/load-testing-server.git


> 생성한 RDS 정보 입력

> Spring Boot 서버 빌드 및 실행

$ cd ~/load-testing-server
$ ./gradlew clean build -x test
$ cd build/libs
$ sudo nohup java -jar jscode-0.0.1-SNAPSHOT.jar &

 

> 80번 포트에서 실행되고 있는 프로세스 확인

$ sudo lsof -i:80

 

> EC2 퍼블릭 IP 주소 접속하여 서버 구동 확인 ex) 45.4.523.413/health
> ELB 주소로도 접속해보기

 

 

6. k6 테스트용 인스턴스 생성 및 부하테스트

1. EC2 생성

> 인스턴스명 입력

> OS - ubuntu

> 인스턴스 유형 - t3.small (free tier)

> 키페어 - 키페어 없이 (권장X - 이번 단순 테스트 후 삭제 예정)

> 네트워크 설정 - 편집 - 보안 그룹 규칙 추가 - 유형 : 사용자 지정 TCP, 포트범위 : 5665, 소스유형 : 위치무관

(k6에서 테스트 결과를 모니터링하는 페이지를 제공하여 해당 페이지 접근하기 위함)

 

2. k6 설치

인스턴스 접속 후 k6 설치

$ sudo gpg -k && / sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 && / echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list && / sudo apt-get update && / sudo apt-get install k6
> 설치 후 k6 명령어 입력으로 설치 확인

 

3. k6 스크립트 작성

script.js 생성 및 작성

> 서버는 기본적으로 하나의 로직만 작동하지 않는다. 그런 상황을 간략하게 대비하여 작성과 조회가 랜덤하게 요청되는 상황을 만들어

부하 테스트가 되도록 하는 스크립트를 작성하여 테스트한다.

코드엔 option을 통해 10분에 걸쳐 50 가상 유저수가 점진적으로 증가된다는 가정하에 sleep(1) 을 넣어 1초단위로 테스트 부하가 증가 요청 되도록 한다.

 

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

export const options = {
  // 부하를 생성하는 단계(stages)를 설정
  stages: [
    // 10분에 걸쳐 vus(virtual users, 가상 유저수)가 50에 도달하도록 설정
    { duration: '10m', target: 50 }
  ],
};

export default function () {
  let random = Math.random();
  // 100명 중 5명의 비율로 게시글을 작성
  if (random < 0.05) {
    const data = { title: '제목', content: '내용' };
    http.post('http://{ELB 주소}/boards', JSON.stringify(data), {
      headers: { 'Content-Type': 'application/json' },
    });
    
  // 100명 중 95명의 비율로 게시글을 조회
  } else {
    http.get('http://{ELB 주소}/boards');
  }
  
  // 1초 휴식
  sleep(1);
}

 

4. DB 더미 데이터 생성 (/board로 조회될 데이터)

5. 부하 테스트 시작

K6_WEB_DASHBOARD=true k6 run script.js

 

6. k6에서 제공 하는 모니터링 페이지로 확인

> http://{k6가 실행되는 IP주소}: 5665 로 접속

현재 시스템의 최대 Throughput은 4 TPS

 

확인 방법 

- VUs : 초당 접근 요청수
- Http Request Rate : 초당 요청 수
- HTTP Request Duration : 요청에대한 평균 응답시간

 

1) Request Rate(요청 처리량)가 더 이상 증가하지 않는 순간

 

VU가 지속적으로 증가하는데도 Request Rate가 일정 지점에서 수평으로 고정되는 것을 확인할 수 있다.
이는 다음을 의미한다.

서버가 1초 동안 처리할 수 있는 최대 요청 수(TPS/RPS)에 도달했다는 뜻이다.

 

애플리케이션 서버든 DB든 내부 어떤 지점이든 해당 순간부터는 더 이상 처리 속도를 올릴 수 없게 된다.

즉 이 구간이 서버의 실제 최대 처리량(peak throughput) 이라고 볼 수 있다.

 

 

2) 처리되지 못한 요청이 Request Duration에 쌓이기 시작한다

 

Request Rate가 더 이상 올라가지 않지만 사용자(VU)는 계속 증가한다.
그러면 생기는 현상은 다음과 같다.

  • 처리량은 이미 한계에 도달한 상태
  • 그 이상 들어오는 요청들은 즉시 처리되지 못함
  • 처리 대기 큐(Queue)에 요청이 쌓임
  • 그 결과 요청별 Request Duration(응답 시간)이 급격히 증가

즉 그래프 상에서는 이렇게 보인다. 처리 속도는 그대로인데, 응답 시간은 가파르게 증가 

>  병목(Bottleneck)이 드러나는 순간 이다

 

 

 

 

 

 

댓글