← 목록으로

진짜 유저처럼! API 성능 테스트, 현실 시나리오 설계 핵심 가이드

2026. 9. 18.

진짜 유저처럼! API 성능 테스트, 현실 시나리오 설계 핵심 가이드

새로운 기능을 배포하거나, 애플리케이션을 론칭하기 전, 혹은 단순히 특정 부하를 감당할 수 있는지 확인하기 위해 '성능 테스트'라는 용어를 한 번쯤은 들어보셨을 겁니다. 우리 서비스가 예상되는 트래픽 속에서 어떻게 작동하고, 압력을 얼마나 견딜 수 있는지 파악하는 데 성능 테스트는 필수적이죠.

하지만 제대로 설계되지 않은 테스트는 오히려 잘못된 결론으로 이끌어 혼란만 가중시킬 수 있습니다. 오해의 소지가 있는 결과는 시스템 병목 지점을 가리키기는커녕, 엉뚱한 곳에 시간과 리소스를 낭비하게 만들기도 합니다. 반대로, 잘 설계된 성능 테스트는 애플리케이션의 숨겨진 병목 현상을 찾아내고, 한계를 명확히 인지하며, 출시 전 자신감을 불어넣는 든든한 아군이 되어줍니다.

이번 글에서는 먼저 성능 테스트의 기본 개념과 다양한 종류를 살펴보겠습니다. 이어서 실용적인 부분으로 넘어가, 실제 같은 테스트 시나리오를 어떻게 설계해야 하는지, 애플리케이션이 감당해야 할 부하를 어떻게 정의하는지, 외부 서비스는 어떻게 처리해야 하는지, 그리고 테스트 환경은 얼마나 프로덕션과 유사해야 하는지에 대해 깊이 있게 다뤄볼 예정입니다. 단순히 많은 요청을 쏟아붓는 것을 넘어, 실제 세상에서 우리 앱이 어떻게 작동할지 유용한 정보를 제공하는 성능 테스트를 만드는 것이 목표입니다.

목차

자, 그럼 성능 테스트의 기본 정의와 다양한 유형부터 시작해볼까요?

기본 정의

**성능 테스트(Performance Testing)**는 특정 워크로드 하에서 애플리케이션의 속도, 안정성, 확장성 및 응답성을 평가하는 비기능 소프트웨어 테스트 방법이라고 요약할 수 있습니다.

좀 더 쉽게 설명하기 위해 API를 예로 들어보죠. 우리가 테스트하고자 하는 엔드포인트를, 실제 애플리케이션이 사용되는 방식과 유사한 특정 순서에 따라 부하를 주며 호출하는 테스트를 준비하는 것입니다. 성능 테스트의 핵심은 예상되는 부하에 대비되지 않은 애플리케이션을 출시하는 것을 막는 데 있습니다.

성능이 좋지 않으면 프로덕션 환경에서의 서비스 장애, 느린 응답 시간, 또는 애플리케이션이 정해진 시간 내에 작업을 처리하지 못하는 상황이 발생할 수 있습니다. 이 모든 문제는 결국 고객 이탈, 매출 감소, 그리고 제품 평판 손상으로 이어지게 됩니다. 저도 예전에 성능 테스트를 제대로 거치지 않고 배포했다가 특정 시간대에 느려지는 서비스 때문에 고객 불만이 폭주했던 아찔한 경험이 있습니다. 그 이후로는 항상 성능 테스트의 중요성을 뼈저리게 느끼고 있죠.

성능 테스트는 설정, 목적, 그리고 우리가 관찰하고자 하는 측정 지표에 따라 여러 유형으로 나눌 수 있습니다.

테스트 종류

Types of Tests

그림이 많은 것을 설명해주지만, 각 유형을 간략히 설명해보겠습니다.

**부하 테스트(Load tests)**는 예상되거나 일반적인 수준의 트래픽에서 애플리케이션이 어떻게 작동하는지 확인합니다. 시스템이 허용 가능한 응답 시간, 오류율, 리소스 사용량을 유지하면서 필요한 사용자 수 또는 요청을 처리할 수 있는지 확인하는 것이 주된 목표입니다.

