ベクトル検索エンドポイントの負荷テストを構成する
このページでは、負荷テストの連続検索エンドポイントのためのガイダンス、サンプル コード、およびサンプル ノートブックを提供します。 負荷テストは、本番運用にデプロイする前に、一斉検索エンドポイントのパフォーマンスと本番運用の準備状況を理解するのに役立ちます。 負荷テストでは次のことがわかります。
- 異なるスケーリングレベルでのレイテンシ
- スループットの制限とボトルネック(1秒あたりのリクエスト数、レイテンシの内訳)
- 持続負荷時のエラー率
- リソースの活用とキャパシティプランニング
ロード テストと関連する概念の詳細については、サービス エンドポイントのロード テストを参照してください。
要件
これらのステップを開始する前に、展開済み検索エンドポイントと、エンドポイント上で Can Query 権限を持つサービスプリンシパルが必要です。 「ステップ 1: サービスプリンシパル認証を設定する」を参照してください。
次のファイルとサンプルノートブックのコピーをダウンロードして、Databricks ワークスペースにインポートします。
- 入力JSON 。 これは、すべての並列接続によってエンドポイントに送信されるペイロードを指定する
input.jsonファイルの例です。 必要に応じて複数のファイルを作成できます。サンプルノートブックを使用する場合、このファイルは提供された入力テーブルから自動的に生成されます。 - fast_vs_load_test_async_load.py 。このスクリプトは、サンプルノートブックによって認証とペイロード処理に使用されます。
- 次のサンプルノートブックは、負荷テストを実行します。最高のパフォーマンスを得るには、多数のコアと大容量メモリを備えたクラスターでこのノートブックを実行してください。埋め込みはメモリを大量に消費することが多いため、事前に生成された埋め込みを含むクエリにはメモリが必要です。
サンプルノートブックとクイックスタート
開始するには、次のサンプルノートブックを使用してください。これには、負荷テストを実行するためのすべてのステップが含まれています。 Databricksシークレット、エンドポイント名など、いくつかの懸念を入力する必要があります。
Locust 負荷テストノートブック
負荷テストフレームワーク: Locust
Locust は、次のことを可能にするオープンソースの負荷テスト フレームワークです。
- 並列クライアント接続の数を変える
- 接続の発生速度を制御する
- テスト全体を通じてエンドポイントのパフォーマンスを測定する
- 利用可能なすべてのCPUコアを自動検出して使用する
サンプルノートブックは、 --processes -1フラグを使用して CPU コアを自動検出し、それらを完全に利用します。
Locust が CPU によってボトルネックになっている場合は、出力にメッセージが表示されます。
ステップ 1: サービスプリンシパル認証を設定する
本番運用のようなパフォーマンス テストの場合は、常にOAuthサービス プリンシパル認証を使用してください。 サービスシプリンパルは、パーソナル アクセス オンライン (PAT) と比較して、最大 100 ミリ秒速い応答時間と高い要求レート制限を提供します。
サービスプリンシパルの作成と構成
-
Databricksサービス プリンシパルを作成します。 手順については、 「サービスプリンシパルをアカウントに追加する」を参照してください。
-
権限を付与する:
- 「検索」エンドポイント ページに移動します。
- [ アクセス許可 ]をクリックします。
- サービスプリンシパル Can Query 権限を付与します。
-
OAuth シークレットを作成します。
- サービスプリンシパルの詳細ページへ移動します。
- [ シークレット ] タブをクリックします。
- [シークレットの生成] をクリックします。
- 有効期間を設定します (長期テストの場合は 365 日を推奨)。
- クライアント ID とシークレットの両方をすぐにコピーします 。
-
資格情報を安全に保存します。
- Databricksシークレットスコープを作成します。 手順については、 「チュートリアル: Databricks シークレットの作成と使用」を参照してください。
- 次のコード例に示すように、サービスプリンシパル クライアント ID を
service_principal_client_idとして保存し、 OAuthシークレットをservice_principal_client_secretとして保存します。
Python# 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: 負荷テストを構成する
ノートブックの構成
サンプル ノートブックのコピーで、次の設定を行います。
パラメーター | 説明 | 推奨値 |
|---|---|---|
| あなたの検索エンドポイントの名前 | エンドポイント名 |
| 完全なインデックス名 ( | インデックス名 |
| 各負荷テストの所要時間 | 300~600秒(5~10分) |
| CSV出力ファイルのプレフィックス |
|
| Databricksシークレットスコープの名前 | スコープ名 |
なぜ5〜10分なのでしょうか?
最小 5 分のテスト期間が重要です。
- 初期クエリにはコールド スタートのオーバーヘッドが含まれる場合があります。
- エンドポイントが安定したパフォーマンスに到達するには時間が必要です。
- モデルサービング エンドポイントの自動スケーリング (有効な場合) がアクティブになるまでに時間がかかります。
- 短いテストでは、持続的な負荷の下での調整動作を見逃してしまいます。
次の表は、テストの目標に応じて推奨されるテスト期間を示しています。
テストの種類 | テスト期間 | テストの目的 |
|---|---|---|
クイックスモークテスト | 2~3分 | 基本的な機能を確認する |
パフォーマンスベースライン | 5~10分 | 信頼性の高い定常状態メトリクス |
ストレステスト | 15~30分 | リソース枯渇を特定する |
耐久テスト | 1~4時間 | 劣化、レイテンシーの安定性 |
ステップ 3. クエリセットを設計する
可能であれば、クエリ セットは、予想される本番運用トラフィックをできるだけ正確に反映する必要があります。 具体的には、コンテンツ、複雑さ、多様性の観点から、クエリの予想される分布に一致するようにする必要があります。
-
現実的なクエリを使用します。「テスト クエリ 1234」などのランダムなテキストを使用しないでください。
-
予想される本番運用のトラフィック分布と一致させます。 一般的なクエリが 80%、中頻度のクエリが 15%、頻度の低いクエリが 5% になると予想される場合は、クエリ セットはその分布を反映する必要があります。
-
本番運用で期待されるクエリの種類と一致します。 たとえば、本番運用クエリでハイブリッド検索またはフィルターが使用されることが予想される場合は、クエリ セットでもそれらを使用する必要があります。
フィルターを使用したクエリの例:
JSON{
"query_text": "wireless headphones",
"num_results": 10,
"filters": { "brand": "Sony", "noise_canceling": true }
}ハイブリッド検索を使用したクエリの例:
JSON{
"query_text": "best noise canceling headphones for travel",
"query_type": "hybrid",
"num_results": 10
}
クエリの多様性とキャッシュ
トレンド検索エンドポイントは、パフォーマンスを向上させるために数種類のクエリ結果をキャッシュします。 このキャッシュは負荷テストの結果に影響を与える可能性があります。このため、クエリ セットの多様性に注意することが重要です。たとえば、同じクエリ セットを繰り返し送信する場合は、実際の検索パフォーマンスではなく、キャッシュをテストしていることになります。
使用: | いつ: | 例 |
|---|---|---|
同一または少数のクエリ |
| 「トレンドアイテム」を表示する製品推奨ウィジェット - 同じクエリが 1 時間あたり数千回実行されます。 |
多様なクエリ |
| ユーザーごとに異なる製品検索を入力する電子商取引検索。 |
追加の推奨事項については、 「ベスト プラクティスの概要」を参照してください。
クエリセットを作成するためのオプション
コード タブには、多様なクエリ セットを作成するための 3 つのオプションが表示されます。すべての人に当てはまるものは存在しません。あなたにとって最適なものを選択してください。
- (推奨) インデックス入力テーブルからのランダム サンプリング。これは一般的な出発点としては良いでしょう。
- 本番運用ログからのサンプリング。 本番運用ログがある場合は、これが良いスタートとなります。 クエリは通常、時間の経過とともに変化するため、テスト セットを定期的に更新して最新の状態に保つようにしてください。
- 合成クエリを生成します。これは、本番運用ログがない場合や複雑なフィルターを使用している場合に便利です。
- Random sampling from input table
- Sample from production logs
- Synthetic queries
次のコードは、インデックス入力テーブルからランダムなクエリをサンプリングします。
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. 負荷テストを実行する
初期ウォームアップテスト(30秒)
ノートブックは最初に次の操作を実行する 30 秒間のテストを実行します。
- エンドポイントがオンラインで応答していることを確認します
- キャッシュをウォームアップする
- 認証を検証する
このウォームアップ テストの結果にはコールド スタートのオーバーヘッドが含まれるため、メトリクスのパフォーマンスには使用しないでください。
主な負荷試験シリーズ
ノートブックは、クライアントの同時実行性を高めながら一連のテストを実行します。
- 開始: 同時実行性が低い (たとえば、5 台のクライアント)
- 中: 中程度の同時実行性 (たとえば、10、20、または 50 クライアント)
- 終了: 高い同時実行性 (たとえば、100 を超えるクライアント)
各テストは構成されたlocust_run_timeの間実行されます (5 ~ 10 分を推奨)。
ノートブックの測定対象
ノートブックは次のことを測定して報告します。
レイテンシメトリクス:
- P50 (中央値): クエリの半分はこれより高速です。
- P95: クエリの 95% はこれよりも高速です。これが重要なSLAです。
- P99: クエリの 99% はこれよりも高速です。
- 最大: 最悪のケースの遅延。
スループット メトリクス:
- RPS (1 秒あたりのリクエスト数): 1 秒あたりの成功したクエリ数。
- 合計クエリ数: 完了したクエリの数。
- 成功率: 成功したクエリの割合。
エラー:
- クエリ失敗の種類
- 例外メッセージ
- タイムアウトカウント
ステップ 6. 結果を解釈する
次の表は、良好なパフォーマンスの目標を示しています。
メトリクス | ターゲット | Comment |
|---|---|---|
P95 レイテンシ | < 500ミリ秒 | ほとんどのクエリは高速です |
P99レイテンシ | < 1秒 | ロングテールクエリでの適切なパフォーマンス |
成功率 | > 99.5% | 低い故障率 |
時間の経過に伴うレイテンシ | 安定した | 試験中に劣化は観察されなかった |
1 秒あたりのクエリー | 目標を達成 | エンドポイントは予想されるトラフィックを処理できる |
次の結果はパフォーマンスが低いことを示しています。
- P95 > 1秒。クエリがリアルタイムで使用するには遅すぎることを示します。
- P99 > 3秒。ロングテールクエリのレイテンシはユーザーエクスペリエンスを低下させます。
- 成功率 <99%。失敗が多すぎます。
- レイテンシの増加。リソースの枯渇またはメモリ リークを示します。
- レート制限エラー(429)。より高いエンドポイント容量が必要であることを示します。
RPSとレイテンシのトレードオフ
最大 RPS は本番運用スループットの最適点ではありません。 最大スループットに近づくにつれて、レイテンシは非線形に増加します。最大 RPS で動作させると、最大容量の 60 ~ 70% で動作させる場合と比べて、レイテンシが 2 ~ 5 倍高くなることがよくあります。
次の例は、結果を分析して最適な動作点を見つける方法を示しています。
- 最大 RPS は、150 並列クライアントで 480 です。
- 最適な動作点は、50 並列クライアント (65% の容量) で 310 RPS です。
- 最大P95でのレイテンシペナルティは4.3倍(1.5秒対350ミリ秒)
- この例では、エンドポイントを 480 RPS の容量に合わせてサイズ設定し、約 310 RPS で動作させることが推奨されます。
同時実行 | P50 | P95 | P99 | RPS | 成功 | 容量 |
|---|---|---|---|---|---|---|
5 | 80ミリ秒 | 120ミリ秒 | 150ミリ秒 | 45 | 100% | 10% |
10 | 85ミリ秒 | 140ミリ秒 | 180ミリ秒 | 88 | 100% | 20% |
20 | 95ミリ秒 | 180ミリ秒 | 250ミリ秒 | 165 | 99.8% | 35% |
50 | 150ミリ秒 | 350ミリ秒 | 500ミリ秒 | 310 | 99.2% | 65% ← スイートスポット |
100 | 250ミリ秒 | 800ミリ秒 | 1.2秒 | 420 | 97.5% | 90% ⚠️ 最大値に近づいています |
150 | 450ミリ秒 | 1.5秒 | 2.5秒 | 480 | 95.0% | 100% ❌ 最大RPS |
最大 RPS で動作すると、次の問題が発生する可能性があります。
- レイテンシの低下。この例では、P95 は容量 65% では 350 ミリ秒ですが、容量 100% では 1.5 秒になります。
- トラフィックのバーストや急増に対応する余裕がありません。容量が 100% の場合、スパイクが発生するとタイムアウトが発生します。容量が 65% の場合、トラフィックの 50% の急増も問題なく処理できます。
- エラー率の増加。この例では、65% の容量で成功率は 99.2% ですが、100% の容量では 95.0% (失敗率は 5%) になります。
- リソース枯渇のリスク。最大負荷になると、キューが増加し、メモリ負荷が増大し、接続プールが飽和状態になり始め、インシデント発生後の回復時間が長くなります。
次の表は、さまざまなユースケースに推奨される動作ポイントを示しています。
ユースケース | 目標容量 | 根拠 |
|---|---|---|
遅延に敏感(検索、チャット) | 最大の50~60% | P95/P99の低レイテンシを優先する |
バランス(推奨) | 最大の60~70% | コストとレイテンシーのバランスが良い |
コスト最適化(バッチジョブ) | 最大値の70~80% | 許容できる高いレイテンシー |
推奨されません | 最大値の85%以上 | レイテンシの急上昇、バースト容量なし |
動作点とエンドポイントのサイズを計算するためのヘルパー関数
- Find the optimal point
- Size recommendation formula
次のコードは、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
ステップ 7: エンドポイントのサイズを設定する
ノートブックの推奨事項を使用する
結果を分析した後、ノートブックは次のことを要求します。
- レイテンシ要件に最も適した行を選択します。
- アプリケーションの希望する RPS を入力します。
ノートブックには推奨されるエンドポイント サイズが表示されます。必要な容量は、以下に基づいて計算されます。
- 目標RPS
- 異なる同時実行レベルで観測されたレイテンシ
- 成功率の閾値
- 安全マージン(通常、予想されるピーク負荷の2倍)
スケーリングの考慮事項
標準エンドポイント:
- インデックスサイズをサポートするために自動的にスケールアップ
- スループットをサポートするために手動でスケールアップする
- インデックスが削除されると自動的にスケールダウンする
- 手動でスケールダウンして容量を減らす
ストレージ最適化エンドポイント:
- インデックスサイズをサポートするために自動的にスケールアップ
- インデックスが削除されると自動的にスケールダウンする
ステップ 8: 最終構成を検証する
エンドポイント構成を更新した後:
- エンドポイントの準備が整うまで待ちます。これには数分かかる場合があります。
- ノートブックで最終検証テストを実行します。
- パフォーマンスが要件を満たしていることを確認します。
- RPS ≥ 目標スループット
- P95レイテンシはSLAを満たす
- 成功率 > 99.5%
- 持続的なエラーなし
検証に失敗した場合は、次の操作を試してください。
- エンドポイント容量の増加
- クエリの複雑さを最適化する
- フィルターのパフォーマンスを確認する
- 埋め込みエンドポイントの構成を確認する
再テストのタイミング
パフォーマンスの可視性を維持するために、ベースライン負荷テストを四半期ごとに実行することをお勧めします。次のいずれかの変更を行った場合も、再テストを行う必要があります。
- クエリパターンまたは複雑さを変更する
- トレンド検索インデックスを更新する
- フィルタ設定を変更する
- 大幅なトラフィック増加が見込まれる
- 新機能や最適化を導入する
- 標準エンドポイントタイプからストレージ最適化エンドポイントタイプへの変更
ベストプラクティスの概要
テスト構成
-
ピーク負荷時に少なくとも 5 分間テストを実行します。
-
認証にはOAuthサービスプリンシパルを使用します。
-
予想される本番運用クエリに一致する現実的なクエリ ペイロードを作成します。
-
本番運用のようなフィルターと問題を使用してテストします。
-
測定前にウォームアップ期間を設けてください。
-
複数の同時実行レベルでテストします。
-
平均値だけでなく、P95/P99 レイテンシも追跡します。
-
キャッシュされたパフォーマンスとキャッシュされていないパフォーマンスの両方をテストします。
Python# 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 ではない) - 100 ミリ秒の改善
num_resultsを減らす - 100 件の結果を取得するのは 10 件よりも遅い- フィルターの最適化 - 複雑または過度に制限的なフィルターはクエリの速度を低下させます
- 埋め込みエンドポイントを確認する - ゼロにスケールされていないか、十分な帯域幅があるか確認する
レート制限に達した場合は、次の操作を試してください。
- エンドポイントの容量を増やす - エンドポイントをスケールアップする
- クライアント側のレート制限や時間内でのクエリ分散を実装する
- 接続プールを使用する - 接続を再利用する
- 再試行ロジックを追加 - 指数バックオフを使用する (すでに Python SDK の一部)