RAG やセマンティック検索の需要が高まるにつれ、ベクトル検索システムはしばしば 2 つの一般的な課題に直面します:
マルチテナント分離:多くの企業顧客にナレッジベースを提供する SaaS プロバイダーや、部門ごとに個別のナレッジベースを持つ企業では、厳格なデータ分離が求められます。
超大規模データ:単一のインデックスに数千万から数億のベクトルが含まれる場合、検索レイテンシーが大幅に増加し、リアルタイム要件を満たすことが困難になります。
OSS Vectors を使用すると、同じアカウントとリージョン内に多数のベクターインデックスを作成できます。マルチインデックスアーキテクチャを使用することで、テナントまたはビジネスディメンションごとにデータをパーティション分割し、データ分離と高性能な検索の両方を実現できます。

マルチインデックスアーキテクチャの利点
データ分離:異なるテナントまたはビジネスユニットのデータは個別のインデックスに保存されるため、テナント間のデータ漏洩を防ぎます。
検索の高速化:大規模なインデックスを複数の小さなインデックスに分割することで、各検索の範囲が狭まります。複数のインデックスを並列検索し、その結果をマージすることで、全体的な応答時間を大幅に短縮できます。
柔軟な運用:各インデックスに対して、ディメンション、モデル、類似性アルゴリズムを個別に設定できます。特定のテナントのデータを削除するには、レコードを 1 つずつフィルタリングして削除する代わりに、対応するインデックスを削除するだけです。
CLI を使用したテナントごとのデータインポート
oss-vectors-embed CLI ツールを使用すると、特定のインデックスにファイルを書き込むことができ、テナントやビジネスディメンションごとにターゲットを絞ったデータインポートが可能になります。
インストール手順については、「OSS Vectors Embed CLI ツールを使用したベクトルデータの書き込みとクエリ」をご参照ください。
開始する前に、次の要件を満たしていることを確認してください:
OSS_ACCESS_KEY_ID、OSS_ACCESS_KEY_SECRET、DASHSCOPE_API_KEYの環境変数が設定されていること。各テナントに対応するベクターバケットとベクターインデックスが作成されていること。
次の例のプレースホルダーを実際の値に置き換えてください:
プレースホルダー | 説明 |
| Alibaba Cloud アカウント ID |
| ベクターバケットの名前 |
テナントごとのデータ書き込み
異なるテナントのデータをそれぞれのインデックスに書き込むことで、データ分離を確保します。
# テナント A のドキュメントをテナント A のインデックスに書き込みます
oss-vectors-embed \
--account-id "<your-account-id>" \
--vectors-region cn-hangzhou \
put \
--vector-bucket-name "<your-vector-bucket>" \
--index-name "tenantcompanya" \
--model-id text-embedding-v4 \
--text-value "Knowledge base content for Tenant A" \
--key "doc_001" \
--metadata '{"tenant": "company_a", "category": "faq"}'
# テナント B のドキュメントをテナント B のインデックスに書き込みます
oss-vectors-embed \
--account-id "<your-account-id>" \
--vectors-region cn-hangzhou \
put \
--vector-bucket-name "<your-vector-bucket>" \
--index-name "tenantcompanyb" \
--model-id text-embedding-v4 \
--text-value "Knowledge base content for Tenant B" \
--key "doc_001" \
--metadata '{"tenant": "company_b", "category": "manual"}'テナントを対象とした検索
対象のテナントのインデックスのみをクエリすることで、データ分離が保証されます。
# テナント A のインデックスのみを検索します
oss-vectors-embed \
--account-id "<your-account-id>" \
--vectors-region cn-hangzhou \
query \
--vector-bucket-name "<your-vector-bucket>" \
--index-name "tenantcompanya" \
--model-id text-embedding-v4 \
--text-value "frequently asked questions" \
--top-k 5 \
--return-metadataSDK を使用したマルチインデックスアーキテクチャの構築
Python SDK
開始する前に、alibabacloud-oss-v2 SDK をインストールします:
pip install alibabacloud-oss-v2OSS_ACCESS_KEY_ID と OSS_ACCESS_KEY_SECRET の環境変数が設定されていることを確認してください。
マルチテナントインデックスの作成
テナント ID をサフィックスとしてインデックス名を付け、専用のベクターインデックスをバッチで作成します。
import alibabacloud_oss_v2 as oss
import alibabacloud_oss_v2.vectors as oss_vectors
ACCOUNT_ID = "<your-account-id>"
REGION = "cn-hangzhou"
BUCKET = "<your-vector-bucket>"
def create_vector_client():
credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
cfg = oss.config.load_default()
cfg.credentials_provider = credentials_provider
cfg.region = REGION
cfg.account_id = ACCOUNT_ID
return oss_vectors.Client(cfg)
client = create_vector_client()
# テナントのインデックスをバッチで作成します
tenant_ids = ["companya", "companyb", "companyc"]
for tenant_id in tenant_ids:
index_name = f"tenant{tenant_id}"
result = client.put_vector_index(oss_vectors.models.PutVectorIndexRequest(
bucket=BUCKET,
index_name=index_name,
dimension=1024,
data_type="float32",
distance_metric="cosine",
))
print(f"Index {index_name} created, status_code={result.status_code}")出力:
Index tenantcompanya created, status_code=200
Index tenantcompanyb created, status_code=200
Index tenantcompanyc created, status_code=200テナントごとのデータ書き込み
異なるテナントのデータをそれぞれのインデックスに書き込みます。
import alibabacloud_oss_v2 as oss
import alibabacloud_oss_v2.vectors as oss_vectors
ACCOUNT_ID = "<your-account-id>"
REGION = "cn-hangzhou"
BUCKET = "<your-vector-bucket>"
def create_vector_client():
credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
cfg = oss.config.load_default()
cfg.credentials_provider = credentials_provider
cfg.region = REGION
cfg.account_id = ACCOUNT_ID
return oss_vectors.Client(cfg)
client = create_vector_client()
# テナント A のインデックスにデータを書き込みます
result = client.put_vectors(oss_vectors.models.PutVectorsRequest(
bucket=BUCKET,
index_name="tenantcompanya",
vectors=[
{
"key": "faq_001",
"data": {"float32": [0.1] * 1024}, # ベクトルディメンションはインデックスと一致する必要があります。
"metadata": {"tenant": "company_a", "category": "faq"}
}
]
))
print(f"Tenant A write completed, status_code={result.status_code}")
# テナント B のインデックスにデータを書き込みます
result = client.put_vectors(oss_vectors.models.PutVectorsRequest(
bucket=BUCKET,
index_name="tenantcompanyb",
vectors=[
{
"key": "manual_001",
"data": {"float32": [0.2] * 1024}, # ベクトルディメンションはインデックスと一致する必要があります。
"metadata": {"tenant": "company_b", "category": "manual"}
}
]
))
print(f"Tenant B write completed, status_code={result.status_code}")出力:
Tenant A write completed, status_code=200
Tenant B write completed, status_code=200並列検索とマージ
大規模なインデックスを小さなインデックスに分割した後、インデックスを並列検索し、結果をマージしてソートすることで、全体的な応答時間を短縮できます。
from concurrent.futures import ThreadPoolExecutor, as_completed
import alibabacloud_oss_v2 as oss
import alibabacloud_oss_v2.vectors as oss_vectors
ACCOUNT_ID = "<your-account-id>"
REGION = "cn-hangzhou"
BUCKET = "<your-vector-bucket>"
def create_vector_client():
credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
cfg = oss.config.load_default()
cfg.credentials_provider = credentials_provider
cfg.region = REGION
cfg.account_id = ACCOUNT_ID
return oss_vectors.Client(cfg)
def search_index(client, index_name, query_vector, top_k=10):
"""単一のインデックスを検索します。"""
result = client.query_vectors(oss_vectors.models.QueryVectorsRequest(
bucket=BUCKET,
index_name=index_name,
query_vector=query_vector,
return_metadata=True,
return_distance=True,
top_k=top_k,
))
return {
"index": index_name,
"status_code": result.status_code,
"vectors": result.vectors or [ ],
}
def parallel_search(index_names, query_vector, top_k=10):
"""複数のインデックスを並列で検索し、結果をマージします。"""
client = create_vector_client()
all_vectors = [ ]
with ThreadPoolExecutor(max_workers=len(index_names)) as executor:
futures = {
executor.submit(search_index, client, idx, query_vector, top_k): idx
for idx in index_names
}
for future in as_completed(futures):
result = future.result()
print(f"Index {result['index']} returned {len(result['vectors'])} results")
all_vectors.extend(result["vectors"])
# 距離の昇順 (距離が小さいほど類似度が高い) でソートし、グローバルトップ K の結果を取得します。
all_vectors.sort(key=lambda v: v.get("distance", float("inf")))
return all_vectors[:top_k]
# 3 つの分割されたインデックスを並列で検索します。
indices = ["tenantcompanya", "tenantcompanyb", "tenantcompanyc"]
query_vec = {"float32": [0.1] * 1024} # ベクトルディメンションはインデックスと一致する必要があります。
results = parallel_search(indices, query_vec, top_k=5)
print(f"\nGlobal Top-2 after merging:")
for v in results:
print(f" key={v.get('key')}, distance={v.get('distance')}, metadata={v.get('metadata')}")出力:
Index tenantcompanya returned 1 result
Index tenantcompanyb returned 1 result
Index tenantcompanyc returned 0 results
Global Top-2 after merging:
key=faq_001, distance=0.0, metadata={'tenant': 'company_a', 'category': 'faq'}
key=manual_001, distance=0.19999998807907104, metadata={'tenant': 'company_b', 'category': 'manual'}注意:複数のインデックスにわたる並列検索の後、結果はクライアント側で距離に基づいてマージおよびソートされます。より高い精度を得るために、Rerank モデルを導入してセカンダリランキングを行うことができます。
Go SDK
開始する前に、alibabacloud-oss-go-sdk-v2 SDK をインストールします:
go get github.com/aliyun/alibabacloud-oss-go-sdk-v2OSS_ACCESS_KEY_ID と OSS_ACCESS_KEY_SECRET の環境変数が設定されていることを確認してください。
マルチテナントインデックスの作成
package main
import (
"context"
"fmt"
"log"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/vectors"
)
const (
region = "cn-hangzhou"
bucketName = "<your-vector-bucket>"
accountId = "<your-account-id>"
)
func main() {
cfg := oss.LoadDefaultConfig().
WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
WithRegion(region).
WithAccountId(accountId)
client := vectors.NewVectorsClient(cfg)
// テナントのインデックスをバッチで作成します。
tenantIDs := [ ]string{"companya", "companyb", "companyc"}
for _, tenantID := range tenantIDs {
indexName := fmt.Sprintf("tenant%s", tenantID)
result, err := client.PutVectorIndex(context.TODO(), &vectors.PutVectorIndexRequest{
Bucket: oss.Ptr(bucketName),
IndexName: oss.Ptr(indexName),
Dimension: oss.Ptr(1024),
DataType: oss.Ptr("float32"),
DistanceMetric: oss.Ptr("cosine"),
})
if err != nil {
log.Printf("Index %s creation failed: %v", indexName, err)
continue
}
fmt.Printf("Index %s created, status_code=%d\n", indexName, result.StatusCode)
}
}出力:
Index tenantcompanya created, status_code=200
Index tenantcompanyb created, status_code=200
Index tenantcompanyc created, status_code=200並列検索とマージ
package main
import (
"context"
"fmt"
"log"
"sort"
"sync"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/vectors"
)
const (
region = "cn-hangzhou"
bucketName = "<your-vector-bucket>"
accountId = "<your-account-id>"
dimension = 1024
)
func makeVector(val float32, dim int) [ ]float32 {
v := make([ ]float32, dim)
for i := range v {
v[i] = val
}
return v
}
func main() {
cfg := oss.LoadDefaultConfig().
WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
WithRegion(region).
WithAccountId(accountId)
client := vectors.NewVectorsClient(cfg)
indices := [ ]string{"tenantcompanya", "tenantcompanyb", "tenantcompanyc"}
queryVector := map[string]any{"float32": makeVector(0.1, dimension)}
var mu sync.Mutex
var allVectors [ ]map[string]any
var wg sync.WaitGroup
for _, indexName := range indices {
wg.Add(1)
go func(idx string) {
defer wg.Done()
result, err := client.QueryVectors(context.TODO(), &vectors.QueryVectorsRequest{
Bucket: oss.Ptr(bucketName),
IndexName: oss.Ptr(idx),
QueryVector: queryVector,
ReturnMetadata: oss.Ptr(true),
ReturnDistance: oss.Ptr(true),
TopK: oss.Ptr(10),
})
if err != nil {
log.Printf("Index %s query failed: %v", idx, err)
return
}
fmt.Printf("Index %s returned %d results\n", idx, len(result.Vectors))
mu.Lock()
allVectors = append(allVectors, result.Vectors...)
mu.Unlock()
}(indexName)
}
wg.Wait()
// 距離の昇順でソートし、グローバルトップ K の結果を取得します。
sort.Slice(allVectors, func(i, j int) bool {
di, _ := allVectors[i]["distance"].(float64)
dj, _ := allVectors[j]["distance"].(float64)
return di < dj
})
topK := 5
if len(allVectors) < topK {
topK = len(allVectors)
}
fmt.Printf("\nGlobal Top-%d after merging:\n", topK)
for _, v := range allVectors[:topK] {
fmt.Printf(" key=%v, distance=%v, metadata=%v\n", v["key"], v["distance"], v["metadata"])
}
}出力:
Index tenantcompanya returned 1 result
Index tenantcompanyc returned 0 results
Index tenantcompanyb returned 1 result
Global Top-2 after merging:
key=faq_001, distance=0, metadata=map[category:faq tenant:company_a]
key=manual_001, distance=0.19999998807907104, metadata=map[category:manual tenant:company_b]ベストプラクティス
インデックスの命名規則:インデックス名には、
tenant{tenantid}のように、テナント ID やビジネスディメンションをサフィックスとして使用します。インデックス名には、小文字と数字のみ使用できます。アンダースコアとハイフンはサポートされていません。テナント数が多い場合:論理的な分離にはインデックス名を使用します。OSS ベクターインデックスの作成には数秒しかかからず、管理オーバーヘッドは最小限です。
極めて低いレイテンシーが要求される場合:単一のインデックスが数千万ベクトルを超えた場合は、時間やカテゴリなどのビジネスロジックに基づいて水平パーティショニングを使用します。次に、複数のインデックスに対して並列検索を実行し、結果をマージします。
結果のリランキング (Rerank):複数のインデックスからの検索結果をマージした後、距離に基づいて結果を並べ替えるか、Rerank モデルを使用してセカンダリランキングを行うことができます。
インデックスのクリーンアップ:テナントまたはビジネスのデータを削除するには、
DeleteVectorIndexを呼び出して、データを 1 つずつフィルタリングして削除する代わりに、対応するインデックスを削除します。