**스트레스 테스트(Stress tests)**는 애플리케이션을 예상 범위를 넘어선 한계까지 밀어붙여, 언제부터 성능이 저하되거나 실패하는지 찾아냅니다. 이는 시스템의 최대 용량을 파악하고 CPU, 메모리, 데이터베이스 연결 또는 스레드와 같은 리소스가 소진될 때 어떻게 동작하는지 관찰하는 데 도움이 됩니다.

**내구성 테스트(Endurance/Soak tests)**는 애플리케이션을 장시간 동안 지속적인 부하 상태에서 실행합니다. 짧은 테스트에서는 나타나지 않을 수 있는 메모리 누수, 연결 누수, 리소스 고갈 또는 시간 경과에 따른 성능 저하와 같은 문제를 발견하는 것이 목적입니다.

**스파이크 테스트(Spike/Peak tests)**는 트래픽이 갑자기 급격하게 증가하는 상황을 시뮬레이션합니다. 애플리케이션이 급격한 부하 변화에 어떻게 반응하고, 트래픽이 정상으로 돌아왔을 때 복구할 수 있는지 확인하는 데 유용합니다.

**볼륨 테스트(Volume tests)**는 애플리케이션이 많은 양의 데이터를 처리하거나 작업해야 할 때 어떻게 작동하는지에 초점을 맞춥니다. 예를 들어, 데이터 볼륨이 평소보다 훨씬 클 때 데이터베이스 쿼리, 가져오기(import), 내보내기(export) 또는 배치 작업이 어떻게 작동하는지 테스트할 수 있습니다.

**확장성 테스트(Scalability tests)**는 워크로드를 늘리고 리소스를 추가할 때 애플리케이션의 성능이 어떻게 변하는지 확인합니다. 시스템이 효율적으로 확장될 수 있는지, 예를 들어 애플리케이션 인스턴스, CPU, 메모리 또는 데이터베이스 용량을 더 추가함으로써 효율적으로 확장될 수 있는지 파악하는 것이 목표입니다.

이 모든 유형에 대해 완전히 다른 테스트를 구현할 필요는 없습니다. 많은 경우, 동일한 성능 테스트 시나리오를 재사용하면서 동시 사용자 수, 지속 시간, 요청 비율 또는 워크로드 패턴과 같은 구성을 변경하여 측정하고자 하는 바에 따라 다르게 관찰할 수 있습니다.

또한, 모든 애플리케이션에 모든 유형의 성능 테스트가 필요한 것도 아닙니다. 어떤 애플리케이션은 주로 부하 및 스트레스 테스트가 필요할 수 있고, 다른 애플리케이션은 부하 및 스파이크 테스트에서 더 큰 이점을 얻을 수 있습니다. 이는 애플리케이션의 요구 사항과, 더 중요하게는 사용자가 실제로 어떻게 사용하는지에 달려 있습니다.

성능 테스트 제대로 설계하기

성능 테스트를 제대로 설계하는 것은 매우 중요합니다. 대개 우리는 단순히 무작위 엔드포인트에 요청을 마구 날리고 싶지 않습니다. 그런 방식으로는 애플리케이션의 실제 동작이나 병목 지점에 대해 많은 정보를 얻기 어렵기 때문입니다.

제가 실무에서 오랫동안 효과를 본 방법은, 전형적인 사용자 행동을 파악하고 이를 성능 테스트에서 시뮬레이션하는 것입니다. 예를 들어, 주문 시스템을 가진 이커머스 애플리케이션을 상상해봅시다.

일반적인 사용자는 다음과 같은 행동을 할 수 있습니다.

  1. 몇 가지 제품을 검색합니다.
  2. 제품을 장바구니에 추가합니다.
  3. 결제 과정을 진행합니다.
  4. 결제를 완료합니다.

각 엔드포인트를 개별적으로 테스트하는 대신, 저는 이 전체 흐름을 시뮬레이션하고 프론트엔드가 일반적으로 호출하는 것과 동일한 엔드포인트를 호출하는 성능 테스트를 설계할 것입니다. 이렇게 하면 실제 사용자와 유사한 트래픽 패턴을 만들어낼 수 있죠.

