성능 테스트, 단순 부하만 걸면 망한다? API 현실 시나리오 설계 핵심 가이드
2026. 9. 19.
성능 테스트, 단순 부하만 걸면 망한다? API 현실 시나리오 설계 핵심 가이드
대규모 신규 기능을 배포하거나, 새로운 애플리케이션을 런칭하기 전, 혹은 단순히 특정 부하를 우리 시스템이 견딜 수 있는지 확인하는 작업에서 우리는 '성능 테스트'라는 용어를 수없이 들어왔을 겁니다.
성능 테스트는 우리 애플리케이션이 부하 상황에서 어떻게 동작하는지, 그리고 예상되는 압력을 견딜 수 있는지 파악하는 데 필수적인 과정입니다. 하지만 제대로 설계되지 않은 성능 테스트 결과는 오히려 오해를 불러일으키고, 잘못된 결론으로 이끌 수 있습니다. 제가 실무에서 여러 프로젝트를 거치며 느낀 점은, 무작정 요청만 쏟아붓는 테스트는 차라리 안 하는 것만 못하다는 것입니다.
반면, 적절히 설계된 성능 테스트는 시스템의 병목 현상을 식별하고, 애플리케이션의 한계를 이해하며, 서비스 배포 전 더 큰 확신을 안겨줄 수 있습니다.
이 글에서는 먼저 성능 테스트의 기본적인 개념과 유형을 살펴봅니다. 그리고 더 실용적인 부분으로 넘어가, 어떻게 현실적인 테스트 시나리오를 설계하고, 애플리케이션이 감당해야 할 부하를 정의하며, 외부 서비스와는 어떻게 상호작용할지, 그리고 테스트 환경이 프로덕션 환경과 얼마나 유사해야 하는지에 대해 자세히 다룰 예정입니다.
궁극적인 목표는 단순히 많은 수의 요청을 생성하는 것이 아니라, 우리 애플리케이션이 실제 환경에서 어떻게 동작할지에 대해 유용한 정보를 제공하는 성능 테스트를 만드는 것입니다.
목차
자, 그럼 성능 테스트의 기본적인 정의와 다양한 유형부터 살펴보며 시작해봅시다.
기본 정의
**성능 테스트(Performance Testing)**는 특정 워크로드 하에서 애플리케이션의 속도, 안정성, 확장성 및 응답성을 평가하는 비기능 소프트웨어 테스트 방법이라고 요약할 수 있습니다.
이를 단순화하기 위해 API를 예로 들어보겠습니다. 우리는 부하 상황에서 테스트하려는 엔드포인트를 호출하는 테스트를 준비합니다. 이때 호출 순서는 애플리케이션이 실제로 사용되는 방식을 반영하는 것이 중요합니다. 성능 테스트의 주된 아이디어는 예상되는 부하에 대비되지 않은 애플리케이션을 배포하는 것을 방지하는 데 있습니다.
성능이 좋지 않으면 프로덕션 장애, 느린 응답 시간, 또는 애플리케이션이 필요한 시간 내에 작업을 처리하지 못하는 상황으로 이어질 수 있습니다. 생각해보세요, 사용자가 느려터진 서비스에 얼마나 오래 머물까요? 단 몇 초의 지연도 이탈로 직결될 수 있습니다. 이 모든 문제는 결국 고객 이탈, 수익 손실, 또는 제품 평판 손상으로 이어질 수 있습니다.
성능 테스트는 구성, 목적, 그리고 관찰하려는 지표에 따라 다양한 유형으로 나눌 수 있습니다.
테스트 유형

