이 페이지에서는 부하 테스트 벡터 검색 엔드포인트에 대한 지침, 예제 코드 및 예제 Notebook을 제공합니다. 부하 테스트를 사용하면 프로덕션 환경에 배포되기 전에 벡터 검색 엔드포인트의 성능 및 프로덕션 준비 상태를 이해할 수 있습니다. 부하 테스트는 다음을 알려줄 수 있습니다.
- 다양한 크기 조정 수준의 대기 시간
- 처리량 제한 및 병목 상태(초당 요청 수, 대기 시간 분석)
- 지속적인 부하에 따른 오류율
- 리소스 사용률 및 용량 계획
부하 테스트 및 관련 개념에 대한 자세한 내용은 엔드포인트를 제공하기 위한 부하 테스트를 참조하세요.
요구 사항
이러한 단계를 시작하기 전에 배포된 벡터 검색 엔드포인트와 엔드포인트에 대해 Can Query 권한이 있는 서비스 주체가 있어야 합니다. 1단계: 서비스 주체 인증 설정 참조
다음 파일 및 예제 Notebook의 복사본을 다운로드하여 Azure Databricks 작업 영역으로 가져옵니다.
-
input.json. 이는 엔드포인트의 모든 동시 연결에서
input.json로 전송되는 페이로드를 지정하는 파일의 예입니다. 필요한 경우 여러 파일을 가질 수 있습니다. 예제 Notebook을 사용하는 경우 이 파일은 제공된 입력 테이블에서 자동으로 생성됩니다. -
fast_vs_load_test_async_load.py. 이 스크립트를 작업 영역(예
/Workspace/Users/<your-username>/fast_vs_load_test_async_load.py: )에 업로드하고 Notebook 매개 변수를locust_script_path해당 경로로 설정합니다. 이 스크립트는 인증, 페이로드 배달 및 디버그 메트릭 컬렉션을 처리합니다. - 다음은 부하 테스트를 실행하는 예제 노트북입니다. 최상의 성능을 위해 다수의 코어가 장착된 단일 노드 클러스터에서 이 노트북을 실행합니다(사용 가능한 모든 CPU에서 Locust가 확장됩니다). 미리 생성된 포함이 있는 쿼리에는 높은 메모리를 사용하는 것이 좋습니다.
예제 노트북 및 빠른 시작
시작하려면 다음 예제 Notebook을 사용합니다. 두 가지 탐색 모드, 즉 정의한 특정 동시성 수준을 테스트하는 점진적 스윕과 몇 단계에서 지속 가능한 최대 QPS(중단점)를 자동으로 찾는 이진 검색 모드를 지원합니다. 모든 매개 변수는 위젯을 사용하여 구성되므로 코드 편집 없이 Notebook을 대화형으로 또는 Databricks 작업으로 실행할 수 있습니다.
메뚜기 부하 테스트 Notebook
부하 테스트 프레임워크: Locust
Locust 는 다음을 수행할 수 있는 오픈 소스 부하 테스트 프레임워크입니다.
- 동시 클라이언트 연결 수 변경
- 연결 생성 속도 제어
- 테스트 전체에서 엔드포인트 성능 측정
- 사용 가능한 모든 CPU 코어 자동 검색 및 사용
예제 노트북에서는 --processes -1 플래그를 사용하여 CPU 코어를 자동으로 감지하고 최대한 활용합니다.
로커스트가 CPU에 의해 병목 상태가 되면 출력에 메시지가 나타납니다.
1단계: 서비스 주체 인증 설정
중요합니다
프로덕션과 유사한 성능 테스트의 경우 항상 OAuth 서비스 주체 인증을 사용합니다. 서비스 주체는 PAT(개인 액세스 토큰)에 비해 최대 100ms 더 빠른 응답 시간 및 더 높은 요청 속도 제한을 제공합니다.
서비스 주체 만들기 및 구성
Databricks 서비스 주체를 만듭니다. 자세한 내용은 계정에 서비스 주체 추가를 참조하세요.
권한 부여:
- 벡터 검색 엔드포인트 페이지로 이동합니다.
- 사용 권한을 클릭합니다.
- 서비스 주체에 쿼리할 수 있는 권한을 부여합니다.
OAuth 비밀을 만듭니다.
- 서비스 주체 세부 정보 페이지로 이동합니다.
- 비밀 탭을 클릭합니다.
- 비밀 생성을 클릭합니다.
- 수명을 설정합니다(장기 테스트에는 365일 권장).
- 클라이언트 ID와 비밀을 모두 즉시 복사합니다.
자격 증명을 안전하게 저장합니다.
- Databricks 시크릿 범위를 만듭니다. 자세한 내용은 자습서: Databricks 비밀 만들기 및 사용을 참조하세요.
- 다음 코드 예제와 같이 서비스 주체 클라이언트 ID를 저장
service_principal_client_id하고 OAuth 비밀을 다음과 같이service_principal_client_secret저장합니다.
# In a Databricks notebook dbutils.secrets.put("load-test-auth", "service_principal_client_id", "<CLIENT_ID>") dbutils.secrets.put("load-test-auth", "service_principal_client_secret", "<SECRET>")
2단계: 부하 테스트 구성
Notebook 구성
Notebook 맨 위에 있는 위젯을 사용하여 Notebook 매개 변수를 구성합니다. Notebook을 Databricks 작업으로 실행하는 경우 이러한 값을 작업 매개 변수로 전달합니다. 코드 편집이 필요하지 않습니다.
| 매개 변수 | Description | 권장되는 값 |
|---|---|---|
endpoint_name |
벡터 검색 엔드포인트의 이름 | 엔드포인트 이름 |
index_name |
전체 인덱스 이름(catalog.schema.index) |
인덱스 이름 |
test_table |
원본 테이블에서 샘플 쿼리를 가져옵니다(catalog.schema.table) |
인덱스 입력 테이블 |
query_column |
관리형 임베딩에 사용할 텍스트 열 |
text로 그대로 두거나 컬럼 이름으로 설정하십시오. |
embedding_column |
미리 계산된 포함 벡터를 포함하는 열입니다. 자체 관리형 임베딩에만 사용됩니다. | 관리되는 임베딩에는 빈칸으로 두세요. |
sample_size |
테스트에 대해 샘플링할 쿼리 수 | 1000 |
target_concurrencies |
테스트할 동시 클라이언트 수의 쉼표로 구분된 목록 | 5,10,20,50 |
step_duration_seconds |
동시성 수준당 기간(초)입니다. 하나의 값은 모든 수준에 적용되거나 수준당 하나를 쉼표로 구분된 목록으로 제공합니다. |
300 (5분) |
secret_scope_name |
Databricks 비밀 범위의 이름 | 범위 이름 |
locust_script_path |
스크립트의 fast_vs_load_test_async_load.py 작업 영역 경로 |
/Workspace/Users/<your-username>/fast_vs_load_test_async_load.py |
output_table |
(선택 사항) 결과를 ()에catalog.schema.table 저장할 델타 테이블입니다. 처음 실행할 때 자동으로 생성됩니다. |
catalog.schema.load_test_results |
run_name |
나중에 분석할 수 있도록 이 실행에 태그를 지정하는 이름 또는 주석 | 설명이 포함된 레이블 |
exploration_mode |
gradual 순서대로 진행합니다 target_concurrencies .
binary_search 는 중단점을 자동으로 찾습니다( 중단점 탐색 참조). |
gradual |
max_target_qps |
(binary_search 전용) 쿼리 초당 수(QPS) 검색에 대한 상한치 |
500 |
exploration_steps |
(binary_search 만) 이진 검색 반복의 최대 수 |
8 |
error_rate_threshold |
(binary_search 만) 성공으로 계산되는 단계의 최대 허용 오류율(%)입니다. |
1.0 |
num_results |
쿼리당 반환할 결과 수 | 10 |
columns_to_return |
쿼리 결과에서 반환할 열의 쉼표로 구분된 목록입니다(예: id,text). 모든 열을 반환하려면 비워 둡니다. |
기본값으로 비워 둡니다. |
관리형 임베딩 대 자체 관리형 임베딩
Notebook은 관리형 임베딩(Databricks가 쿼리 시점에 임베딩을 생성하는 경우)과 자체 관리형 임베딩(사전 계산된 벡터를 직접 전달하는 경우)을 모두 지원합니다. 인덱스 형식에 따라 적절한 매개 변수를 구성합니다.
| 인덱스 유형 | 설정할 매개 변수 | 설정되지 않은 상태로 둡니다. |
|---|---|---|
| 관리되는 임베딩 (Databricks 관리 임베딩 모델을 사용하는 델타 동기화 인덱스) |
query_column - 쿼리로 사용할 텍스트 열 이름 |
embedding_column (비워 두기) |
| 자체 관리형 임베딩 (사전 계산된 벡터를 사용하는 델타 동기화 또는 직접 벡터 액세스 인덱스) |
embedding_column - 미리 계산된 임베딩 벡터가 있는 열 |
query_column |
비고
관리형 임베딩 인덱스의 경우 부하 테스트는 임베딩 생성 시간을 포함한 종단 간 지연 시간을 측정합니다. 임베딩 엔드포인트가 0으로 축소되면 첫 번째 테스트 실행에 콜드 스타트 오버헤드가 표시됩니다. 포함 대기 시간을 검색 대기 시간에서 격리 하는 방법은 포함 모델 병목 상태 식별 을 참조하세요.
왜 5-10 분?
최소 테스트 기간은 5분입니다.
- 초기 쿼리에는 콜드 시작 오버헤드가 포함될 수 있습니다.
- 엔드포인트는 안정적인 상태 성능에 도달하는 데 시간이 필요합니다.
- 엔드포인트를 제공하는 모델의 자동 크기 조정(사용하도록 설정된 경우)은 활성화하는 데 시간이 걸립니다.
- 짧은 테스트는 지속된 부하 상태에서 스로틀링 동작을 간과합니다.
다음 표에서는 테스트 목표에 따라 권장되는 테스트 기간을 보여 줍니다.
| 테스트 유형 | 테스트 지속 시간 | 테스트 목표 |
|---|---|---|
| 빠른 연기 테스트 | 2-3분 | 기본 기능 확인 |
| 성능 기준 | 5~10분 | 신뢰할 수 있는 안정 상태 메트릭 |
| 스트레스 테스트 | 15-30분 | 리소스 고갈 식별 |
| 지구력 테스트 | 1-4시간 | 성능 저하, 대기 시간 안정성 |
중단점 탐색(이진 검색 모드)
점진적 스윕(exploration_mode=gradual)외에도 Notebook은 동시성 수준을 수동으로 지정하지 않고도 지속 가능한 최대 QPS를 찾는 자동 이진 검색 모드를 지원합니다.
작동 방식
설정 exploration_mode=binary_search 및 지정 max_target_qps (예: 500). Notebook은 Little's Law (concurrency = QPS × avg_latency_sec)를 사용하여 각 QPS 대상을 예상 동시성 수준으로 변환한 다음 다음과 같이 이진 검색을 실행합니다.
- (예제의 경우 250)에서
max_target_qps / 2시작합니다. - 오류 비율이 아래
error_rate_threshold(성공)이면 하한을 높이고 더 높은 QPS(375, 500 등)를 시도합니다. - 오류 속도가 임계값(실패)을 초과하는 경우 상한을 낮추고 마지막 성공과 실패 사이의 중간을 시도합니다.
- 최대
exploration_steps단계(기본값 8)에 대해 반복하거나 검색 범위가 5%max_target_qps이내로 좁힐 때까지 반복합니다.
다음 표에서는 약 430 QPS의 중단점이 있는 가상 엔드포인트에 대한 검색이 수렴되는 방법을 보여줍니다.
| Step | 대상 QPS | 오류율 | 결과 | 새 범위 |
|---|---|---|---|---|
| 1 | 250 | 0.1% | 성공 | [250, 500] |
| 2 | 375 | 0.3% | 성공 | [375, 500] |
| 3 | 437 | 4.5% | 실패 | [375, 437] |
| 4 | 406 | 0.8% | 성공 | [406, 437] |
| 5 | 421 | 2.1% | 실패 | [406, 421] |
5~8단계를 거치면 검색이 중단점(이 예제에서는 약 406-421 QPS)에 수렴되며 전체 스윕보다 테스트 실행 수가 훨씬 적습니다.
각 모드를 사용하는 경우
| 모드 | 사용 시기 |
|---|---|
gradual |
예상 작동 범위를 이미 알고 있으며 특정 동시성 수준에서 성능을 특성화하려고 합니다. |
binary_search |
동시성 수준을 미리 알지 못하고 지속 가능한 최대 QPS를 빠르게 찾으려고 합니다. |
3단계. 쿼리 세트 디자인
가능한 경우 쿼리 집합은 예상 프로덕션 트래픽을 가능한 한 가깝게 반영해야 합니다. 특히 콘텐츠, 복잡성 및 다양성 측면에서 쿼리의 예상 분포를 일치시키려고 노력해야 합니다.
실제 쿼리를 사용합니다. "테스트 쿼리 1234"와 같은 임의 텍스트를 사용하지 마세요.
예상 프로덕션 트래픽 분포와 일치합니다. 80개의% 일반적인 쿼리, 15개의% 중간 빈도 쿼리 및 5개의% 자주 발생하는 쿼리가 예상되는 경우 쿼리 집합은 해당 분포를 반영해야 합니다.
프로덕션에서 볼 것으로 예상되는 쿼리 유형과 일치합니다. 예를 들어 프로덕션 쿼리가 하이브리드 검색 또는 필터를 사용할 것으로 예상하는 경우 쿼리 집합에도 사용해야 합니다.
필터를 사용하는 예제 쿼리:
{ "query_text": "wireless headphones", "num_results": 10, "filters": { "brand": "Sony", "noise_canceling": true } }하이브리드 검색을 사용하는 예제 쿼리:
{ "query_text": "best noise canceling headphones for travel", "query_type": "hybrid", "num_results": 10 }
쿼리 다양성 및 캐싱
벡터 검색 엔드포인트는 성능을 향상시키기 위해 여러 유형의 쿼리 결과를 캐시합니다. 이 캐싱은 부하 테스트 결과에 영향을 줄 수 있습니다. 이러한 이유로 쿼리 집합의 다양성에 주의해야 합니다. 예를 들어 동일한 쿼리 집합을 반복적으로 보내는 경우 실제 검색 성능이 아닌 캐시를 테스트합니다.
| 사용: | 언제: | 예시 |
|---|---|---|
| 동일하거나 적은 쿼리 |
|
"추세 항목"을 보여 주는 제품 추천 위젯 - 동일한 쿼리가 시간당 수천 번 실행됩니다. |
| 다양한 쿼리 |
|
모든 사용자가 서로 다른 제품 검색을 입력하는 전자 상거래 검색입니다. |
추가 권장 사항은 모범 사례 요약을 참조하세요.
쿼리 집합을 만들기 위한 옵션
코드 탭에는 다양한 쿼리 집합을 만들기 위한 세 가지 옵션이 표시됩니다. 모든 상황에 딱 맞는 해결책은 없습니다. 가장 적합한 항목을 선택합니다.
- (권장) 인덱스 입력 테이블의 임의 샘플링입니다. 이것은 좋은 일반적인 시작점입니다.
- 프로덕션 로그에서 샘플링합니다. 프로덕션 로그가 있는 경우 좋은 시작입니다. 쿼리는 일반적으로 시간이 지남에 따라 변경되므로 테스트 집합을 정기적으로 새로 고쳐 최신 상태로 유지합니다.
- 가상 쿼리 생성 이 기능은 프로덕션 로그가 없거나 복잡한 필터를 사용하는 경우에 유용합니다.
입력 테이블의 임의 샘플링
다음 코드는 인덱스 입력 테이블의 임의 쿼리를 샘플링합니다.
import pandas as pd
import random
# Read the index input table
input_table = spark.table("catalog.schema.index_input_table").toPandas()
# Sample random rows
n_samples = 1000
if len(input_table) < n_samples:
print(f"Warning: Only {len(input_table)} rows available, using all")
sample_queries = input_table
else:
sample_queries = input_table.sample(n=n_samples, random_state=42)
# Extract the text column (adjust column name as needed)
queries = sample_queries['text_column'].tolist()
# Create query payloads
query_payloads = [{"query_text": q, "num_results": 10} for q in queries]
# Save to input.json
pd.DataFrame(query_payloads).to_json("input.json", orient="records", lines=True)
print(f"Created {len(query_payloads)} diverse queries from index input table")
프로덕션 로그의 샘플
다음 코드는 프로덕션 쿼리에서 비례적으로 샘플링합니다.
# Sample proportionally from production queries
production_queries = pd.read_csv("queries.csv")
# Take stratified sample maintaining frequency distribution
def create_test_set(df, n_queries=1000):
# Group by frequency buckets
df['frequency'] = df.groupby('query_text')['query_text'].transform('count')
# Stratified sample
high_freq = df[df['frequency'] > 100].sample(n=200) # 20%
med_freq = df[df['frequency'].between(10, 100)].sample(n=300) # 30%
low_freq = df[df['frequency'] < 10].sample(n=500) # 50%
return pd.concat([high_freq, med_freq, low_freq])
test_queries = create_test_set(production_queries)
test_queries.to_json("input.json", orient="records", lines=True)
합성 쿼리
프로덕션 로그가 아직 없는 경우 가상의 다양한 쿼리를 생성할 수 있습니다.
# Generate diverse queries programmatically
import random
# Define query templates and variations
templates = [
"find {product} under ${price}",
"best {product} for {use_case}",
"{adjective} {product} recommendations",
"compare {product1} and {product2}",
]
products = ["laptop", "headphones", "monitor", "keyboard", "mouse", "webcam", "speaker"]
prices = ["500", "1000", "1500", "2000"]
use_cases = ["gaming", "work", "travel", "home office", "students"]
adjectives = ["affordable", "premium", "budget", "professional", "portable"]
diverse_queries = []
for _ in range(1000):
template = random.choice(templates)
query = template.format(
product=random.choice(products),
product1=random.choice(products),
product2=random.choice(products),
price=random.choice(prices),
use_case=random.choice(use_cases),
adjective=random.choice(adjectives)
)
diverse_queries.append(query)
print(f"Generated {len(set(diverse_queries))} unique queries")
4단계. 페이로드를 테스트하세요
전체 부하 테스트를 실행하기 전에 페이로드의 유효성을 검사합니다.
- Databricks 작업 영역에서 벡터 검색 엔드포인트로 이동합니다.
- 왼쪽 사이드바에서 [제공]을 클릭합니다.
- 엔드포인트를 선택합니다.
- 사용 → 쿼리를 클릭합니다.
- 쿼리 상자에
input.json콘텐츠를 붙여넣습니다. - 엔드포인트가 예상 결과를 반환했는지 확인합니다.
이렇게 하면 부하 테스트가 오류 응답이 아닌 실제 쿼리를 측정할 수 있습니다.
5단계 부하 테스트 실행
연결 확인 및 준비
부하 테스트를 시작하기 전에 Notebook은 다음 두 가지 설정 단계를 수행합니다.
연결 확인: 서비스 주체 자격 증명을 사용하여 단일 프로브 쿼리를 보냅니다. 엔드포인트가 401 또는 403 오류를 반환하는 경우, 노트북은 전체 부하 테스트를 실행하여 오류 데이터만 생성하는 대신 명확한
PermissionError상태로 즉시 실패합니다. 이렇게 하면 자격 증명 또는 사용 권한이 잘못 구성된 시간이 절약됩니다.준비 테스트 (1분): 엔드포인트 캐시를 준비하고 엔드 투 엔드 요청 흐름의 유효성을 검사하는 짧은 낮은 동시성 테스트를 실행합니다. 준비 결과는 성능 메트릭에 사용되지 않습니다. 이진 검색 모드에서는 준비 지연 시간이 리틀의 법칙에 의한 동시성 예측의 기준으로도 사용됩니다.
주 부하 테스트 시리즈
Notebook은 클라이언트 동시성을 증가시키는 일련의 테스트를 실행합니다.
- 시작: 낮은 동시성(예: 동시 클라이언트 5개)
- 중간: 중간 동시성(예: 10개, 20개 또는 50개 클라이언트)
- 끝: 높은 동시성(예: 100개 이상의 클라이언트)
각 테스트는 구성된 step_duration_seconds 기간 동안 실행됩니다(5-10분 권장).
Notebook이 측정하는 내용
Notebook은 다음을 측정하고 보고합니다.
대기 시간 메트릭:
- P50(중앙값): 쿼리의 절반이 이보다 빠릅니다.
- P95: 95개% 쿼리가 이보다 빠릅니다. 이는 주요 SLA 메트릭입니다.
- P99: 99개% 쿼리가 이보다 빠릅니다.
- 최대: 최악의 경우 대기 시간.
처리량 메트릭:
- RPS(초당 요청 수): 초당 성공한 쿼리 수입니다.
- 총 쿼리 수: 완료된 쿼리 수입니다.
- 성공률: 성공한 쿼리의 백분율입니다.
오류:
- 형식별 쿼리 실패
- 예외 메시지
- 타임아웃 횟수
결과 스토리지
매개 변수가 output_table 설정된 경우 Notebook은 동시성 수준당 한 행(또는 이진 검색 단계당)을 Unity 카탈로그 델타 테이블에 저장합니다. 테이블은 첫 번째 실행에서 자동으로 만들어지고 후속 실행에 추가됩니다. 각 행에는 , run_name동시성, 성공/실패율, 대기 시간 백분위수, RPS 및 이진 검색 관련 필드(exploration_mode, bs_step, bs_target_qps)가 포함bs_outcome됩니다. 이렇게 하면 SQL 또는 BI 도구를 사용하여 시간에 따른 실행을 비교할 수 있습니다.
Databricks 작업으로 실행
모든 Notebook 매개 변수는 Databricks 작업 매개 변수에 직접 매핑되는 것으로 dbutils.widgets정의됩니다. 부하 테스트를 예약하거나 자동화하려면 다음을 수행합니다.
- Notebook을 작업으로 사용하여 작업을 만듭니다.
- 위젯 값을 작업 매개 변수로 설정합니다. 코드 편집이 필요하지 않습니다.
- CPU 코어가 많은 단일 노드 클러스터에 작업을 연결합니다(Locust는 병렬로 수행하는 작업의 이점을 얻습니다).
- 요청 시 또는 되풀이 기준 테스트를 위한 일정에 따라 실행합니다.
6단계 결과 해석
다음 표에서는 우수한 성능을 위한 목표를 보여 줍니다.
| Metric | 목표/타겟 | 주석 |
|---|---|---|
| P95 대기 시간 | < 500ms | 대부분의 쿼리는 빠릅니다. |
| P99 대기 시간 | < 1초 | 긴 꼬리 검색어에서 합리적인 성능 |
| 성공률 | > 99.5% | 낮은 실패율 |
| 시간 경과에 따른 대기 시간 | 안정 | 테스트 중에 성능 저하가 관찰되지 않음 |
| 초당 쿼리 수 | 대상을 충족합니다. | 엔드포인트는 예상 트래픽을 처리할 수 있습니다. |
다음 결과는 성능 저하를 나타냅니다.
- P95 > 1초. 쿼리가 실시간 사용을 위해 너무 느리다는 것을 나타냅니다.
- P99 > 3초. 긴 꼬리 쿼리의 대기 시간은 사용자 환경에 영향을 줍니다.
- 성공률 < 99%. 실패가 너무 많습니다.
- 대기 시간 증가. 리소스 소모 또는 메모리 누수를 나타냅니다.
- 속도 제한 오류(429). 더 높은 엔드포인트 용량이 필요했음을 나타냅니다.
RPS와 대기 시간 간의 절충
최대 RPS는 프로덕션 처리량에 최적 지점이 아닙니다. 최대 처리량에 접근하면 대기 시간이 비선형으로 증가합니다. 최대 RPS에서 작동하면 최대 용량의 60-70% 작동에 비해 대기 시간이 2-5배 더 높은 경우가 많습니다.
다음 예제에서는 결과를 분석하여 최적의 운영 지점을 찾는 방법을 보여줍니다.
- 최대 RPS는 동시 클라이언트 150개에서 480개입니다.
- 최적 운영 지점은 50명의 동시 클라이언트에서 310 RPS(65% 용량)입니다.
- 최대 대기 시간 페널티: P95는 4.3배 높음(1.5s 대 350ms)
- 이 예제에서는 480 RPS 용량의 엔드포인트 크기를 조정하고 최대 310 RPS에서 작동하는 것이 좋습니다.
| Concurrency | P50 | P95 | P99 | RPS | 성공 | Capacity |
|---|---|---|---|---|---|---|
| 5 | 80ms | 120ms | 150ms | 45 | 100% | 10% |
| 10 | 85ms | 140ms | 180ms | 88 | 100% | 20% |
| 20 | 95ms | 180ms | 250밀리초 | 165 | 99.8% | 35% |
| 50 | 150ms | 350ms | 500밀리초 | 310 | 99.2% | 65% ← 스위트 스팟 |
| 100 | 250밀리초 | 800ms | 1.2s | 420 | 97.5% | 90% ⚠️ 최대값 접근 |
| 백오십 | 450ms | 1.5s | 2.5s | 480 | 95.0% | 100% 최대 RPS ❌ |
최대 RPS에서 작동하면 다음과 같은 문제가 발생할 수 있습니다.
- 대기 시간 저하. 이 예제에서 P95는 65% 용량에서 350ms이지만 100% 용량에서 1.5s입니다.
- 트래픽 버스트 또는 급증을 수용할 여지가 없습니다. 용량이 100% 급증하면 시간 제한이 발생합니다. 65%의 용량에서 50% 트래픽 급증도 문제 없이 처리할 수 있습니다.
- 오류 비율이 증가했습니다. 이 예제에서 65% 용량일 때 성공률은 99.2%이며, 100% 용량에서는 95.0%로 5%의 실패율이 있습니다.
- 리소스 소모의 위험. 최대 로드 시, 큐가 증가하고 메모리 압력이 증가하며, 연결 풀이 포화되기 시작하고, 사고 후 복구 시간이 증가합니다.
다음 표에서는 다양한 사용 사례에 권장되는 운영 지점을 보여 줍니다.
| 사용 사례 | 대상 용량 | 근거 |
|---|---|---|
| 지연에 민감한 (검색, 채팅) | 최대치의 50-60% | 낮은 P95/P99 대기 시간 우선 순위 지정 |
| 균형(권장 사항) | 최대치의 60-70% | 비용 및 대기 시간의 적절한 균형 |
| 비용 최적화(일괄 작업) | 최대치의 70-80% | 허용 가능한 더 높은 대기 시간 |
| 권장되지 않음 | > 최대 85% | 대기 시간 급증, 버스트 용량 없음 |
운영 지점 및 엔드포인트 크기를 계산하기 위한 도우미 함수
최적의 지점 찾기
다음 코드는 QPS와 P95 대기 시간을 표시합니다. 그림에서 곡선이 급격히 위쪽으로 구부러지기 시작하는 지점을 찾습니다. 최적 운영 지점입니다.
import matplotlib.pyplot as plt
# Plot QPS vs. P95 latency
qps_values = [45, 88, 165, 310, 420, 480]
p95_latency = [120, 140, 180, 350, 800, 1500]
plt.plot(qps_values, p95_latency, marker='o')
plt.axvline(x=310, color='green', linestyle='--', label='Optimal (65% capacity)')
plt.axvline(x=480, color='red', linestyle='--', label='Maximum (100% capacity)')
plt.xlabel('Queries Per Second (QPS)')
plt.ylabel('P95 Latency (ms)')
plt.title('QPS vs. Latency: Finding the Sweet Spot')
plt.legend()
plt.grid(True)
plt.show()
크기 추천 공식
def calculate_endpoint_size(target_qps, optimal_capacity_percent=0.65):
"""
Calculate required endpoint capacity
Args:
target_qps: Your expected peak production QPS
optimal_capacity_percent: Target utilization (default 65%)
Returns:
Required maximum endpoint QPS
"""
required_max_qps = target_qps / optimal_capacity_percent
# Add 20% safety margin for unexpected bursts
recommended_max_qps = required_max_qps * 1.2
return {
"target_production_qps": target_qps,
"operate_at_capacity": f"{optimal_capacity_percent*100:.0f}%",
"required_max_qps": required_max_qps,
"recommended_max_qps": recommended_max_qps,
"burst_capacity": f"{(1 - optimal_capacity_percent)*100:.0f}% headroom"
}
# Example
result = calculate_endpoint_size(target_qps=200)
print(f"Target production QPS: {result['target_production_qps']}")
print(f"Size endpoint for: {result['recommended_max_qps']:.0f} QPS")
print(f"Operate at: {result['operate_at_capacity']}")
print(f"Available burst capacity: {result['burst_capacity']}")
# Output:
# Target production QPS: 200
# Size endpoint for: 369 QPS
# Operate at: 65%
# Available burst capacity: 35% headroom
임베딩 모델 병목 상태 식별
인덱스가 관리되는 포함을 사용하는 경우, 부하 테스트 노트북은 각 쿼리의 debug_level=1 매개 변수를 통해 구성 요소별 타이밍을 캡처합니다. 결과 테이블에는 다음이 포함됩니다.
-
ann_time— 근사 인접 검색에 소요된 시간 -
embedding_gen_time— 엔드포인트를 제공하는 모델에 쿼리 포함을 생성하는 데 소요된 시간 -
reranker_time— 재순위 지정에 소요된 시간(사용하도록 설정된 경우) -
response_time— 총 엔드 투 엔드 응답 시간
상대적으로 embedding_gen_time 엔드포인트에 비해 ann_time이(가) 지속적으로 큰 경우, 벡터 검색 엔드포인트가 아닌 포함 엔드포인트가 병목 현상입니다. 일반적인 원인:
- 엔드포인트를 제공하는 포함 모델에는 크기 조정을 0으로 설정할 수 있습니다. 프로덕션 부하 테스트에 대해 사용하지 않도록 설정합니다. 프로덕션의 경우 0으로 확장 방지를 참조하세요.
- 포함 엔드포인트에 테스트 중인 쿼리 속도에 대해 프로비전된 동시성이 충분하지 않습니다.
- 포함 모델 엔드포인트는 다른 워크로드와 공유됩니다. 부하 테스트에 전용 엔드포인트를 사용합니다.
팁 (조언)
모델 임베딩 성능과 벡터 검색 성능을 분리하기 위해 부하 테스트에서는 자체 관리형 임베딩으로 전환합니다. 텍스트 쿼리 대신 매개 변수에 EMBEDDING_COLUMN 미리 계산된 벡터를 전달합니다. 이렇게 하면 측정에서 대기 시간 포함이 완전히 제거됩니다.
7단계: 엔드포인트 크기 조정
노트북의 권장 사항 사용
결과를 분석한 후, 노트북에서 다음을 요청합니다.
- 대기 시간 요구 사항을 가장 잘 충족하는 행을 선택합니다.
- 애플리케이션의 원하는 RPS를 입력합니다.
그런 다음 Notebook에 권장 엔드포인트 크기가 표시됩니다. 다음을 기준으로 필요한 용량을 계산합니다.
- 목표 RPS
- 다른 동시성 수준에서 관찰된 대기 시간
- 성공률 임계값
- 안전 마진(일반적으로 예상 최대 부하의 2배)
규모 조정 고려 사항
표준 엔드포인트:
- 인덱스 크기를 지원하도록 자동으로 강화
- 수동으로 확장하여 처리량 지원
- 인덱스가 삭제되면 자동으로 축소
- 용량을 줄이기 위해 수동으로 축소
스토리지 최적화 엔드포인트:
- 인덱스 크기를 지원하도록 자동으로 강화
- 인덱스가 삭제되면 자동으로 축소
8단계: 최종 구성 유효성 검사
엔드포인트 구성을 업데이트한 후:
- 엔드포인트가 준비될 때까지 기다립니다. 이 작업은 몇 분 정도 걸릴 수 있습니다.
- Notebook에서 최종 유효성 검사 테스트를 실행합니다.
- 성능이 요구 사항을 충족하는지 확인합니다.
- RPS ≥ 대상 처리량
- P95 대기 시간이 SLA를 충족합니다.
- 성공률 > 99.5%
- 지속적인 오류 없음
유효성 검사에 실패하면 다음을 시도합니다.
- 엔드포인트 용량 증가
- 쿼리 복잡성 최적화
- 필터 성능 검토
- 임베딩 엔드포인트 구성 확인
다시 테스트해야 하는 경우
성능 가시성을 유지하려면 분기별로 기준 부하 테스트를 실행하는 것이 좋습니다. 다음 변경 내용을 수행할 때도 다시 테스트해야 합니다.
- 쿼리 패턴 또는 복잡성 변경
- 벡터 검색 인덱스 업데이트
- 필터 구성 수정
- 상당한 트래픽 증가 예상
- 새 기능 또는 최적화 배포
- 표준에서 스토리지 최적화 엔드포인트 유형으로 변경
Troubleshooting
최대 10ms 대기 시간 및 240 바이트 응답으로 모든 요청이 실패합니다.
이는 서비스 주체가 401/403 응답을 받고 있음을 나타냅니다. 확인:
- 서비스 주체에는 인덱스뿐만 아니라 벡터 검색 엔드포인트에 대한 Can Query 권한이 있습니다.
- 비밀 범위에는 유효한
service_principal_client_id키와service_principal_client_secret키가 포함됩니다. - OAuth 비밀이 만료되지 않았습니다.
Notebook에는 전체 부하 테스트를 실행하기 전에 문제를 식별하는 연결 검사가 포함되어 있습니다.
동일한 클러스터에서 여러 부하 테스트 작업 실행
동일한 클러스터에서 두 부하 테스트 작업을 동시에 실행하는 경우 한 작업은 부실한 OAuth 토큰을 받거나 다른 작업의 Locust 작업자와 CPU 경합이 발생할 수 있습니다. 신뢰할 수 있는 결과를 얻으려면 전용 클러스터 에서 한 번에 하나씩 부하 테스트 작업을 실행합니다.
구성 요소 타이밍 그래프가 비어 있음
구성 요소 타이밍 그래프(ann_time, embedding_gen_time, reranker_time)는 엔드포인트가 쿼리 응답에서 반환 debug_info 되어야 합니다. 이러한 그래프가 비어 있는 경우:
- 스크립트가 응답에서
fast_vs_load_test_async_load.py을(를) 구문 분석하는debug_info를locust_script_path로 사용하는지 확인합니다. - 일부 엔드포인트 구성은
debug_info를 반환하지 않을 수 있습니다. 자체 관리형 임베딩 인덱스는 일반적으로ann_time및response_time를 반환하지만embedding_gen_time나reranker_time는 반환하지 않습니다.
SQL 웨어하우스에서 쿼리할 수 없는 결과 테이블
Notebook은 클러스터의 Spark 세션에서 결과를 기록합니다. SQL 웨어하우스에서 Notebook이 채워진 것으로 보고하는 테이블에 대해 0개의 행을 표시하는 경우 문제는 Unity 카탈로그 메타데이터 동기화 지연일 수 있습니다. 몇 분 정도 기다렸다가 다시 시도하거나 동일한 클러스터에 연결된 Notebook에서 직접 테이블을 쿼리합니다.
모범 사례 요약
테스트 구성
최대 부하 시 최소 5분 동안 테스트를 실행합니다.
인증에 OAuth 서비스 주체를 사용합니다.
예상 프로덕션 쿼리와 일치하는 실제 쿼리 페이로드를 만듭니다.
프로덕션과 유사한 필터 및 매개 변수를 사용하여 테스트합니다.
측정하기 전에 준비 기간을 포함합니다.
여러 동시성 수준에서 테스트합니다.
평균뿐만 아니라 P95/P99 대기 시간을 추적합니다.
캐시된 성능과 캐시되지 않은 성능을 모두 테스트합니다.
# Conservative approach: Size endpoint for UNCACHED performance uncached_results = run_load_test(diverse_queries, duration=600) endpoint_size = calculate_capacity(uncached_results, target_rps=500) # Then verify cached performance is even better cached_results = run_load_test(repetitive_queries, duration=300) print(f"Cached P95: {cached_results['p95']}ms (bonus performance)")
쿼리 집합 디자인
- 테스트 쿼리 다양성을 실제 트래픽 분산(빈번하고 드문 쿼리)과 일치합니다.
- 로그의 실제 쿼리를 사용합니다(익명화됨).
- 다른 쿼리 복잡성을 포함합니다.
- 캐시된 시나리오와 캐시되지 않은 시나리오를 모두 테스트하고 결과를 별도로 추적합니다.
- 예상 필터 조합으로 테스트합니다.
- 프로덕션 환경에서 사용할 것과 동일한 매개 변수를 사용합니다. 예를 들어 프로덕션 환경에서 하이브리드 검색을 사용하는 경우 하이브리드 검색 쿼리를 포함합니다. 프로덕션에서와 유사한
num_results매개 변수를 사용합니다. - 프로덕션 환경에서는 발생하지 않는 쿼리를 사용하지 마세요.
성능 최적화
대기 시간이 너무 높은 경우 다음을 시도합니다.
- OAuth 서비스 주체 사용(PAT 아님) - 100ms 개선
- 감소
num_results- 100 결과 가져오기가 10보다 느립니다. - 필터 최적화 - 복잡하거나 지나치게 제한적인 필터로 인해 쿼리 속도가 느려집니다.
- 포함 엔드포인트 확인 - 크기가 0으로 조정되지 않았거나 대역폭이 충분한지 확인합니다.
속도 제한에 도달한 경우 다음을 시도합니다.
- 엔드포인트 용량 증가 - 엔드포인트 확장
- 시간에 따라 클라이언트 쪽 속도 제한 또는 분산 쿼리 구현
- 연결 풀링 사용 - 연결 다시 사용
- 재시도 논리 추가 - 지수 백오프 사용(이미 Python SDK의 일부)