すべてのプロダクト
Search
ドキュメントセンター

Object Storage Service:マルチインデックスアーキテクチャによる大規模なベクトル検索

最終更新日:Jun 25, 2026

RAG やセマンティック検索の需要が高まるにつれ、ベクトル検索システムはしばしば 2 つの一般的な課題に直面します:

  • マルチテナント分離:多くの企業顧客にナレッジベースを提供する SaaS プロバイダーや、部門ごとに個別のナレッジベースを持つ企業では、厳格なデータ分離が求められます。

  • 超大規模データ:単一のインデックスに数千万から数億のベクトルが含まれる場合、検索レイテンシーが大幅に増加し、リアルタイム要件を満たすことが困難になります。

OSS Vectors を使用すると、同じアカウントとリージョン内に多数のベクターインデックスを作成できます。マルチインデックスアーキテクチャを使用することで、テナントまたはビジネスディメンションごとにデータをパーティション分割し、データ分離と高性能な検索の両方を実現できます。

image.png

マルチインデックスアーキテクチャの利点

  • データ分離:異なるテナントまたはビジネスユニットのデータは個別のインデックスに保存されるため、テナント間のデータ漏洩を防ぎます。

  • 検索の高速化:大規模なインデックスを複数の小さなインデックスに分割することで、各検索の範囲が狭まります。複数のインデックスを並列検索し、その結果をマージすることで、全体的な応答時間を大幅に短縮できます。

  • 柔軟な運用:各インデックスに対して、ディメンション、モデル、類似性アルゴリズムを個別に設定できます。特定のテナントのデータを削除するには、レコードを 1 つずつフィルタリングして削除する代わりに、対応するインデックスを削除するだけです。

CLI を使用したテナントごとのデータインポート

oss-vectors-embed CLI ツールを使用すると、特定のインデックスにファイルを書き込むことができ、テナントやビジネスディメンションごとにターゲットを絞ったデータインポートが可能になります。

インストール手順については、「OSS Vectors Embed CLI ツールを使用したベクトルデータの書き込みとクエリ」をご参照ください。

開始する前に、次の要件を満たしていることを確認してください:

  • OSS_ACCESS_KEY_IDOSS_ACCESS_KEY_SECRETDASHSCOPE_API_KEY の環境変数が設定されていること。

  • 各テナントに対応するベクターバケットとベクターインデックスが作成されていること。

次の例のプレースホルダーを実際の値に置き換えてください:

プレースホルダー

説明

<your-account-id>

Alibaba Cloud アカウント ID

<your-vector-bucket>

ベクターバケットの名前

テナントごとのデータ書き込み

異なるテナントのデータをそれぞれのインデックスに書き込むことで、データ分離を確保します。

# テナント 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-metadata

SDK を使用したマルチインデックスアーキテクチャの構築

Python SDK

開始する前に、alibabacloud-oss-v2 SDK をインストールします:

pip install alibabacloud-oss-v2

OSS_ACCESS_KEY_IDOSS_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-v2

OSS_ACCESS_KEY_IDOSS_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 つずつフィルタリングして削除する代わりに、対応するインデックスを削除します。