또 다른 예로는 여러 유형의 사용자가 있고, 각 사용자 유형마다 다른 권한을 가지며 애플리케이션을 다르게 사용하는 비즈니스 애플리케이션이 있을 수 있습니다. 이런 경우, 각 사용자 유형에 대한 전형적인 워크플로우를 설계하고 프론트엔드가 하는 것과 동일한 순서로 API 엔드포인트를 호출할 수 있습니다. 이 접근 방식의 장점은 실제 사용자 행동을 시뮬레이션할 수 있다는 점입니다. 그런 다음, 동일한 현실적인 워크플로우를 유지하면서 동시 사용자 수를 늘리는 식으로 테스트 구성을 변경할 수 있습니다.

API 간 통신도 좋은 예시입니다. POST 엔드포인트 다음에 GET 엔드포인트가 호출되는 상황을 가정해봅시다. 이 경우, 특정 부하 하에서 다양한 입력 매개변수와 요청 패턴을 시뮬레이션하고 시스템이 어떻게 작동하는지 관찰할 수 있습니다.

사용자 경로를 설계했다고 해서 모든 준비가 끝난 것은 아닙니다. 실제 테스트 흐름을 구축할 때 고려해야 할 몇 가지 중요한 사항이 더 있습니다. 아래 그림은 몇 가지 핵심 아이디어를 요약하고 있습니다.

User Paths

  • 진짜 사용자는 성능 테스트가 요청을 보내는 것처럼 빠르게 클릭하지 않습니다. 따라서 각 작업 사이에 실제와 같은 '생각 시간(think time)'을 도입해야 합니다. 무작정 요청만 쏟아붓는 것은 실제 사용자의 행동 패턴을 반영하지 못합니다.
  • 모든 사용자가 동일한 경로를 따르지 않습니다. 이커머스 애플리케이션에서 일부 사용자는 주문을 완료하지만, 다른 사용자는 단순히 제품을 둘러보거나 장바구니에 담기만 하고 나중에 돌아올 수도 있습니다. 따라서 다양한 행동을 가진 여러 사용자 경로를 시뮬레이션해야 합니다.
  • 사용자들이 모두 정확히 동시에 접속하는 경우는 드뭅니다. 따라서 일반적인 부하 테스트에서는 부하를 점진적으로 증가시켜야 합니다(ramp-up).

현실적인 사용자 여정은 무엇을 테스트해야 하는지를 알려줍니다. 다음 질문은 시스템이 얼마나 많은 부하를 처리해야 하는가입니다.

예상 부하 정의하기

현실적인 워크로드를 설계하는 것은 작업의 일부일 뿐입니다. 테스트를 실행하기 전에 '성공적인 결과'가 정확히 무엇을 의미하는지도 정의해야 합니다. 모든 애플리케이션이 초당 1,000개의 요청을 처리할 필요는 없습니다. 모든 시스템에는 고유한 예상 워크로드와 성능 요구 사항이 있습니다.

예를 들어:

예상 부하: 150 RPS (초당 요청 수)

p95 < 400 ms (응답 시간의 95%가 400ms 미만)
p99 < 1 s (응답 시간의 99%가 1초 미만)
오류율 < 0.5%
필요한 처리량 유지
지속적으로 증가하는 큐/연결 없음

그렇다면 이 수치들을 어떻게 결정할 수 있을까요?

다시 이커머스 예시로 돌아가 봅시다. 많은 애플리케이션은 평소보다 트래픽이 현저히 높은 기간을 가집니다. 이커머스 앱이라면 크리스마스, 블랙 프라이데이, 또는 주요 세일 이벤트가 이에 해당할 수 있습니다. 만약 회사에 이미 좋은 관측 가능성(observability) 시스템이 구축되어 있다면, 과거 프로덕션 메트릭이 유용한 시작점이 될 수 있습니다. Grafana 같은 도구를 확인하여 최대 요청 비율, 동시 사용자 수, 주문량, CPU 사용량, 메모리 소비량 및 기타 관련 메트릭을 파악할 수 있습니다. 이를 바탕으로 미래의 부하를 추정할 수 있습니다. 예를 들어, 내년에 트래픽이 10% 증가할 것으로 예상된다면, 예상 부하에 추가적인 안전 마진을 더하여 시스템을 테스트하기로 결정할 수 있습니다.