그림이 많은 것을 말해주지만, 각 유형을 간략하게 설명해 보겠습니다.
**부하 테스트(Load tests)**는 예상 트래픽 또는 정상적인 수준의 부하에서 애플리케이션이 어떻게 작동하는지 확인하는 테스트입니다. 목표는 시스템이 허용 가능한 응답 시간, 오류율 및 리소스 사용량을 유지하면서 필요한 사용자 또는 요청 수를 처리할 수 있음을 확인하는 것입니다.
**스트레스 테스트(Stress tests)**는 애플리케이션을 예상 한계 이상으로 밀어붙여 언제부터 성능 저하를 보이거나 실패하는지 찾아냅니다. 이를 통해 시스템의 최대 용량을 식별하고 CPU, 메모리, 데이터베이스 연결 또는 스레드와 같은 리소스가 소진될 때 어떻게 동작하는지 관찰할 수 있습니다.
**내구성 테스트(Endurance / Soak tests)**는 애플리케이션을 장시간 동안 지속적인 부하 상태에서 실행합니다. 이 테스트의 목적은 짧은 테스트에서는 나타나지 않을 수 있는 문제, 예를 들어 메모리 누수, 커넥션 누수, 리소스 고갈, 또는 시간이 지남에 따른 성능 저하 등을 찾아내는 데 있습니다.
**스파이크 테스트(Spike / Peak tests)**는 갑작스럽고 상당한 트래픽 증가를 시뮬레이션합니다. 이를 통해 애플리케이션이 급격한 부하 변화에 어떻게 반응하고, 트래픽이 정상으로 돌아왔을 때 얼마나 잘 복구되는지 확인할 수 있습니다.
**볼륨 테스트(Volume tests)**는 애플리케이션이 대량의 데이터를 처리하거나 작업해야 할 때 어떻게 동작하는지에 초점을 맞춥니다. 예를 들어, 데이터 볼륨이 평소보다 훨씬 클 때 데이터베이스 쿼리, 가져오기(imports), 내보내기(exports) 또는 배치(batch) 작업이 어떻게 동작하는지 테스트할 수 있습니다.
**확장성 테스트(Scalability tests)**는 워크로드를 늘리고 더 많은 리소스를 추가할 때 애플리케이션의 성능이 어떻게 변하는지 검증합니다. 목표는 시스템이 효율적으로 확장될 수 있는지 이해하는 것입니다. 예를 들어, 더 많은 애플리케이션 인스턴스, CPU, 메모리 또는 데이터베이스 용량을 추가하여 시스템이 확장되는지를 확인합니다.
이 모든 유형별로 완전히 다른 테스트를 구현할 필요는 없습니다. 많은 경우, 동일한 성능 테스트 시나리오를 재사용하고 동시 사용자 수, 지속 시간, 요청 속도 또는 워크로드 패턴과 같은 구성을 변경하여 측정하려는 목표에 따라 다양한 테스트를 수행할 수 있습니다. 그리고 테스트 목표에 따라 다른 지표들을 관찰하면 됩니다.
모든 애플리케이션이 모든 유형의 성능 테스트를 필요로 하는 것은 아닙니다. 어떤 애플리케이션은 주로 부하 및 스트레스 테스트만 필요할 수 있고, 다른 애플리케이션은 부하 및 스파이크 테스트에서 더 많은 이점을 얻을 수 있습니다. 이는 전적으로 애플리케이션의 요구사항과 더 중요하게는 사용자가 실제로 어떻게 사용하는지에 따라 달라집니다.
성능 테스트 제대로 설계하기
제대로 설계된 성능 테스트는 매우 중요합니다. 왜냐하면 대부분의 경우, 우리는 단순히 임의의 엔드포인트에 요청을 쏟아붓고 싶지 않기 때문입니다. 그런 방식으로는 애플리케이션의 실제 동작이나 병목 지점에 대해 많은 것을 알려주지 않습니다.
제가 실무에서 오랫동안 효과적이라고 느꼈던 방법은, 전형적인 사용자 행동을 식별하고 이를 성능 테스트에서 시뮬레이션하는 것입니다. 예를 들어, 주문 시스템을 가진 이커머스 애플리케이션을 상상해봅시다.
일반적인 사용자는 다음과 같은 행동을 할 수 있습니다.
- 몇 가지 상품을 검색합니다.
- 상품을 장바구니에 추가합니다.
- 결제 과정을 진행합니다.
- 결제를 완료합니다.
각 엔드포인트를 개별적으로 테스트하는 대신, 저는 이 전체 흐름을 시뮬레이션하고 프론트엔드가 일반적으로 호출하는 것과 동일한 엔드포인트를 호출하는 성능 테스트를 설계할 것입니다.
또 다른 예로는 여러 유형의 사용자가 있고, 각 사용자 유형이 다른 권한을 가지며 애플리케이션을 다르게 사용하는 비즈니스 애플리케이션이 있을 수 있습니다. 이 경우, 각 사용자 유형에 대한 전형적인 워크플로우를 설계하고 프론트엔드가 호출하는 것과 동일한 순서로 API 엔드포인트를 호출할 수 있습니다. 이 접근 방식의 장점은 현실적인 사용자 행동을 시뮬레이션할 수 있다는 것입니다. 그런 다음, 동일한 현실적인 워크플로우를 유지하면서 동시 사용자 수를 늘리는 식으로 테스트 구성을 변경할 수 있습니다. 예전에 제가 맡았던 프로젝트 중 하나는 핵심 서비스 API의 성능 병목이 단순히 특정 엔드포인트에 부하를 주는 방식으로는 전혀 드러나지 않았습니다. 사용자 시나리오를 그대로 모사하여 테스트했을 때 비로소 결제 시스템의 복잡한 트랜잭션 로직에서 지연이 발생한다는 것을 찾아낼 수 있었죠. 단발성 테스트로는 절대 알 수 없었던 문제였습니다.
API 간 통신도 좋은 예시가 될 수 있습니다. POST 엔드포인트가 GET 엔드포인트로 이어지는 상황을 가정해봅시다. 이 경우, 우리는 특정 부하 하에서 다양한 입력 파라미터와 요청 패턴을 시뮬레이션하고 시스템이 어떻게 동작하는지 관찰할 수 있습니다.
사용자 경로를 설계했다고 가정해 봅시다. 실제 테스트 흐름을 구축할 때 여전히 고려해야 할 몇 가지 중요한 사항이 있습니다. 아래 이미지는 몇 가지 핵심 아이디어를 요약합니다.

