나: n+1 문제를 해결해서 성능을 올렸습니다!
면접관: 얼마나요?
나: 엄... 그건 잘...
면접관: 수치화 안했어요?
나: 네......
DMS(Dorimtory Management System) 프로젝트를 개발하다가
모범학생 투표 기능에서 투표에서 제외된 학생을 가져오는 로직에서 쿼리가 오래걸리는 문제가 생겼다. 알고보니 학생을 가져오는 로직에서 room 이라는 하위 엔티티를 가져올 때 N+1 문제가 발생하는 것이었다.
그래서 나는 @EntityGraph(attributePaths = {"room"}) 을 이용하여 원래는 학생을 조회하면 room이라는 하위 엔티티를 가져오는 과정에서 발생하는 N+1 문제를 해결했다.
선배: 한샘아 그래서 얼마나 좋아졌는데?
나: 많이...?
선배: 수치화 해야겠지?
나: 그게 뭐죠?
수치화를 해야 before와 after의 차이를 명확하게 알 수 있고, 내가 한 작업이 얼마나 성능을 올려주었는지 확인하는 것은 매우 중요하기 때문에 수치화를 하는 것은 개발자에게 있어서 매우 중요하다.
게틀링이란 코드(Scala/Java/Kotlin)로 작성하는 고성능 오픈소스 부하 테스트 도구이다.
public class BasicSimulation extends Simulation {
HttpProtocolBuilder httpProtocol = http
// 모든 요청의 기본이 되는 공통 주소 (Host)
.baseUrl("http://localhost:8080")
// 모든 요청에 포함될 공통 헤더
.header("Content-Type", "application/json")
// 공통 인증 토큰
.header("Authorization", "Bearer token");
// 테스트 시나리오의 이름 (보고서 식별용)
ScenarioBuilder scenario = scenario("get excluded-student")
.exec(
// 개별 HTTP 요청의 이름 (보고서에 통계로 찍힘)
http("get-excluded-student-request")
// 상세 경로 (baseUrl 뒤에 붙음)
.get("/votes/excluded-student")
// 응답 상태 코드가 200(성공)인지 검증
.check(status().is(200))
);
{
setUp(
scenario.injectOpen(
// atOnceUsers(100) // 100명의 사용자가 동시에 요청
// rampUsers(100).during(10) // 10초 동안 100명의 사용자가 순차적으로 요청
// constantUsersPerSec(10).during(30) // 초당 요청 수(TPS 10)을 유지하면서 30초동안 요청
)
).protocols(httpProtocol);
}
}
위 코드에서 setUp 부분에서 요청을 보내는 방식은 개발자가 선택하면 된다.
여기서 중요한 점은 서버와 비슷한 환경에서 테스트를 해야한다는 점이다. 자신의 컴퓨터가 양자컴퓨터 뺨치면 N+1이든 N^2+1이든 상관이 없기 때문이다.
그렇기 테스트는 Docker라는 독립적인 환경에서 테스트를 진행하였다.
deploy:
resources:
limits:
cpus: '2' # OS가 일할 최소한의 통로 확보
memory: 4G # OS 메모리 영역 침범 방지
이런식으로 docker-compose.yml 에서 docker 에서 cpu와 메모리를 얼마나 할당한지 정해줄 수 있다. 이 방법을 이용해 배포되어있는 서버와 최대한 비슷한 환경으로 테스트를 하면 더 정확할 수 있다.
이제 환경설정이 끝났다면 터미널에 아래 명령어를 실행 시켜주면 된다.
gradle
./gradlew gatlingRun
maven
mvn gatling:test

그리고 이런식으로 build 패키지 안에서 report를 확인할 수 있다.
여기서 주의할점은 이름을 따로 바꾸어 놓아야 나중에 확인이 편하다는 점이다!


| 지표 (Metric) | 기존 | 변경 후 | 성능 개선 |
|---|---|---|---|
| Mean(평균) | 1,697 ms | 812 ms | 2.09배 빨라짐 |
| Min(최소) | 410 ms | 220 ms | 1.86배 빨라짐 |
| Max(최대) | 2,217 ms | 1,008 ms | 2.20배 빨라짐 |
| 50th pct(중간값) | 1,795 ms | 865 ms | 2.08배 빨라짐 |
위를 표를 보면 기존 코드보다 대략 평균 응답 시간이 약 2배 이상 개선되었고,
최대 응답 시간 또한 감소하여 전체적인 응답 안정성도 향상된 걸 확인할 수 있다.
성능을 개선하면 어떻게 성능을 개선했는지도 중요하지만 얼마나 성능이 개선되었는지 수치화하는 것 또한 중요하다. 수치화를 통해 다른 사람들에게 내가 한 작업을 통해 얼마나 성능이 개선되었는지 알려줄 수 있기 때문이다.
워밍업 추가하면 더 정확한 결과를 얻을 수 있을 것 같아요