본문 바로가기

개발일지(일간)

Gatling을 이용한 부하 테스트

CEP의 서버 성능은 그리 좋은 편이 아니다. 그래서 요청에 대한 부하 분산을 하고자 로드밸런싱을 하였다.

하지만 로드밸런싱을 하고 난 후 요청에 대한 응답이 어느정도로 개선이 되었는지 확인을 하지 않았는데, 이를 확인해보고자 부하 테스트를 할 수 있는 gatling을 이용해보았다.

사용한 gatling코드는 다음과 같다.

더보기
package computerdatabase;

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.stream.Collectors;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

public class ProductLoadTest extends Simulation {

  HttpProtocolBuilder httpProtocol = http
      .baseUrl("")
      .acceptHeader("application/json")
      .contentTypeHeader("application/json");

  public enum ConvenienceClassification {
    CU, GS25, EMART24;
  }
  // 2. 시나리오 정의
  List<String> keywords = Arrays.asList("코카", "펩시", "아이스크림", "라면", "스프라이트","null");
  List<ConvenienceClassification> convenienceClassifications = Arrays.asList(ConvenienceClassification.values());
  List<String> eventClassifications = Arrays.asList("1+1", "2+1", "덤증정", "세일");

  // 3. 기본 페이지 접속 시나리오
  ScenarioBuilder basicPageScenario = scenario("Basic Page Access Scenario")
      .exec(
          http("Fetch Products - Basic Page")
              .post("/products?page=0&size=10")
              .body(StringBody(
                  "{\n" +
                      "  \"keyword\": null,\n" + // 기본 키워드 없음
                      "  \"convenienceClassifications\": [],\n" +
                      "  \"eventClassifications\": []\n" +
                      "}"))
              .asJson()
              .check(status().is(200)) // 응답 코드 200 확인
      );

  // 4. 특정 키워드를 사용한 검색 시나리오
  ScenarioBuilder searchScn = scenario("Product Search Load Test")
      .exec(session -> {
        Random random = new Random();

        // keyword: null 또는 랜덤 한 개 선택
        String keyword = keywords.get(random.nextInt(keywords.size()));

        // convenienceClassifications: 랜덤으로 [] 또는 한개 이상 선택
        List<String> selectedConvenience = random.nextBoolean() ?
            List.of() :
            Arrays.asList(convenienceClassifications.get(random.nextInt(convenienceClassifications.size())).name(),
                convenienceClassifications.get(random.nextInt(convenienceClassifications.size())).name());

        // eventClassifications: 랜덤으로 [] 또는 한개 이상 선택
        List<String> selectedEvents = random.nextBoolean() ?
            List.of() :
            Arrays.asList(eventClassifications.get(random.nextInt(eventClassifications.size())),
                eventClassifications.get(random.nextInt(eventClassifications.size())));

        // 세션에 저장
        return session
            .set("keyword", keyword)
            .set("convenienceClassifications", selectedConvenience)
            .set("eventClassifications", selectedEvents);
      })
      .exec(
          http("Fetch Products - search")
              .post("/products?page=0&size=10")
              .body(StringBody(session -> "{\n" +
                  "  \"keyword\": " + (session.get("keyword").equals("null") ? "null" : "\"" + session.get("keyword") + "\"") + ",\n" +
                  "  \"convenienceClassifications\": " +
                  session.getList("convenienceClassifications").stream()
                      .map(convenience -> "\"" + convenience + "\"") // 문자열로 변환
                      .toList() + ",\n" +
                  "  \"eventClassifications\": " + session.getList("eventClassifications").stream()
                  .map(event -> "\"" + event + "\"") // 문자열로 변환
                  .toList() + "\n" +
                  "}"))
              .asJson()
              .check(status().is(200)) // 응답 코드 200 확인
      );

  // 5. 부하 프로필 설정
  {
    setUp(
        basicPageScenario.injectOpen(
            atOnceUsers(300),// 즉시 300명 접속
            constantUsersPerSec(50).during(120),// 120초동안 50명씩 1초마다 주기적으로 요청
            rampUsers(1000).during(120) // 120초 동안 1000명 순차접속
        ),

// 10초 대기한 후 searchScn 실행
        searchScn.injectOpen(
            nothingFor(10),
            constantUsersPerSec(40).during(110),// 110초동안 40명씩 1초마다 주기적으로 요청
            rampUsers(1000).during(110)
        )
    ).protocols(httpProtocol);
  }
}

 

메인페이지에 접속하면 기본적으로 products를 불러오는 요청을 하기 때문에 products를 불러오도록 했다.

그리고 products만 불러오면 테스트가 좀 심심한것 같아 메인페이지 접속후, 검색하는 상황을 가정하고 랜덤한 조건으로 검색 요청을 하게 만들었다.

이와 같은 조건으로 초기 300명 동시접속후, 120초동안 기본 products 요청을 1초마다 50명씩, 그리고 1000명이 순차적으로 요청하게 하고, 그와 동시에 10초후에는 검색요청을 110초동안 1초마다 40명씩, 그와 별개로 1000명이 순차적으로 요청하게 했다.

이렇게 요청을 하게 되면 총 12700건의 요청이 120초동안 서버에 걸리게 된다.

컨테이너가 한대일때는 다음과 같은 테스트 결과를 보이게 된다.

 

1.로드밸런싱x

0.8초이내 처리가 15%, 1.2초 초과 처리가 84%로 1초당 90개정도의 동시 요청이 오게 되면 다소 처리가 늦어지는 것을 확인 할 수 있다.

 

2.로드밸런싱O

로드밸런싱을 하면, 요청을 여러 서버로 분산시키기때문에 부하가 분산되어 응답성능이 개선될 것이라고 예상하고 진행했다.

컨테이너 두대를 로드밸런싱했을때 테스트 결과는 다음과 같다.

0.8초 이내 응답률이 36프로, 1.2초 초과 응답률이 58프로다.

로드밸런싱을 하지 않았을때랑 비교하면

0.8초 이내 응답률 : 15% -> 36%

0.8초 이상 1.2초 미만 응답률 : 0% -> 4%

1.2초 초과 응답률 : 84% -> 58%

로 로드밸런싱 이전과 비교하면 1.2초 초과 응답률이 26% 감소하는 결과를 보인다. 

예상대로 로드밸런싱 이전보다 응답성능이 개선된 것을 확인할 수 있다.