- 실제 사용자는 성능 테스트 도구처럼 빠르게 클릭하지 않으므로, 작업 사이에 현실적인 '고려 시간(think time)'을 도입해야 합니다.
- 모든 사용자가 동일한 경로를 따르지는 않습니다. 이커머스 애플리케이션에서 어떤 사용자는 주문을 완료하지만, 다른 사용자는 단순히 제품을 탐색하거나 장바구니에 항목을 추가하고 나중에 돌아올 수 있습니다. 따라서 다양한 행동을 가진 여러 사용자 경로를 시뮬레이션해야 합니다.
- 사용자들은 보통 정확히 같은 순간에 모두 연결되지 않으므로, 정상적인 부하 테스트의 경우 부하를 점진적으로 늘려야(ramp up) 합니다.
현실적인 사용자 여정은 무엇을 테스트할지 알려줍니다. 다음 질문은 시스템이 얼마나 많은 부하를 처리해야 하는가입니다.
예상 부하 정의하기
현실적인 워크로드를 설계하는 것은 일의 한 부분일 뿐입니다. 테스트를 실행하기 전에 성공적인 결과가 무엇을 의미하는지 정의해야 합니다. 모든 애플리케이션이 초당 1,000개의 요청을 처리할 필요는 없습니다. 모든 시스템은 고유한 예상 워크로드와 성능 요구사항을 가집니다.
예를 들면 다음과 같습니다.
예상 부하: 150 RPS
p95 < 400 ms
p99 < 1 s
오류율(Error rate) < 0.5%
필요한 처리량(throughput) 유지
지속적으로 증가하는 큐/연결 없음
그렇다면 이 숫자들을 어떻게 결정할 수 있을까요?
이커머스 예시로 돌아가 봅시다. 많은 애플리케이션은 평소보다 트래픽이 훨씬 높은 기간을 가집니다. 이커머스 애플리케이션의 경우, 이는 크리스마스, 블랙 프라이데이 또는 다른 주요 세일 이벤트일 수 있습니다. 만약 회사에 이미 좋은 관측 가능성(observability)이 구축되어 있다면, 과거 프로덕션 지표는 유용한 시작점을 제공할 수 있습니다. Grafana와 같은 도구를 확인하여 피크 요청률, 동시 사용자 수, 주문량, CPU 사용량, 메모리 소비량 및 기타 관련 지표를 확인할 수 있습니다. 그런 다음 미래의 부하를 추정할 수 있습니다. 예를 들어, 내년에 트래픽이 10% 증가할 것으로 예상한다면, 예상 부하에 추가적인 안전 마진을 더하여 시스템을 테스트하기로 결정할 수 있습니다.
필요한 부하를 추정하는 또 다른 방법은 비즈니스 요구사항에서 시작하는 것입니다. 예를 들어, 비즈니스 측에서 우리 이커머스 애플리케이션이 2시간의 피크 기간 동안 10,000건의 주문을 처리할 것으로 예상한다고 가정해 봅시다. 일반적인 주문 흐름을 살펴보고, 사용자가 제품을 검색하고, 장바구니에 항목을 추가하거나 제거하고, 결제 과정을 거쳐 주문을 완료하는 동안 얼마나 많은 HTTP 요청이 발생하는지 추정할 수 있습니다. 만약 완료된 주문 하나가 평균 20개의 요청을 생성한다면, 10,000건의 주문은 그 2시간 동안 대략 200,000개의 요청을 의미합니다.
여기서부터 평균 요청률을 계산할 수 있습니다.
200,000 요청 / 7,200초 ≈ 초당 28 요청
이는 대략 초당 28개 요청의 평균을 제공합니다. 그렇다고 해서 28 RPS가 자동으로 우리의 테스트 목표가 되어야 한다는 의미는 아닙니다. 이 계산은 완료된 주문 흐름으로 인해 발생하는 트래픽만 추정합니다. 실제 애플리케이션 트래픽은 일반적으로 더 높을 것입니다. 왜냐하면 탐색, 버려진 장바구니, 백그라운드 요청 및 기타 사용자 여정 또한 전체 워크로드에 기여하기 때문입니다. 실제 트래픽은 고르게 분산되는 경우가 거의 없으므로, 더 짧은 트래픽 피크를 고려하고 적절한 안전 마진을 추가해야 합니다.
이 접근 방식은 신뢰할 수 있는 프로덕션 지표를 사용할 수 없을 때 필요한 부하를 추정하는 또 다른 방법을 제공하며, 성능 목표를 기술 데이터와 비즈니스 요구사항 모두에서 도출할 수 있음을 보여줍니다.
성능 테스트와 외부 서비스
외부 서비스는 성능 테스트 중에 특별한 주의가 필요합니다.
어떤 경우에는 외부 서비스를 모의(mock)할 수 있습니다. 단순히 여러 가지 이유로 해당 서비스를 테스트할 필요가 없거나 원하지 않기 때문입니다. 좋은 예로는 LLM API나 다른 유료 서드파티 서비스와 같이 우리 엔드포인트 뒤에 있는 유료 API가 있습니다. 비용 문제도 무시할 수 없습니다. 성능 테스트 중에는 우리 애플리케이션이 단시간에 엄청난 수의 요청을 생성할 수 있는데, 유료 API를 그대로 사용하면 비용 폭탄을 맞을 수도 있겠죠.
또 다른 시나리오는 단순히 외부 API를 전혀 호출할 필요가 없는 경우입니다. 예를 들어, 단순한 참조 데이터 API이거나 성능 테스트의 범위 밖에 있는 결제 제공업체일 수 있습니다. 대신, WireMock과 같은 도구를 사용하여 통제된 종속성으로 대체하고 테스트 중에 예상하는 응답을 반환할 수 있습니다. 이를 통해 외부 서비스의 성능, 비율 제한 또는 가용성이 결과에 영향을 미치거나 실제 병목 현상을 식별하기 어렵게 만들지 않고, 우리 애플리케이션의 성능에 집중할 수 있습니다.
반면에 외부 서비스와의 통신이 시스템의 실제 성능에 중요한 부분이라면, 우리는 이를 별도로 테스트하거나 성능 테스트에 포함하는 것을 고려해야 합니다.
결론적으로, 외부 서비스를 모의할지 여부는 우리가 시뮬레이션하려는 행동과 정확히 무엇을 측정하고 싶은지에 따라 달라져야 합니다.
환경 구성
환경 구성은 성능 테스트의 또 다른 중요한 부분입니다. 이상적으로는 성능 테스트 환경이 프로덕션 환경과 가능한 한 유사해야 합니다. 만약 환경이 프로덕션과 크게 다르다면, 결과가 오해를 불러일으킬 수 있으며, 특히 애플리케이션이 실제 프로덕션 부하에서 어떻게 동작할지 추정하려 할 때 더욱 그렇습니다.
그렇다면 무엇을 구성해야 할까요?
첫째, 애플리케이션 자체는 프로덕션과 동일하거나 매우 유사한 구성을 사용해야 합니다. API가 실행되는 서버 또는 클러스터 또한 비교 가능한 CPU, 메모리, 스케일링 규칙 및 기타 리소스 제한을 가져야 합니다.
데이터베이스에도 동일하게 적용됩니다. 단순히 유사한 데이터베이스 리소스를 사용하는 것뿐만이 아닙니다. 거의 비어있는 데이터베이스를 대상으로 테스트하는 것을 피해야 합니다. 데이터의 양과 분포는 성능에 상당한 영향을 미칠 수 있습니다. 높은 부하에서 CRUD 작업, 조인, 필터링 및 정렬은 테이블에 수백만 개의 행이 포함된 경우와 몇 개의 테스트 레코드만 있는 경우 매우 다르게 동작할 수 있습니다. 인덱스, 쿼리 실행 계획, 통계 및 캐싱은 데이터의 크기와 구조에 따라 모두 다르게 작동할 수 있습니다. 제 경험상, 개발 단계에서 성능 최적화를 마쳤다고 생각했던 API가 실제 프로덕션과 유사한 데이터 볼륨을 가진 테스트 환경에서 완전히 다른 병목을 드러내는 경우가 많았습니다. 인덱스나 쿼리 플랜이 데이터 양에 따라 천차만별로 변하기 때문이죠.
이러한 이유로, 테스트 데이터베이스는 가능한 한 현실적인 양의 대표 데이터를 포함해야 합니다.
Redis 또는 인메모리 캐싱과 같은 캐싱 구성에도 주의를 기울여야 합니다. 다른 캐시 구성이나 항상 캐시가 '따뜻한(warm)' 상태에서 테스트하는 것은 실제 프로덕션 동작을 나타내지 않는 결과를 초래할 수 있습니다.
네트워크 조건 또한 중요한 요소입니다. 예를 들어, 외부 서비스가 로컬에서 모의될 경우 요청이 거의 즉시 완료될 수 있지만, 프로덕션의 실제 서비스는 수십 또는 수백 밀리초의 네트워크 지연 시간을 추가할 수 있습니다. 무엇을 측정하느냐에 따라 더 현실적인 결과를 얻기 위해 이 지연 시간을 시뮬레이션해야 할 수도 있습니다.
마지막으로, 부하 발생기(load generator) 자체도 중요합니다. 이것은 간과하기 쉽습니다. 부하를 생성하는 머신은 필요한 워크로드를 생성하기에 충분한 CPU, 메모리, 네트워크 용량 및 사용 가능한 연결을 가지고 있어야 합니다. 그렇지 않으면 우리가 실제로 테스트하려는 애플리케이션 대신, 부하 발생기 자체가 병목이 되어 그 성능 한계를 측정하는 결과를 낳을 수 있습니다.
지표 및 결과
성능 테스트를 실행하고 나면, 결과를 평가하고 일반적으로 어떤 형태의 보고서를 작성해야 합니다. 우리가 집중하는 지표는 테스트 유형에 따라 달라지는데, 이는 다른 테스트 유형이 다른 질문에 답하기 때문입니다.
우리 이커머스 애플리케이션으로 돌아가서, 부하 테스트와 스트레스 테스트를 모두 실행하기로 결정했다고 가정해봅시다.
부하 테스트의 경우, 우리는 이미 예상 워크로드(예: 초당 특정 요청 수 또는 동시 사용자 수)를 알고 있습니다. 이제 애플리케이션이 허용 가능한 성능을 유지하면서 이 부하를 처리할 수 있는지 확인하고자 합니다.
가장 중요한 지표 중 일부는 다음과 같습니다.
- 응답 시간(Response time), 특히 p95 또는 p99와 같은 백분위수(percentiles).
- 오류율(Error rate) — 실패한 요청의 백분율.
- 처리량(Throughput) — 애플리케이션이 실제로 예상되는 수의 요청을 처리했는지 여부.
- 리소스 사용률(Resource usage) — CPU, 메모리, 데이터베이스 연결, 연결 풀 및 기타 관련 리소스.
예를 들어, 특정 엔드포인트의 p95 응답 시간이 예상보다 훨씬 높다면, 병목 지점이 어디인지 조사하기 시작할 수 있습니다. 마찬가지로, 오류율이 허용 한도를 초과하면 어떤 요청이 실패하고 왜 실패하는지 찾아내야 합니다.
스트레스 테스트의 경우, 우리의 목표는 약간 다릅니다. 우리는 의도적으로 예상 수준을 넘어 부하를 증가시키고 애플리케이션이 성능 저하를 시작하는 지점을 찾으려고 합니다. 여기서는 부하가 증가함에 따라 응답 시간과 오류율이 어떻게 변하는지, 그리고 CPU, 메모리, 데이터베이스 연결, 큐 및 기타 제한된 리소스와 함께 관찰합니다. 시스템이 포화 상태가 되거나, 너무 많은 오류를 생성하거나, 더 이상 필요한 처리량을 유지할 수 없는 지점을 찾고 있는 것입니다.
부하가 감소한 후 애플리케이션이 어떻게 동작하는지 관찰하는 것도 유용합니다. 극심한 부하에서 느려지지만 나중에 회복되는 시스템은 멈추거나 재시작이 필요한 시스템과는 매우 다르게 동작하기 때문입니다.
아래 이미지는 우리가 예시에서 모니터링한 지표들을 보여주며, 특정 병목 현상이나 포화 지점을 식별하기 위해 여러 그래프를 함께 봐야 하는 이유를 강조합니다.

