[설문이용] 기업 연계 시나리오 기반 부하 테스트 진행하기

정훈희·2024년 10월 24일
4

설문이용

목록 보기
2/2
post-thumbnail

0️⃣ 배경

진행 중인 프로젝트 "설문이용"을 기업과 연계하게될 기회가 생겼다.

우리 팀은 기업 연계에 앞서 서비스가 잘 동작할 수 있는지 파악하기 위해 실제 요구사항을 바탕으로 시나리오를 작성하고, 부하 테스트를 진행해보았다.

목차

1️⃣ 시나리오 작성

2️⃣ 부하 테스트 준비

3️⃣ 부하 테스트 진행 및 결과 분석

4️⃣ 개선 결과 및 비교

1️⃣ 시나리오 작성

배경

이번에 기업 연계를 진행하게 될 대형 마트의 회원 수는 약 5천명이고, 모든 회원에게 고객 만족도 설문조사 참여 메시지를 보낼예정이다. 이러한 상황에서 필요한 요구사항을 정의해보자.

기능적 요구사항

  • 설문 참여 과정을 수행할 수 있다. (설문 조회 및 응답 제출)

비기능적 요구사항

  • 설문 참여 과정을 1분에 최대 50번 수행할 수 있다.
    • 기업이 제시한 요구사항, 기존 사용자들의 요청, 갑작스러운 변수 등을 고려하여 위와 같이 요구사항을 정의하였다.
  • 각 API 요청의 평균 응답 시간은 200ms 미만이여야 한다.
    • 단, 설문 응답 API는 외부 서비스를 호출하므로 400ms로 설정한다.

2️⃣ 부하 테스트 준비

더미 데이터 삽입

부하 테스트에 앞서 실제 운영 환경과 최대한 비슷하도록 더미 데이터를 삽입하는 API를 구현하였다. PR 링크

  • 추후 운영을 고려하여, 예상 시나리오보다 더 많은 더미데이터를 삽입하고 진행하였습니다. (매일 설문조사가 50개 생성되고 각 설문의 평균 응답자 수가 100명이라고 가정 )

외부 서비스 모킹 서버 구성

image

현재 설문 응답 시 중복 참가자 검사를 위해 Fingerprint라는 외부 서비스를 이용하고 있다.

하지만, 해당 서비스에는 요청 수 제한이 있고, 이를 초과하면 추가 비용이 발생하기 때문에 부하 테스트로 많은 호출을 할 수 없었다.

image

그래서, 위와 같이 부하 테스트를 진행 중이면 외부 네트워크에 별도로 구성한 Fingerprint Mocking 서버로 요청을 보내도록 하였다. Repository 링크

image

Fingerprint Mocking 서버는 API 요청을 받으면 Fingerprint API의 평균 응답 시간인 175ms 뒤에 요청을 반환하도록 구현하였다.

스크립트 작성 & 결과 저장 및 시각화(k6 & InfluxDB & Grafana)

다른 부하 테스트 도구에 비해 성능이 가장 좋고, 시나리오 기반의 테스트가 가능한 k6로 스크립트를 작성했다.

스크립트가 긴 관계로, 자세한 코드는 이곳에서 확인할 수 있다.

또한, 테스트 결과를 저장하고 이를 쉽게 확인하고 분석하기 위해 InfluxDB와 Grafana를 로컬 환경에 세팅하였다. Repository 링크

Grafana 대시보드는 해당 링크의 대시보드를 조금 커스텀하여 각 API의 응답과 시나리오 성공률 등을 볼 수 있도록 하였다.

3️⃣ 부하 테스트 결과 분석

테스트 결과 - 요구사항 달성 실패

테스트 결과, 설문 응답 API의 응답 시간이 요구사항(400ms)을 달성하지 못하였다.

문제점 찾기 - 왜 느릴까?

APM이 제공하는 Breakdown Table을 통해 설문 응답 API의 세부 동작과 소요 시간을 확인해보았다.

image

확인결과, MongoDB 쿼리(participants find)가 대부분의 응답 시간을 차지하는 것을 확인할 수 있었다.

각 쿼리들이 왜 느린지 분석하기 위해 MongoDB의 slow query log를 분석하였다.

{
    "attr.command.filter.surveyId.$uuid": "2b051e6a-4d92-4ad9-b34c-7e4b7de89377",
    "attr.command.find": "participants",
    "attr.cursorid": 1970698316047089400,
    "attr.docsExamined": 109091,
    "attr.durationMillis": 2728,
    "attr.nreturned": 101,
    "attr.planSummary": "COLLSCAN"
}

확인해보면, 우선 find 명령으로 surveyId가 2b051e6a-4d92-4ad9-b34c-7e4b7de89377인 참가자를 COLLSCAN 방식(컬렉션 전체를 스캔)으로 먼저 101개 찾는다.

{
    "attr.command.collection": "participants",
    "attr.command.getMore": 1970698316047089400,
    "attr.cursorExhausted": true,
    "attr.docsExamined": 110830,
    "attr.durationMillis": 2762,
    "attr.nreturned": 101,
    "attr.planSummary": "COLLSCAN"
}

그 다음, find 명령으로 생성된 커서를 통해 나머지 데이터 101개를 찾아서 반환한다.

결국 가장 큰 문제는 surveyId에 해당하는 참가자들을 찾는 과정에서 컬렉션 전체를 탐색하는 것이다.

4️⃣ 개선 및 결과 비교

개선하기 - participants의 surveyId 속성에 대해 index 생성

결국 가장 큰 문제는 참가자를 조회할 때 surveyId에 대해 index가 없어서 컬렉션 전체를 조회하는 것이었다.

그래서, participants 컬렉션의 surveyId에 대해 아래와 같이 index를 생성해주었다.

db.participants.createIndex({ surveyId: 1 })

2차 부하 테스트 진행 결과

participants 컬렉션에 surveyId에 대한 index를 생성한 결과 설문 응답 API의 응답시간이 93% 감소하여 요구사항을 달성하였다. (3.95s → 263.49ms)

5️⃣ 느낀점

이전에 서비스를 개발했을 땐 대충 "어느정도는 버티겠지?"라고 생각하고, 문제가 닥치고 나서야 급하게 해결했던 기억이 난다.

이번 기회에 서비스 사용 시나리오를 꼼꼼하게 작성하고, 이를 바탕으로 부하 테스트를 진행해보니 놓쳤던 문제를 조기에 발견할 수 있어서 좋았다.

또한, 이번 시나리오 기반 부하 테스트를 통해 기업 연계 과정에서도 문제 없이 서비스가 동작할 수 있음을 검증하여 서비스의 안정성과 신뢰성을 확인할 수 있었다.

profile
DB를 사랑하는 백엔드 개발자입니다. 열심히 공부하고 열심히 기록합니다.

0개의 댓글