필요한 부하를 추정하는 또 다른 방법은 비즈니스 요구 사항에서 시작하는 것입니다. 예를 들어, 비즈니스에서 우리의 이커머스 애플리케이션이 2시간의 피크 기간 동안 10,000건의 주문을 처리할 것으로 예상한다고 가정해봅시다. 일반적인 주문 흐름을 살펴보고, 사용자가 제품을 검색하고, 장바구니에 항목을 추가하거나 제거하고, 결제를 진행하고, 주문을 완료하는 동안 얼마나 많은 HTTP 요청이 발생하는지 추정할 수 있습니다. 만약 한 건의 완료된 주문이 평균 20개의 요청을 생성한다면, 10,000건의 주문은 해당 2시간 동안 약 200,000개의 요청을 의미합니다.

여기서부터 평균 요청 비율을 계산할 수 있습니다.

200,000 요청 / 7,200초 (2시간) ≈ 초당 약 28 요청

이것은 평균적으로 약 28 RPS를 제공합니다. 그러나 28 RPS가 자동으로 우리의 테스트 목표가 되어야 한다는 의미는 아닙니다. 이 계산은 완료된 주문 흐름으로 인해 발생하는 트래픽만 추정합니다. 실제 애플리케이션 트래픽은 일반적으로 더 높을 것입니다. 왜냐하면 탐색, 버려진 장바구니, 백그라운드 요청 및 다른 사용자 여정 또한 전체 워크로드에 기여하기 때문입니다. 실제 트래픽은 또한 균일하게 분산되는 경우가 드물므로, 더 짧은 트래픽 피크를 고려하고 적절한 안전 마진을 추가해야 합니다.

이러한 접근 방식은 신뢰할 수 있는 프로덕션 메트릭을 사용할 수 없을 때 필요한 부하를 추정하는 또 다른 방법을 제공하며, 기술 데이터와 비즈니스 요구 사항 모두에서 성능 목표를 도출할 수 있음을 보여줍니다.

성능 테스트와 외부 서비스

외부 서비스는 성능 테스트 중에 특별한 주의가 필요합니다.

어떤 경우에는 외부 서비스를 모킹(mocking)할 수 있습니다. 여러 이유로 해당 서비스를 테스트할 필요가 없거나 원치 않기 때문입니다. 좋은 예시는 LLM API나 다른 유료 서드파티 서비스처럼 우리 엔드포인트 뒤에 있는 유료 API입니다. 성능 테스트 중에는 우리 애플리케이션이 비교적 짧은 시간 안에 엄청난 수의 요청을 생성할 수 있기 때문에 비용은 충분히 타당한 우려 사항입니다.

또 다른 시나리오는 외부 API를 아예 호출할 필요가 없는 경우입니다. 예를 들어, 간단한 참조 데이터 API이거나 결제 제공업체처럼 해당 성능이 우리 테스트의 범위 밖에 있는 경우입니다. 대신, WireMock과 같은 도구를 사용하여 제어된 의존성으로 대체하고 테스트 중에 예상하는 응답을 반환할 수 있습니다. 이렇게 하면 외부 서비스의 성능, 비율 제한(rate limits) 또는 가용성이 결과에 영향을 미치거나 실제 병목 지점을 식별하기 어렵게 만들지 않고 우리 애플리케이션의 성능에 집중할 수 있습니다. 저도 예전에 외부 결제 API 연동 테스트에서 실제 결제가 계속 이루어지면 금전적 손실이 너무 커서 WireMock으로 모킹하여 테스트한 경험이 있습니다. 덕분에 핵심 로직의 성능은 정확하게 측정할 수 있었죠.