따라서 최종 보고서는 단순히 평균 응답 시간 같은 단일 숫자만 포함해서는 안 됩니다. 생성된 워크로드와 지연 시간, 처리량, 오류, 그리고 리소스 사용률을 연관 지어 보여줌으로써, 애플리케이션이 우리의 기대를 충족시키지 못한 이유까지 이해할 수 있도록 해야 합니다.
마무리
이 글에서는 성능 테스트의 몇 가지 기본 사항을 다루고, 실제 시나리오를 위한 테스트를 어떻게 설계하는지에 집중했습니다.
핵심은 애플리케이션이 실제로 어떻게 사용되는지 이해하고, 현실적인 테스트 환경을 준비하며, 대표적인 워크로드를 생성하고, 올바른 지표를 모니터링하는 것입니다. 이러한 부분들이 제대로 설계된다면, 성능 테스트는 병목 현상, 시스템 한계 및 부하 하에서의 전반적인 애플리케이션 동작에 대해 유용한 정보를 제공할 수 있습니다.
이미 성능 테스트 이론과 개별 테스트 유형을 설명하는 훌륭한 글들이 많습니다. 제가 여기서 목표로 한 것은 그러한 내용들을 반복하는 것이 아니라, 성능 테스트를 좀 더 실용적인 관점에서 바라보고 현실적인 테스트를 어떻게 설계하는지에 대한 저의 접근 방식을 보여주는 것이었습니다.
결국 성능 테스트는 워크로드, 환경, 그리고 측정 지표가 현실적일 만큼 유의미할 때만 진정한 가치를 발휘합니다.
원문: https://dev.to/gramli/api-performance-testing-how-to-design-realistic-tests-59gn 수집일: 2026-09-19 01:54:26