Gatling으로 LLM API 부하 테스트하는 방법
대규모 언어 모델 (LLMs)로 애플리케이션을 구축할 때는 다양한 수요 수준을 처리할 수 있는지 확인하는 것이 필수적입니다. 바로 여기서 부하 테스트가 중요해집니다. 부하 테스트는 실제 트래픽을 시뮬레이션하여 다양한 조건에서 API의 성능을 평가합니다. 이 접근 방식은 잠재적인 병목 지점과 개선 영역을 식별하는 데 도움이 되며, 애플리케이션이 안정적이고 응답성이 뛰어난 상태를 유지하도록 보장합니다.
최근 베를린 Unstructured Data Meetup에서 Gatling의 개발자 애드보케이트인 Samir Akarioh가 Gatling을 사용하여 LLM API를 부하 테스트하는 방법에 대해 이야기했습니다. Gatling은 javascript 웹 애플리케이션을 부하 테스트하는 데 사용되는 오픈 소스 성능 테스트 프레임워크입니다. 그의 인사이트는 LLM API 부하 테스트의 중요성과 사용되는 다양한 방법을 조명했습니다. 이 블로그에서는 그의 핵심 포인트를 요약하고, 성능, 부하 및 응답 시간을 개선하기 위해 Milvus와 같은 벡터 데이터베이스로 구동되는 대규모 언어 모델 애플리케이션, 특히 RAG 앱(검색 증강 생성)을 부하 테스트하는 방법을 논의하겠습니다.
YouTube에서 Samir의 발표 다시 보기를 시청하세요.
부하 테스트란 무엇인가요?
부하 테스트는 특정 부하 조건에서 시스템이 어떻게 동작하는지 평가하는 성능 테스트의 한 유형으로, 일반적으로 방대한 양의 데이터로 시스템에 스트레스 테스트를 시도합니다. 주요 목표는 시스템이 정상 및 피크 조건에서 예상되는 사용자 트래픽이나 데이터 볼륨을 처리할 수 있는지 평가하는 것입니다. 부하 테스트 중에는 사용자 경험에 영향을 줄 수 있는 병목 현상, 성능 저하 또는 장애를 식별하기 위해 시스템의 동작을 모니터링합니다.
LLM API의 경우 이러한 시스템의 복잡한 특성과 자연어 처리의 높은 계산 요구 사항 때문에 부하 테스트가 특히 중요합니다. 적절한 부하 테스트를 수행하지 않으면 서비스 중단, 느린 응답 시간 또는 부정확한 결과로 이어질 수 있으며, 잠재적으로 사용자 신뢰와 AI 기반 애플리케이션의 전반적인 안정성을 해칠 수 있습니다.
이 비정형 데이터 밋업에서 Samir는 세 가지 유형의 부하 테스트인 용량 테스트, 스트레스 테스트, 그리고 소크 테스트를 빠르게 훑어보았습니다. 이제 조금 속도를 늦추고 각각을 심층적으로 살펴보겠습니다.
용량 테스트
용량 테스트는 성능 요구 사항을 충족하면서 API가 처리할 수 있는 최대 부하를 결정합니다. 목표는 시스템이 응답 시간이나 처리량 저하 없이 최대 부하에서 작동하는 "스위트 스팟"을 식별하는 것입니다. LLM API의 경우 용량 테스트는 정확하고 시기적절한 응답을 계속 제공하면서 초당 처리할 수 있는 최대 요청 수를 찾습니다.
예를 들어, 용량 테스트는 응답 시간이 증가하기 시작하거나 정확도가 감소하기 시작할 때까지 API에 프롬프트를 보내는 동시 사용자 수를 점진적으로 늘리는 방식일 수 있습니다. 이 정보는 용량 계획에 매우 중요하며, 인프라를 확장하거나 API를 최적화해야 하는 시점에 대한 의사 결정에 도움이 될 수 있습니다.
용량 테스트는 예상 트래픽을 계획하고 성능 저하가 나타나기 전에 한계를 이해하는 데 도움이 됩니다. 이는 사용자 경험을 저하시키지 않고 시스템이 예상 성장과 피크 사용 기간을 처리할 수 있도록 보장하는 데 필수적입니다.
스트레스 테스트
용량 테스트가 최적의 부하를 식별하는 반면, 스트레스 테스트는 시스템을 한계 이상으로 밀어붙여 중단 지점을 발견합니다. 목적은 갑작스러운 요청 급증이나 예상치 못하게 많은 데이터량과 같은 극한 조건에서 API가 어떻게 동작하는지 평가하는 것입니다.
스트레스 테스트는 AI 기반 애플리케이션으로 갑자기 대규모 트래픽을 유도하는 바이럴 소셜 미디어 게시물 같은 실제 상황을 시뮬레이션할 수 있습니다. 이러한 테스트 중에는 응답 시간과 처리량뿐만 아니라 오류율, 리소스 사용률(CPU, 메모리, 네트워크), API 응답의 품질도 모니터링하는 것이 중요합니다.
스트레스 테스트는 LLM API가 어떻게 실패할 수 있는지, 그리고 실패할 때 어떤 일이 발생하는지—충돌하는지, 느려지는지, 또는 복구되는지—를 이해하는 데 필수적입니다. 이 정보는 시스템 복원력을 개선하고 예상치 못한 사용량 급증을 원활하게 처리할 수 있도록 보장하는 데 매우 중요합니다. 또한 더 나은 장애 조치 및 로드 밸런싱 전략을 설계하는 데도 도움이 될 수 있습니다.
Soak Test
Soak 테스트, 또는 내구성 테스트는 API가 장기간에 걸쳐 어떻게 성능을 발휘하는지 평가합니다. 이는 짧은 테스트에서는 드러나지 않을 수 있는 메모리 누수, 성능 저하, 데이터베이스 연결 포화와 같은 문제를 식별합니다. LLM API의 경우, Soak 테스트는 시스템이 성능을 어떻게 유지하는지 관찰하기 위해 몇 시간 또는 며칠 동안 안정적인 요청 흐름을 실행합니다.
Soak 테스트의 이상적인 기간은 시스템과 예상 사용 패턴에 따라 달라질 수 있습니다. 일부 LLM API의 경우 24시간 테스트로 충분할 수 있지만, 다른 경우에는 장기간에 걸쳐서만 나타나는 미묘한 문제를 발견하기 위해 일주일간의 테스트가 도움이 될 수 있습니다.
Soak 테스트는 점진적인 성능 저하를 드러내는 데 특히 효과적입니다. 예를 들어, 시간이 지남에 따라 응답 시간이 서서히 증가하거나, 많은 수의 요청을 처리한 후 생성된 텍스트의 품질이 미묘하게 저하된다는 것을 보여줄 수 있습니다. 이러한 인사이트는 선제적 유지보수 조치를 구현하고 장기 성능을 최적화하는 데 매우 중요할 수 있습니다.
이 테스트는 API가 성능 저하 없이 지속적인 수요를 처리할 수 있도록 보장하며, 이는 지속적으로 실행되거나 장기적이고 일관된 트래픽을 처리할 것으로 예상되는 애플리케이션에 매우 중요합니다.
LLM API 부하 테스트를 위한 모범 사례
LLM API에 대한 부하 테스트를 수행할 때는 다음 모범 사례를 고려하세요:
실제 사용 패턴을 모방하는 현실적인 데이터와 시나리오를 사용하세요.
성능 임계값을 정확하게 식별하기 위해 부하를 점진적으로 증가시키세요.
응답 시간, 오류율, 리소스 사용률을 포함한 다양한 지표를 모니터링하세요.
네트워크 지연 시간을 고려하기 위해 다양한 지리적 위치에서 테스트하세요.
API가 일반적으로 처리하는 다양한 유형의 요청을 혼합하여 포함하세요.
부하 테스트 도구 LLM API의 부하 테스트에는 다음을 포함한 여러 도구를 사용할 수 있습니다:
Apache JMeter: 다양한 유형의 부하 테스트에 사용할 수 있는 오픈 소스 도구입니다.
Locust: 분산 부하 테스트에 특히 적합한 Python 기반 도구입니다.
Gatling: 대용량 부하 테스트에 뛰어난 Scala 기반 도구입니다. 다음 섹션에서는 Gatling을 사용한 부하 테스트를 살펴보겠습니다.
Gatling을 사용한 LLM API 부하 테스트
Gatling은 DevOps 및 Continuous Integration을 위해 설계된 웹 애플리케이션용 부하 테스트 도구입니다. 부하 테스트의 이론을 이해했으므로, Gatling을 사용하여 OpenAI Chat Completions API에 대한 부하 테스트를 실제로 수행하는 방법을 살펴보겠습니다. Gatling 프로젝트를 설정하려면 이 가이드를 따르세요.
1. 시뮬레이션 클래스 설정
먼저, 필요한 라이브러리를 가져오고 시뮬레이션 클래스를 설정하는 것부터 시작하세요:
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
이러한 import는 Gatling에서 시나리오를 생성하고 HTTP 요청을 처리하기 위해 핵심 DSL(Domain-Specific Language)과 HTTP DSL을 가져옵니다. 라이브러리를 import한 후, 시뮬레이션 클래스를 정의합니다.
public class SSELLM extends Simulation {
String api_key = System.getenv("api_key");
위 코드에서 SSELLM 클래스는 모든 Gatling 시뮬레이션에 필요한 기본 클래스인 Simulation 을 확장합니다. api_key 변수는 환경 변수에서 API 키를 가져와 민감한 정보가 하드코딩되지 않도록 합니다.
2. HTTP 프로토콜 구성
API 키를 전달한 후, 다음 단계는 LLM 서비스의 기본 URL을 지정하는 것입니다.
HttpProtocolBuilder httpProtocol =
http.baseUrl("https://api.openai.com/v1/chat")
.sseUnmatchedInboundMessageBufferSize(100);
baseUrl 은 API의 기본 엔드포인트를 지정하며, sseUnmatchedInboundMessageBufferSize(100) 은 예상 응답과 일치하지 않는 서버 전송 이벤트(SSE) 메시지를 처리하기 위한 버퍼 크기를 구성합니다.
3. 시나리오 정의
모든 Gatling 테스트의 핵심은 사용자 동작을 시뮬레이션하는 시나리오입니다. 테스트를 위한 시나리오를 정의해 보겠습니다.
ScenarioBuilder prompt = scenario("Scenario").exec(
sse("Connect to LLM and get Answer")
.post("/completions")
.header("Authorization", "Bearer " + api_key)
.body(StringBody("{"model": "gpt-3.5-turbo","stream":true,"messages":[{"role":"user","content":"What is a vector database "}]}"))
.asJson(),
asLongAs("#{stop.isUndefined()}").on(
sse.processUnmatchedMessages((messages, session) -> {
return messages.stream()
.anyMatch(message -> message.message().contains("{"data":"[DONE]"}")) ? session.set("stop", true) : session;
})
),
sse("close").close()
);
위 시나리오에서는 사용자가 OpenAI Chat API에 요청을 보내고 응답을 기다리는 과정을 시뮬레이션합니다. 테스트는 API에 SSE(Server-Sent Events) 연결을 설정합니다. 그런 다음 벡터 데이터베이스를 정의하도록 모델에 프롬프트하는 메시지와 함께 /completions 엔드포인트로 POST 요청을 보냅니다. 테스트는 asLongAs("#{stop.isUndefined()}") 를 사용해 들어오는 SSE 메시지를 계속 처리하며, 이는 특정 조건이 충족될 때까지 연결을 열린 상태로 유지합니다.
메시지가 수신되면 sse.processUnmatchedMessages(...) 는 응답에 상호작용이 완료되었음을 나타내는 "[DONE]" 신호가 포함되어 있는지 확인합니다. 이 신호가 감지되면 세션이 중지되고 sse("close").close()를 통해 연결이 닫힙니다.
4. 가상 사용자 주입
시나리오를 생성한 후, 마지막 단계는 시뮬레이션할 사용자 수를 지정하고 프로토콜을 구성하는 것입니다.
{
setUp(
prompt.injectOpen(atOnceUsers(3))
).protocols(httpProtocol);
}
}
위 코드는 세 명의 가상 사용자를 동시에 시나리오에 주입합니다. 이 간단한 부하 테스트는 세 개의 동시 요청을 처리할 때 API의 성능을 확인합니다.
다음 명령을 사용하여 코드를 실행하세요:
.mvnw.cmd gatling:test
코드가 실행되면 Gatling은 터미널에 파일 경로를 제공합니다. 이는 테스트 보고서의 경로입니다. 다음은 보고서의 일부 예시입니다.
그림 1: OpenAI chat completion API 테스트에 대한 Gatling Response Time Range Report
OpenAI chat completion API에 대해 예상한 대로, 테스트는 OpenAI API가 동시 요청을 효율적으로 처리했으며 모든 응답이 적절한 시간 범위 내에 수신되었음을 보여줍니다. 하지만 기억하세요. 우리는 동시 요청을 세 개만 보냈고 매우 짧은 프롬프트를 사용했습니다.
실제 시나리오에서 LLM은 동시에 수천 개의 요청과 더 긴 프롬프트를 받을 수 있습니다. 더 긴 프롬프트는 대부분 사용자가 LLM에 더 많은 컨텍스트를 제공할 때 발생합니다. 실제 사례에서는 가상 사용자 수와 프롬프트 길이를 추가하는 것을 고려하세요.
Gatling은 LLM API에만 국한되지 않습니다. Retrieval Augmented Generation (RAG) 애플리케이션을 구동하는 API를 부하 테스트하는 데도 사용할 수 있습니다. 이 접근 방식은 애플리케이션 성능에 대한 전반적인 평가를 제공합니다. RAG 기반 애플리케이션을 부하 테스트할 수 있는 시나리오를 만들어 보겠습니다.
하지만 그 전에 RAG가 무엇인지 궁금할 수 있습니다. 먼저 RAG의 개념을 이해해 보겠습니다.
Retrieval Augmented Generation (RAG) 이해하기
RAG, 즉 retrieval augmented generation은 대규모 언어 모델(LLM)의 생성 능력과 검색 메커니즘을 결합하여 Milvus 및 Zilliz Cloud(관리형 Milvus)와 같은 vector database에서 관련 정보를 가져오는 기법입니다. 외부 데이터를 컨텍스트로 활용함으로써 LLM은 hallucinate할 가능성이 낮아지고 더 정확하며 문맥적으로 관련성 있는 응답을 생성합니다. 또한 RAG를 사용하면 데이터 보안 문제를 걱정하지 않고도 더 개인화된 응답을 위한 컨텍스트로 LLM에 사용할 비공개 또는 독점 데이터를 검색할 수 있습니다.
일반적인 RAG 설정에서는 사용자 쿼리가 수신되면 RAG 시스템이 vector database로 구동되는 지식 베이스에서 관련 문서나 스니펫을 검색합니다. 그런 다음 이러한 문서는 LLM에 컨텍스트를 제공하는 데 사용되어 LLM이 더 잘 정보에 기반하고 포괄적인 응답을 생성할 수 있도록 합니다.
그림 2: RAG 작동 방식
RAG 애플리케이션을 테스트하는 시나리오 만들기
이제 RAG를 이해했으니 Gatling을 사용하여 RAG 애플리케이션을 부하 테스트하는 방법을 알아보겠습니다.
고객 지원 애플리케이션이 LLM으로 구동되는 상황을 상상해 보세요. 사용자는 상세하고 컨텍스트를 인식한 응답이 필요한 질문으로 시스템에 쿼리할 수 있습니다. 애플리케이션은 Milvus와 같은 vector database에서 관련 문서를 가져오기 위해 RAG를 활용하여 이러한 응답의 품질을 향상시킵니다. 이러한 문서는 LLM에 필요한 컨텍스트를 제공하여 더 정확하고 정보에 기반한 답변을 생성할 수 있게 합니다.
부하 테스트를 위해 아래 프로세스로 시나리오를 설계할 것입니다.
시뮬레이션된 사용자 요청: 가상 사용자가 추가 컨텍스트가 필요한 복잡한 쿼리를 보냅니다. 예를 들어, 사용자가 "내 기기의 연결 문제를 어떻게 해결하나요?"라고 물을 수 있습니다. 이 질문만으로는 LLM에서 고품질 응답을 얻기에 충분한 정보를 제공하지 않으므로, 시스템은 Milvus vector database에서 관련 문제 해결 문서를 검색해야 합니다.
컨텍스트 검색: 시스템은 사용자 쿼리를 기반으로 Milvus에서 가장 관련성 높은 문서를 가져옵니다. 이 단계는 LLM에 제공되는 컨텍스트의 품질을 결정하므로 매우 중요하며, 생성된 응답의 정확도에 직접적인 영향을 미칩니다. RAG 파이프라인에서 Milvus는 방대한 양의 문서를 indexes하고 vector similarity search를 수행하여 가장 관련성 높은 정보를 빠르게 찾습니다.
LLM 응답 생성: 관련 문서가 검색되면, 해당 문서들은 LLM에 컨텍스트로 제공됩니다. 그런 다음 LLM은 이 정보를 사용하여 사용자의 쿼리에 응답합니다. 이 프로세스는 답변을 생성하고 검색된 문서에서 특정 출처를 인용할 수도 있습니다.
동시 사용자로 부하 테스트: 그런 다음 더 많은 수의 가상 사용자를 주입하여 피크 사용 조건을 시뮬레이션합니다.
모니터링 및 분석: Gatling이 보고서를 생성하면, 애플리케이션을 구동하는 API가 마주칠 수 있는 병목 현상을 모니터링합니다. 테스트 결과는 대규모 언어 모델의 성능과 부하 상황에서의 검색 메커니즘 효율성 모두의 영향을 받는다는 점에 유의하는 것이 중요합니다. 이를 통해 전체 시스템이 어떻게 수행되고 있는지 종합적으로 평가할 수 있습니다.
위 시나리오를 사용하여 RAG 기반 앱을 부하 테스트하면 확장성 문제를 식별할 수 있습니다.
결론
Samir는 Gatling을 사용하여 LLM API를 부하 테스트하는 방법에 대한 유용한 인사이트를 제공했습니다. 그는 다양한 유형의 부하 테스트와 LLM API를 부하 테스트하는 방법을 설명했습니다. 또한 이 글을 확장하여 RAG 기반 고객 지원 애플리케이션과 같은 다른 LLM 기반 애플리케이션에서 부하 테스트를 더 깊이 수행하는 방법도 살펴보았습니다. 이 지식을 바탕으로 API에 대한 API 테스트를 개발하여 제품이 문제없이 확장될 수 있도록 할 수 있습니다. LLM API에 대한 부하 테스트를 수행할 때는 다음 모범 사례를 고려하세요:
LLM API 부하 테스트를 위한 모범 사례
실제 사용 패턴을 모방하는 현실적인 데이터와 테스트 케이스를 사용하세요.
성능 임계값을 정확히 식별하기 위해 부하를 점진적으로 증가시키세요.
응답 시간, 오류율, 리소스 사용률을 포함한 광범위한 지표를 모니터링하세요.
네트워크 지연 시간을 고려하기 위해 다양한 지리적 위치에서 테스트하세요.
API가 일반적으로 처리하는 다양한 유형의 요청을 혼합하여 포함하세요.
RAG, GenAI, 벡터 검색에 대한 추가 리소스
계속 읽기

Why We Built Vector Lakebase: Rethinking Unstructured Data Architecture for AI
Vector Lakebase: a unified, lake-native data foundation for AI workloads — and an answer to what happens after vector databases succeed.

The Great AI Agent Protocol Race: Function Calling vs. MCP vs. A2A
Compare Function Calling, MCP, and A2A protocols for AI agents. Learn which standard best fits your development needs and future-proof your applications.

Vector Databases vs. Hierarchical Databases
Use a vector database for AI-powered similarity search; use a hierarchical database for organizing data in parent-child relationships with efficient top-down access patterns.