반면에 외부 서비스와의 통신이 시스템의 실제 성능에 중요한 부분이라면, 이를 별도로 테스트하거나 성능 테스트에 포함하는 것을 고려해야 합니다.

결론적으로, 외부 서비스를 모킹할지 여부는 우리가 시뮬레이션하고자 하는 동작과 정확히 무엇을 측정하고자 하는지에 따라 달라져야 합니다.

환경 설정

환경 설정은 성능 테스트의 또 다른 중요한 부분입니다. 이상적으로는 성능 테스트 환경이 프로덕션 환경과 최대한 유사해야 합니다. 환경이 프로덕션과 크게 다르다면, 결과가 오해의 소지가 있을 수 있으며, 특히 애플리케이션이 실제 프로덕션 부하에서 어떻게 작동할지 추정하고자 할 때 더욱 그렇습니다.

그렇다면 무엇을 설정해야 할까요?

첫째, 애플리케이션 자체는 프로덕션과 동일하거나 매우 유사한 구성을 사용해야 합니다. API가 실행되는 서버 또는 클러스터 또한 비교할 만한 CPU, 메모리, 스케일링 규칙 및 기타 리소스 제한을 가져야 합니다.

데이터베이스에도 마찬가지입니다. 유사한 데이터베이스 리소스를 사용하는 것뿐만 아니라, 거의 비어 있는 데이터베이스에 대해 테스트하는 것을 피해야 합니다. 데이터의 양과 분포는 성능에 상당한 영향을 미칠 수 있습니다. 높은 부하에서 CRUD 작업, 조인, 필터링 및 정렬은 테이블에 수백만 개의 행이 포함된 경우와 몇 개의 테스트 레코드만 있는 경우와 매우 다르게 작동할 수 있습니다. 인덱스, 쿼리 실행 계획, 통계 및 캐싱은 데이터의 크기와 구조에 따라 모두 다르게 작동할 수 있습니다. 초기에는 테스트 DB를 너무 가볍게 구성해서 프로덕션에서는 예상치 못한 쿼리 지연이 발생해 낭패를 본 적도 있습니다.

이러한 이유로, 가능한 한 테스트 데이터베이스는 현실적인 양의 대표적인 데이터를 포함해야 합니다.

캐싱 설정, 예를 들어 Redis 또는 인메모리 캐싱에도 주의를 기울여야 합니다. 다른 캐시 설정이나 영구적으로 '웜(warm)' 상태인 캐시로 테스트하는 것은 실제 프로덕션 동작을 나타내지 않는 결과를 초래할 수 있습니다.

네트워크 조건 또한 중요한 요소입니다. 예를 들어, 외부 서비스가 로컬에서 모킹되는 경우 요청이 거의 즉시 완료될 수 있지만, 프로덕션의 실제 서비스는 수십 또는 수백 밀리초의 네트워크 지연을 추가할 수 있습니다. 무엇을 측정하고자 하는지에 따라, 더 현실적인 결과를 얻기 위해 이러한 지연 시간을 시뮬레이션해야 할 수도 있습니다.

그리고 마지막으로, 부하 생성기(load generator) 자체입니다. 이 부분을 잊기 쉽습니다. 부하를 생성하는 머신은 필요한 워크로드를 생성하기에 충분한 CPU, 메모리, 네트워크 용량 및 사용 가능한 연결을 가지고 있어야 합니다. 그렇지 않으면, 우리가 실제로 테스트하는 애플리케이션 대신 부하 생성기가 병목 현상이 될 수 있습니다.

측정 지표와 결과

성능 테스트를 실행한 후에는 결과를 평가하고 일반적으로 어떤 형태의 보고서를 작성해야 합니다. 우리가 집중하는 측정 지표는 테스트 유형에 따라 달라지는데, 다른 테스트 유형은 다른 질문에 답하기 때문입니다.

다시 이커머스 애플리케이션으로 돌아가서, 부하 테스트와 스트레스 테스트를 모두 실행하기로 결정했다고 가정해봅시다.

