このページでは、Spanner で次の ANN 距離関数を使用して近似最近傍(ANN)を検索し、ベクトル インデックスを作成して、ベクトル エンベディングをクエリする方法について説明します。
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
データセットが小さい場合は、k 近傍法(KNN)を使用して、正確な k 近傍ベクトルを検索できます。ただし、データセットのサイズが大きくなるにつれて、KNN 検索のレイテンシとコストも増加します。ANN を使用すると、レイテンシと費用を大幅に削減しながら、近似 k 最近傍を検索できます。
近似 K 最近傍
ANN 検索では、近似距離が計算され、データセット内のすべてのベクトルが検査されない可能性があるため、返される k 個のベクトルは真の上位 k 近傍ではありません。上位 k 近傍にないベクトルが返されることがあります。これは「再現損失」と呼ばれます。許容できる再現損失の量はユースケースによって異なりますが、ほとんどの場合、データベースのパフォーマンスを改善するために再現率を多少犠牲にすることは許容されるトレードオフです。
Spanner の近似距離関数の詳細については、以下をご覧ください。
- GoogleSQL の
APPROX_COSINE_DISTANCE
- GoogleSQL の
APPROX_EUCLIDEAN_DISTANCE
- GoogleSQL の
APPROX_DOT_PRODUCT
ベクトル インデックス
Spanner は、専用のベクトル インデックスを使用して ANN ベクトル検索を高速化します。このインデックスは、Google Research の高効率な最近傍アルゴリズムである Scalable Nearest Neighbor(ScaNN)を活用しています。
ベクトル インデックスは、ツリーベースの構造を使用してデータをパーティショニングし、高速な検索を可能にします。Spanner では、2 レベルと 3 レベルの両方のツリー構成が可能です。
- 2 レベルのツリー構成: リーフノード(
num_leaves
)には、密接に関連するベクトルのグループと、対応するセントロイドが含まれます。ルートレベルは、すべてのリーフノードのセントロイドで構成されます。 - 3 レベルのツリー構成: 2 レベルのツリーとコンセプトは似ていますが、ブランチレイヤ(
num_branches
)が追加されています。このレイヤからリーフノード セントロイドがさらにパーティショニングされ、ルートレベル(num_leaves
)が形成されます。
Spanner がインデックスを選択します。ただし、特定のインデックスが最適であることをご存じの場合は、FORCE_INDEX
ヒントを使用して、ユースケースに最も適したベクトル インデックスを選択できます。
詳細については、VECTOR INDEX
ステートメントをご覧ください。
制限事項
- ベクトル インデックスを事前に分割することはできません。詳細については、事前分割の概要をご覧ください。
ベクトル インデックスを作成する
ベクトル インデックスの再現率とパフォーマンスを最適化するには、次のことをおすすめします。
エンベディングを含む行のほとんどがデータベースに書き込まれた後に、ベクトル インデックスを作成します。新しいデータを挿入した後で、ベクトル インデックスを定期的に再構築することが必要になる場合があります。詳細については、ベクトル インデックスを再構築するをご覧ください。
STORING
句を使用して、ベクトル インデックスに列のコピーを保存します。列値がベクトル インデックスに保存されている場合、Spanner はインデックスのリーフレベルでフィルタしてクエリのパフォーマンスを向上させます。フィルタ条件で使用される列は保存することをおすすめします。インデックスでSTORING
を使用する方法の詳細については、インデックスのみをスキャンするインデックスを作成するをご覧ください。
テーブルを作成するときに、エンベディング列は FLOAT32
(推奨)または FLOAT64
データ型の配列で、ベクトルのディメンションを示す vector_length アノテーションが必要です。
次の DDL ステートメントは、ベクトル長のエンベディング列 DocEmbedding
を持つ Documents
テーブルを作成します。
CREATE TABLE Documents (
DocId INT64 NOT NULL,
...
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
Documents
テーブルにデータを入力したら、エンベディング列 DocEmbedding
を持つ Documents
テーブルに、2 レベルのツリーと 1,000 個のリーフノードを持つベクトル インデックスを、コサイン距離を使用して作成できます。
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(DocEmbedding)
STORING (WordCount)
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
3 レベルのツリーと 1,000,000 個のリーフノードを含むベクトル インデックスを作成するには:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);
エンベディング列がテーブル定義で NOT NULL
としてマークされていない場合は、WHERE COLUMN_NAME IS NOT NULL
句で宣言する必要があります。
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
ベクトル インデックスをフィルタする
フィルタされたベクトル インデックスを作成して、データベース内でフィルタ条件に一致する最も類似したアイテムを見つけることができます。フィルタ付きベクトル インデックスは、指定されたフィルタ条件を満たす行のみをインデックスに登録するため、検索パフォーマンスが向上します。
次の例では、テーブル Documents
に Category
という列があります。ベクトル検索で「Tech」カテゴリにインデックスを付けるため、カテゴリ条件が満たされない場合に NULL と評価される生成列を作成します。
CREATE TABLE Documents (
DocId INT64 NOT NULL,
Category STRING(MAX),
NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
次に、フィルタを使用してベクトル インデックスを作成します。TechDocEmbeddingIndex
ベクトル インデックスには、「Tech」カテゴリのドキュメントのみがインデックスに登録されます。
CREATE VECTOR INDEX TechDocEmbeddingIndex
ON Documents(DocEmbedding)
STORING(NullIfFiltered)
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
OPTIONS (...);
Spanner は、TechDocEmbeddingIndex
に一致するフィルタを含む次のクエリを実行すると、TechDocEmbeddingIndex
によって自動的に選択され、高速化されます。このクエリは、「Tech」カテゴリ内のドキュメントのみを検索します。{@FORCE_INDEX=TechDocEmbeddingIndex}
を使用して、Spanner に TechDocEmbeddingIndex
を明示的に使用させることもできます。
SELECT *
FROM Documents
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;
ベクトル エンベディングのクエリ
ベクトル インデックスをクエリするには、次の 3 つの近似距離関数のいずれかを使用します。
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
近似距離関数を使用する際の制限事項は次のとおりです。
- 近似距離関数は、エンベディング列と定数式(パラメータやリテラルなど)間の距離を計算する必要があります。
- 近似距離関数の出力を
ORDER BY
句で唯一の並べ替えキーとして使用し、ORDER BY
の後にLIMIT
を指定する必要があります。 - クエリで、インデックスに登録されていない行を明示的に除外する必要があります。ほとんどの場合、テーブル定義で列がすでに
NOT NULL
としてマークされている場合を除き、クエリにベクトル インデックス定義に一致するWHERE <column_name> IS NOT NULL
句を含める必要があります。
制限事項の詳細なリストについては、近似距離関数のリファレンス ページをご覧ください。
例
[1.0, 2.0, 3.0]
に最も近い 100 個のベクトルを検索するには:
SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
エンベディング列が null 値を許容する場合:
SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
ベスト プラクティス
ベクトル インデックスを最適化してクエリ結果を改善するには、次のベスト プラクティスを実施してください。
ベクトル検索オプションを調整する
最適なベクトル検索値は、ユースケース、ベクトル データセット、クエリベクトルによって異なります。特定のワークロードに最適な値を見つけるには、反復的な調整作業が必要になる場合があります。
適切な値を選択する際のガイドラインは次のとおりです。
tree_depth
(ツリーレベル): インデックスに登録するテーブルの行数が 1,000 万行未満の場合は、tree_depth
に2
を使用します。それ以外の場合、3
のtree_depth
は最大 100 億行のテーブルをサポートします。num_leaves
: データセット内の行数の平方根を使用します。値が大きいと、ベクトル インデックスのビルド時間が長くなる可能性があります。num_leaves
をtable_row_count/1000
より大きく設定しないでください。リーフが小さくなりすぎ、パフォーマンスが低下します。num_leaves_to_search
: このオプションは、インデックスのリーフノードを検索する数を指定します。num_leaves_to_search
を増やすと再現率は向上しますが、レイテンシとコストも増加します。num_leaves_to_search
の値には、CREATE VECTOR INDEX
ステートメントで定義されたリーフの合計数の 1% の値を使用することをおすすめします。フィルタ句を使用している場合は、この値を増やすと検索範囲が広がります。
許容できる再現率が達成されたものの、クエリのコストが高すぎて最大 QPS が低い場合は、次の手順で num_leaves
を増やしてみてください。
num_leaves
を元の値の k 倍に設定します(例:2 * sqrt(table_row_count)
)。num_leaves_to_search
を、元の値の k 倍と同じ値に設定します。num_leaves_to_search
を減らして、再現率を維持しながらコストと QPS を改善します。
再現率を改善する
再現率が悪化する原因として、次のようなことが考えられます。
num_leaves_to_search
が小さすぎる: 一部のクエリベクトルの最近傍を見つけるのが難しくなる場合があります。num_leaves_to_search
を増やしてより多くのリーフを検索すると、再現率を改善できます。最近のクエリでは、このような難しいベクトルがより多く含まれるように変化している可能性があります。ベクトル インデックスの再構築が必要: ベクトル インデックスのツリー構造は、作成時にデータセット用に最適化され、その後は静的になります。したがって、最初のベクトル インデックスの作成後に大幅に異なるベクトルが追加された場合、ツリー構造が最適でなくなり、再現率が低下する可能性があります。
ベクトル インデックスを再構築する
ダウンタイムなしでベクトル インデックスを再構築するには:
- 現在のベクトル インデックスと同じエンベディング列に新しいベクトル インデックスを作成し、必要に応じてパラメータ(
OPTIONS
など)を更新します。 - インデックスの作成が完了したら、
FORCE_INDEX
ヒントを使用して新しいインデックスを参照し、ベクトル検索クエリを更新します。これにより、クエリで新しいベクトル インデックスが使用されます。新しいクエリでnum_leaves_to_search
の再調整が必要になる場合もあります。 - 古いベクトル インデックスを削除します。
次のステップ
GoogleSQL の
VECTOR INDEX
ステートメントの詳細を確認する。Spanner ベクトル検索を使ってみるで、ANN の使用例を確認する。