부하 테스트의 경우, 우리는 이미 예상 워크로드(예: 초당 특정 요청 수 또는 동시 사용자 수)를 알고 있습니다. 이제 애플리케이션이 허용 가능한 성능을 유지하면서 이 부하를 처리할 수 있는지 확인하고자 합니다.

가장 중요한 측정 지표는 다음과 같습니다.

  • 응답 시간(Response time), 특히 p95 또는 p99와 같은 백분위수(percentiles).
  • 오류율(Error rate) — 요청 중 실패한 비율.
  • 처리량(Throughput) — 애플리케이션이 실제로 예상되는 수의 요청을 처리했는지 여부.
  • 리소스 사용량(Resource usage) — CPU, 메모리, 데이터베이스 연결, 연결 풀 및 기타 관련 리소스.

예를 들어, 특정 엔드포인트의 p95 응답 시간이 예상보다 훨씬 높다면, 병목 지점이 어디인지 조사하기 시작할 수 있습니다. 마찬가지로, 오류율이 허용 가능한 한도를 초과하면, 어떤 요청이 실패하고 왜 실패하는지 알아내야 합니다.

스트레스 테스트의 목표는 약간 다릅니다. 우리는 의도적으로 예상 수준을 넘어선 부하를 증가시키고 애플리케이션이 성능 저하를 시작하는 지점을 찾으려고 노력합니다. 여기서는 부하가 증가함에 따라 응답 시간과 오류율이 어떻게 변하는지, 그리고 CPU, 메모리, 데이터베이스 연결, 큐 및 기타 제한된 리소스와 함께 관찰합니다. 시스템이 포화 상태가 되어 너무 많은 오류를 생성하거나 더 이상 필요한 처리량을 유지할 수 없는 지점을 찾습니다.

부하가 감소한 후 애플리케이션이 어떻게 작동하는지 관찰하는 것도 유용합니다. 극심한 부하에서 속도가 느려지지만 나중에 회복되는 시스템은, 멈추거나 재시작이 필요한 시스템과는 매우 다르게 작동합니다.

아래 그림은 우리가 예시에서 모니터링했던 측정 지표를 보여주며, 특정 병목 현상이나 포화 지점을 식별하기 위해 여러 그래프를 함께 봐야 하는 이유를 강조합니다.

Performance test metrics

따라서 최종 보고서는 단순히 평균 응답 시간과 같은 단일 숫자만 포함해서는 안 됩니다. 생성된 워크로드를 지연 시간, 처리량, 오류 및 리소스 활용도와 연결하여, 애플리케이션이 우리의 기대치를 충족하지 못했는지 여부뿐만 아니라 그 이유까지도 이해할 수 있도록 해야 합니다.

마무리

이 글에서는 성능 테스트의 몇 가지 기본 사항을 다루고, 실제 시나리오를 위한 테스트를 어떻게 설계하는지에 중점을 두었습니다.

핵심은 애플리케이션이 실제로 어떻게 사용되는지 이해하고, 현실적인 테스트 환경을 준비하며, 대표적인 워크로드를 생성하고, 올바른 측정 지표를 모니터링하는 것입니다. 이러한 부분이 제대로 설계되었을 때, 성능 테스트는 병목 현상, 시스템 한계 및 부하 하에서의 전반적인 애플리케이션 동작에 대한 유용한 정보를 제공할 수 있습니다.

이미 성능 테스트 이론과 개별 테스트 유형을 설명하는 훌륭한 글들이 많이 있습니다. 저의 목표는 그 모든 것을 반복하는 것이 아니라, 성능 테스트를 좀 더 실용적인 관점에서 바라보고 제가 현실적인 테스트를 설계하는 접근 방식을 보여주는 것이었습니다.

결국, 성능 테스트는 워크로드, 환경 및 측정 지표가 충분히 현실적일 때만 그 결과가 의미를 가질 수 있습니다. 현실과 동떨어진 테스트는 시간 낭비일 뿐이라는 점, 잊지 마세요.


원문: https://dev.to/gramli/api-performance-testing-how-to-design-realistic-tests-59gn 수집일: 2026-09-18 01:49:32