ベクトル インデックスを管理する

このドキュメントでは、ベクトル検索を高速化するためにベクトル インデックスを作成して管理する方法について説明します。

ベクトル インデックスは、特に大規模なデータセットで VECTOR_SEARCH 関数をより効率的に実行できるようにするために設計されたデータ構造です。インデックスを使用する場合、VECTOR_SEARCH近似最近傍(ANN)アルゴリズムを使用して、クエリのレイテンシと計算コストを削減します。ANN はある程度の近似を導入するため、再現率が 100% にならない可能性がありますが、通常、パフォーマンスの向上はほとんどのアプリケーションでメリットをもたらします。

ロールと権限

ベクトル インデックスを作成するには、インデックスを作成するテーブルに対する bigquery.tables.createIndex IAM 権限が必要です。ベクトル インデックスを削除するには、bigquery.tables.deleteIndex 権限が必要です。次の IAM 事前定義ロールには、ベクトル インデックスの操作に必要な権限が含まれています。

  • BigQuery データオーナー(roles/bigquery.dataOwner
  • BigQuery データ編集者(roles/bigquery.dataEditor

ベクトル インデックスのタイプを選択する

BigQuery には 2 種類のベクトル インデックス タイプ(IVFTreeAH)があり、それぞれ異なるユースケースをサポートしています。BigQuery は、VECTOR_SEARCH 関数で入力データの複数の行を処理することで、ベクトル検索のバッチ処理をサポートしています。小規模なクエリバッチの場合は、IVF インデックスが推奨されます。大規模なクエリバッチの場合は、Google の ScaNN アルゴリズムで構築された TreeAH インデックスが推奨されます。

IVF インデックス

IVF は反転ファイル インデックスです。K 平均法アルゴリズムを使用してベクトルデータをクラスタ化し、それに基づいてベクトルデータを分割します。VECTOR_SEARCH 関数は、これらのパーティションを使用して、結果を判断するために読み取る必要のあるデータ量を削減できます。

TreeAH インデックス

TreeAH インデックス タイプは、ツリーのような構造と、基盤となる ScaNN アルゴリズムのコア量子化手法である非対称ハッシュ(AH)の使用を組み合わせたことにちなんで名付けられています。TreeAH インデックスは次のように機能します。

  1. ベーステーブルは、より小さく管理しやすいシャードに分割されます。
  2. クラスタリング モデルがトレーニングされ、クラスタの数は CREATE VECTOR INDEX ステートメントの tree_ah_options 引数の leaf_node_embedding_count オプションから取得されます。
  3. ベクトルは、メモリ使用量を削減する手法である直積量子化によって圧縮されます。圧縮されたベクトルは、元のベクトルの代わりにインデックス テーブルに保存されるため、ベクトル インデックスのサイズが削減されます。
  4. VECTOR_SEARCH 関数が実行されると、近似距離計算用にハードウェアで最適化された非対称ハッシュを使用して、各クエリベクトルの候補リストが効率的に計算されます。これらの候補は、正確なエンベディングを使用して再スコアリングされ、再ランク付けされます。

TreeAH アルゴリズムは、数百のクエリベクトルを処理するバッチクエリ用に最適化されています。直積量子化を使用すると、レイテンシとコストを大幅に削減できます。IVF と比較して数桁も削減できる可能性があります。ただし、オーバーヘッドが増加するため、クエリベクトルの数が少ない場合は IVF アルゴリズムのほうが適している場合があります。

ユースケースが次の条件を満たしている場合は、TreeAH インデックス タイプを試すことをおすすめします。

  • テーブルに含まれる行数が 2 億行以下。

  • 数百以上のクエリベクトルが関係する大規模なバッチクエリを頻繁に実行する。

TreeAH インデックス タイプを使用する小規模なバッチクエリの場合、VECTOR_SEARCHブルート フォース検索に戻ることがあります。この場合、ベクトル インデックスが使用されなかった理由を説明する IndexUnusedReason が提供されます。

IVF ベクトル インデックスを作成する

IVF ベクトル インデックスを作成するには、CREATE VECTOR INDEX データ定義言語(DDL)ステートメントを使用します。

  1. [BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタで、次の SQL ステートメントを実行します。

    IVF ベクトル インデックスを作成するには:

    CREATE [ OR REPLACE ] VECTOR INDEX [ IF NOT EXISTS ] INDEX_NAME
    ON DATASET_NAME.TABLE_NAME(COLUMN_NAME)
    STORING(STORED_COLUMN_NAME [, ...])
    OPTIONS(index_type = 'IVF',
      distance_type = 'DISTANCE_TYPE',
      ivf_options = '{"num_lists":NUM_LISTS}')

    次のように置き換えます。

    • INDEX_NAME: 作成するベクトル インデックスの名前。インデックスは常にベーステーブルと同じプロジェクトとデータセットに作成されるため、名前にこれらを指定する必要はありません。
    • DATASET_NAME: テーブルを含むデータセットの名前。
    • TABLE_NAME: エンベディング データを持つ列を含むテーブルの名前。
    • COLUMN_NAME: エンベディング データを含む列の名前。列の型は ARRAY<FLOAT64> にする必要があります。この列に子フィールドを含めることはできません。配列内のすべての要素は非 NULL でなければならず、列の値はすべて同じ配列ディメンションでなければなりません。
    • STORED_COLUMN_NAME: ベクトル インデックスに格納するテーブルの最上位列の名前。列の型は RANGE にすることはできません。テーブルに行レベルのアクセス ポリシーが設定されている場合や、列にポリシータグが設定されている場合、保存された列は使用されません。保存された列を有効にする方法については、列を保存して事前フィルタするをご覧ください。
    • DISTANCE_TYPE: このインデックスを使用してベクトル検索を行う際に使用するデフォルトの距離の種類を指定します。サポートされている値は、EUCLIDEANCOSINEDOT_PRODUCT です。デフォルトは EUCLIDEAN です。

      インデックスの作成自体では、トレーニングに常に EUCLIDEAN 距離を使用しますが、VECTOR_SEARCH 関数で使用される距離は異なる場合があります。

      VECTOR_SEARCH 関数の distance_type 引数に値を指定すると、DISTANCE_TYPE 値の代わりにその値が使用されます。

    • NUM_LISTS: IVF インデックスがクラスタ化してベクトルデータをパーティショニングするリストの数を指定する INT64 値。この値は 5,000 以下にする必要があります。インデックス登録中、ベクトルは最も近いクラスタ セントロイドに対応するリストに割り当てられます。この引数を省略すると、BigQuery はデータの特性に基づいてデフォルト値を決定します。デフォルト値はほとんどのユースケースで適切に機能します。

      NUM_LISTS は、クエリ チューニングの粒度を制御します。値が大きいほど、作成されるリストの数が増えるため、VECTOR_SEARCH 関数の fraction_lists_to_search オプションを設定して、インデックスのより少ない割合をスキャンできます。たとえば、10 個のリストの 10% をスキャンするのではなく、100 個のリストの 1% をスキャンします。これにより、検索速度と再現率をより細かく制御できますが、インデックス処理のコストが若干増加します。この引数の値は、クエリのスコープをどの程度正確に調整する必要があるかに基づいて設定します。

次の例では、my_tableembedding 列にベクトル インデックスを作成します。

CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS(index_type = 'IVF');

次の例では、my_tableembedding 列にベクトル インデックスを作成し、使用する距離の種類と IVF オプションを指定しています。

CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS(index_type = 'IVF', distance_type = 'COSINE',
ivf_options = '{"num_lists": 2500}')

TreeAH ベクトル インデックスを作成する

TreeAH ベクトル インデックスを作成するには、CREATE VECTOR INDEX データ定義言語(DDL)ステートメントを使用します。

  1. [BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタで、次の SQL ステートメントを実行します。

    CREATE [ OR REPLACE ] VECTOR INDEX [ IF NOT EXISTS ] INDEX_NAME
    ON DATASET_NAME.TABLE_NAME(COLUMN_NAME)
    STORING(STORED_COLUMN_NAME [, ...])
    OPTIONS(index_type = 'TREE_AH',
      distance_type = 'DISTANCE_TYPE',
      tree_ah_options = '{"leaf_node_embedding_count":LEAF_NODE_EMBEDDING_COUNT,
        "normalization_type":"NORMALIZATION_TYPE"}')

    次のように置き換えます。

    • INDEX_NAME: 作成するベクトル インデックスの名前。インデックスは常にベーステーブルと同じプロジェクトとデータセットに作成されるため、名前にこれらを指定する必要はありません。
    • DATASET_NAME: テーブルを含むデータセットの名前。
    • TABLE_NAME: エンベディング データを持つ列を含むテーブルの名前。
    • COLUMN_NAME: エンベディング データを含む列の名前。列の型は ARRAY<FLOAT64> にする必要があります。この列に子フィールドを含めることはできません。配列内のすべての要素は非 NULL でなければならず、列の値はすべて同じ配列ディメンションでなければなりません。
    • STORED_COLUMN_NAME: ベクトル インデックスに格納するテーブルの最上位列の名前。列の型は RANGE にすることはできません。テーブルに行レベルのアクセス ポリシーが設定されている場合や、列にポリシータグが設定されている場合、保存された列は使用されません。保存された列を有効にする方法については、列を保存して事前フィルタするをご覧ください。
    • DISTANCE_TYPE: このインデックスを使用してベクトル検索を行う際に使用するデフォルトの距離の種類を指定するオプション引数。サポートされている値は、EUCLIDEANCOSINEDOT_PRODUCT です。デフォルトは EUCLIDEAN です。

      インデックスの作成自体では、トレーニングに常に EUCLIDEAN 距離を使用しますが、VECTOR_SEARCH 関数で使用される距離は異なる場合があります。

      VECTOR_SEARCH 関数の distance_type 引数に値を指定すると、DISTANCE_TYPE 値の代わりにその値が使用されます。

    • LEAF_NODE_EMBEDDING_COUNT: 500 以上の INT64 値。TreeAH アルゴリズムが作成するツリーの各リーフノードにあるベクトルの概数を指定します。TreeAH アルゴリズムは、データ空間全体を複数のリストに分割します。各リストには約 LEAF_NODE_EMBEDDING_COUNT 個のデータポイントが含まれます。値が小さいほど、リスト数は増えますが、データポイント数は少なくなります。値が大きいほど、リスト数は減りますが、データポイント数は多くなります。デフォルトは 1,000 で、ほとんどのデータセットに適しています。

    • NORMALIZATION_TYPE: STRING の値サポートされている値は、NONEL2 です。デフォルトは NONE です。処理の前にベーステーブルのデータとクエリデータの両方に正規化が実行されますが、TABLE_NAME の埋め込み列 COLUMN_NAME は変更されません。データセット、エンベディング モデル、VECTOR_SEARCH で使用される距離タイプによっては、エンベディングの正規化で再現率が向上する場合があります。

次の例では、my_tableembedding 列にベクトル インデックスを作成し、使用する距離の種類と TreeAH オプションを指定しています。

CREATE TABLE my_dataset.my_table(id INT64, embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS (index_type = 'TREE_AH', distance_type = 'EUCLIDEAN',
tree_ah_options = '{"normalization_type": "L2"}');

フィルタリング

以降のセクションでは、事前フィルタと事後フィルタがベクトル検索結果に与える影響と、ベクトル インデックスに保存されている列とパーティションを使用して事前フィルタする方法について説明します。

事前フィルタと事後フィルタ

BigQuery の VECTOR_SEARCH オペレーションでは、事前フィルタリングと事後フィルタリングの両方が、ベクトル エンベディングに関連付けられたメタデータ列に基づいて条件を適用することで、検索結果の絞り込みに役立ちます。クエリのパフォーマンス、費用、精度を最適化するには、これらの違い、実装、影響を理解することが重要です。

事前フィルタリングと事後フィルタリングは次のように定義されます。

  • 事前フィルタリング: 近似最近傍(ANN)検索で候補ベクトルに対して距離計算を行う前に、フィルタ条件を適用します。これにより、検索中に考慮されるベクトルのプールが絞り込まれます。そのため、事前フィルタリングを行うと、ANN 検索で評価される候補が減るため、クエリ時間が短縮され、計算コストが削減されることが多くなります。
  • 事後フィルタリング: ANN 検索によって最初の top_k 個の最近傍が特定された後に、フィルタ条件を適用します。これにより、指定した条件に基づいて最終的な結果セットが絞り込まれます。

WHERE 句の配置によって、フィルタが事前フィルタとして機能するか、事後フィルタとして機能するかが決まります。

事前フィルタを作成するには、クエリの WHERE 句が VECTOR_SEARCH 関数のベーステーブル引数に適用されている必要があります。述語は保存された列に適用する必要があります。そうしないと、事実上、事後フィルタになります。

次の例は、事前フィルタを作成する方法を示しています。

-- Pre-filter on a stored column. The index speeds up the query.
SELECT *
FROM
  VECTOR_SEARCH(
    (SELECT * FROM my_dataset.my_table WHERE type = 'animal'),
    'embedding',
    TABLE my_dataset.my_testdata);

-- Filter on a column that isn't stored. The index is used to search the
-- entire table, and then the results are post-filtered. You might see fewer
-- than 5 matches returned for some embeddings.
SELECT query.test_id, base.type, distance
FROM
  VECTOR_SEARCH(
    (SELECT * FROM my_dataset.my_table WHERE id = 123),
    'embedding',
    TABLE my_dataset.my_testdata,
    top_k => 5);

-- Use pre-filters with brute force. The data is filtered and then searched
-- with brute force for exact results.
SELECT query.test_id, base.type, distance
FROM
  VECTOR_SEARCH(
    (SELECT * FROM my_dataset.my_table WHERE id = 123),
    'embedding',
    TABLE my_dataset.my_testdata,
    options => '{"use_brute_force":true}');

事後フィルタを作成するには、検索によって返された結果をフィルタするように、クエリの WHERE 句を VECTOR_SEARCH 関数の外部に適用する必要があります。

次の例は、事後フィルタを作成する方法を示しています。

-- Use post-filters. The index is used, but the entire table is searched and
-- the post-filtering might reduce the number of results.
SELECT query.test_id, base.type, distance
FROM
  VECTOR_SEARCH(
    TABLE my_dataset.my_table,
    'embedding',
    TABLE my_dataset.my_testdata,
    top_k => 5)
WHERE base.type = 'animal';

SELECT base.id, distance
FROM
  VECTOR_SEARCH(
    TABLE mydataset.base_table,
    'embedding',
    (SELECT embedding FROM mydataset.query_table),
    top_k => 10
  )
WHERE type = 'document' AND year > 2022

事後フィルタを使用する場合、または指定したベーステーブル フィルタが保存されていない列を参照しているために事後フィルタとして機能する場合、最終的な結果セットに top_k 未満の行が含まれる可能性があります。述語が選択的である場合は、ゼロ行になることもあります。フィルタリング後に特定の数の結果が必要な場合は、top_k の値を大きくするか、VECTOR_SEARCH 呼び出しで fraction_lists_to_search の値を増やすことを検討してください。

場合によっては、特に事前フィルタリングの選択性が非常に高い場合、事前フィルタリングによって結果セットのサイズが小さくなることもあります。その場合は、VECTOR_SEARCH 呼び出しで fraction_lists_to_search の値を増やしてみてください。

保存された列で事前フィルタする

ベクトル インデックスの効率をさらに高めるには、ベクトル インデックスに格納するベーステーブルの列を指定します。保存列を使用すると、VECTOR_SEARCH 関数を呼び出すクエリを次のように最適化できます。

  • テーブル全体を検索する代わりに、WHERE 句を使用してベーステーブルを事前フィルタするクエリ ステートメントで VECTOR_SEARCH 関数を呼び出すことができます。テーブルにインデックスがあり、保存されている列のみでフィルタする場合、BigQuery は検索前にデータをフィルタし、インデックスを使用してより小さな結果セットを検索することでクエリを最適化します。保存されていない列でフィルタする場合、BigQuery はテーブルの検索後にフィルタを適用します(後処理フィルタ)。

  • VECTOR_SEARCH 関数は、ベーステーブルのすべての列を含む base という構造体を出力します。保存された列がないと、base に保存されている列を取得するために、結合が必要になる可能性があります。この処理には費用が発生します。IVF インデックスを使用し、クエリで base から保存された列のみを選択する場合、BigQuery はクエリを最適化して結合を排除します。TreeAH インデックスの場合、ベーステーブルとの結合は削除されません。TreeAH インデックスの保存された列は、事前フィルタリングの目的でのみ使用されます。

列を保存するには、CREATE VECTOR INDEX ステートメントSTORING 句に列を指定します。列を保存するとベクトル インデックスのサイズが大きくなるため、頻繁に使用される列またはフィルタされた列のみを保存することをおすすめします。

次の例では、格納された列でベクトル インデックスを作成し、格納された列のみを選択するベクトル検索クエリを実行します。

-- Create a table that contains an embedding.
CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>, type STRING, creation_time DATETIME, id INT64);

-- Create a query table that contains an embedding.
CREATE TABLE my_dataset.my_testdata(embedding ARRAY<FLOAT64>, test_id INT64);

-- Create a vector index with stored columns.
CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
STORING (type, creation_time)
OPTIONS (index_type = 'IVF');

-- Select only stored columns from a vector search to avoid an expensive join.
SELECT query, base.type, distance
FROM
  VECTOR_SEARCH(
    TABLE my_dataset.my_table,
    'embedding'
    TABLE my_dataset.my_testdata);

保存された列の制限事項

  • ベーステーブルで列のモード、タイプ、スキーマが変更され、その列がベクトル インデックスに格納されている場合、その変更がベクトル インデックスに反映されるまでに遅延が生じることがあります。更新がインデックスに適用されるまで、ベクトル検索クエリはベーステーブル内の変更された保存列を使用します。
  • 保存された列を持つインデックスのあるテーブルに対して、VECTOR_SEARCH クエリの query 出力から STRUCT 型の列を選択すると、クエリ全体が失敗する可能性があります。

パーティションで事前フィルタする

ベクトル インデックスを作成するテーブルがパーティション分割されている場合は、ベクトル インデックスもパーティション分割できます。ベクトル インデックスをパーティション分割すると、次のようなメリットがあります。

  • パーティション プルーニングは、テーブル パーティションに加えてベクトル インデックスにも適用されます。パーティション プルーニングは、ベクトル検索でパーティショニング列の値に対する限定フィルタが使用されると発生します。これにより、BigQuery はフィルタに一致するパーティションをスキャンし、残りのパーティションをスキップできます。パーティション プルーニングにより、I/O コストを削減できます。パーティション プルーニングの詳細については、パーティション分割テーブルに対してクエリを実行するをご覧ください。
  • パーティショニング列で事前フィルタすると、ベクトル検索で結果が見つからない可能性が低くなります。

パーティション分割できるのは TreeAH ベクトル インデックスのみです。

ベクトル インデックスのパーティション分割は、事前フィルタリングを使用してほとんどのベクトル検索を少数のパーティションに制限する場合にのみおすすめします。

パーティション分割されたインデックスを作成するには、CREATE VECTOR INDEX ステートメントPARTITION BY 句を使用します。次の例に示すように、CREATE VECTOR INDEX ステートメントで指定する PARTITION BY 句は、ベクトル インデックスを作成するテーブルの CREATE TABLE ステートメントで指定する PARTITION BY 句と同じである必要があります。

-- Create a date-partitioned table.
CREATE TABLE my_dataset.my_table(
  embeddings ARRAY
  id INT64,
  date DATE,
)
PARTITION BY date;

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX my_index ON my_dataset.my_table(embeddings)
PARTITION BY date
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

テーブルで整数範囲または時間単位列パーティショニングを使用している場合、パーティショニング列はベクトル インデックスに格納されるため、ストレージ費用が増加します。テーブルの列が CREATE VECTOR INDEX ステートメントの STORING 句と PARTITION BY 句の両方で使用されている場合、列は 1 回だけ保存されます。

ベクトル インデックス パーティションを使用するには、VECTOR_SEARCH ステートメントのベーステーブル サブクエリでパーティショニング列をフィルタします。次の例では、samples.items テーブルは produced_date 列でパーティション分割されているため、VECTOR_SEARCH ステートメントのベーステーブル サブクエリは produced_date 列でフィルタします。

SELECT query.id, base.id, distance
FROM VECTOR_SEARCH(
  (SELECT * FROM my_dataset.my_table WHERE produced_date = '2025-01-01'),
  'embedding',
  TABLE samples.test,
  distance_type => 'COSINE',
  top_k => 10
);

日時パーティション分割テーブルにパーティション分割ベクトル インデックスを作成します。

-- Create a datetime-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  produced_date DATETIME,
  embeddings ARRAY
)
PARTITION BY produced_date;

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY produced_date
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

タイムスタンプ パーティション分割テーブルにパーティション分割ベクトル インデックスを作成します。

-- Create a timestamp-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  produced_time TIMESTAMP,
  embeddings ARRAY
)
PARTITION BY TIMESTAMP_TRUNC(produced_time, HOUR);

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY TIMESTAMP_TRUNC(produced_time, HOUR)
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

整数範囲パーティション分割テーブルにパーティション分割ベクトル インデックスを作成します。

-- Create a integer range-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  embeddings ARRAY
)
PARTITION BY RANGE_BUCKET(id, GENERATE_ARRAY(-100, 100, 20));

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY RANGE_BUCKET(id, GENERATE_ARRAY(-100, 100, 20))
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

取り込み時間パーティション分割テーブルにパーティション分割ベクトル インデックスを作成します。

-- Create a ingestion time-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  embeddings ARRAY
)
PARTITION BY TIMESTAMP_TRUNC(_PARTITIONTIME, DAY);

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY TIMESTAMP_TRUNC(_PARTITIONTIME, DAY)
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

事前フィルタリングに関する制限事項

  • 事前フィルタで論理ビューを使用できません。
  • 事前フィルタにサブクエリが含まれていると、インデックスを使用できない場合があります。

データがインデックスに登録されるタイミング

ベクトル インデックスは BigQuery によって完全に管理され、インデックス付きテーブルが変更されると自動的に更新されます。

インデックス登録は非同期で実行されます。ベーステーブルに新しい行を追加してから、新しい行がインデックスに反映されるまでには遅延があります。ただし、VECTOR_SEARCH 関数はすべての行を考慮し、インデックス付けされていない行も見逃しません。この関数は、インデックス登録されたレコードの検索にはインデックスを使用し、まだインデックス登録されていないレコードの検索にはブルート フォース検索を使用します。

10 MB 未満のテーブルにベクトル インデックスを作成すると、ベクトル インデックスにはデータが挿入されません。同様に、インデックス付きテーブルからデータを削除してテーブルサイズが 10 MB を下回るようになった場合、ベクトル インデックスは一時的に無効になります。この場合、ベクトル検索クエリではインデックスが使用されず、Job リソースの vectorSearchStatistics セクションにある indexUnusedReasons コードは BASE_TABLE_TOO_SMALL になります。インデックスがない場合、VECTOR_SEARCH は自動的にブルート フォースを使用してエンベディングの最近傍を検出します。

テーブル内のインデックス付き列を削除するか、テーブル自体の名前を変更すると、ベクトル インデックスは自動的に削除されます。

ベクトル インデックスのステータスをモニタリングする

INFORMATION_SCHEMA ビューをクエリすることで、ベクトル インデックスの状態をモニタリングできます。次のビューには、ベクトル インデックスのメタデータが含まれます。

  • INFORMATION_SCHEMA.VECTOR_INDEXES ビューには、データセット内のベクトル インデックスに関する情報が含まれます。

    CREATE VECTOR INDEX ステートメントが完了した後も、インデックスを使用するにはインデックスにデータが入力されている必要があります。last_refresh_time 列と coverage_percentage 列を使用して、ベクトル インデックスの準備状況を確認できます。ベクトル インデックスの準備ができていない場合でも、テーブルに対して VECTOR_SEARCH 関数を使用できますが、インデックスがない場合には実行速度が遅くなる可能性があります。

  • INFORMATION_SCHEMA.VECTOR_INDEX_COLUMNS ビューには、データセット内のすべてのテーブルについてのベクトル インデックス付き列に関する情報が含まれます。

  • INFORMATION_SCHEMA.VECTOR_INDEX_OPTIONS ビューには、データセット内のベクトル インデックスで使用されているオプションに関する情報が含まれています。

ベクトル インデックスの例

次の例では、プロジェクト my_project にあるデータセット my_dataset 内のテーブルに対するアクティブなベクトル インデックスがすべて表示されます。これには、名前、作成に使用された DDL ステートメント、カバレッジの割合が含まれます。インデックス付きベーステーブルが 10 MB 未満の場合、そのインデックスにはデータが取り込まれず、coverage_percentage 値は 0 になります。

SELECT table_name, index_name, ddl, coverage_percentage
FROM my_project.my_dataset.INFORMATION_SCHEMA.VECTOR_INDEXES
WHERE index_status = 'ACTIVE';

次のような結果になります。

+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table_name | index_name | ddl                                                                                             | coverage_percentage |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table1     | indexa     | CREATE VECTOR INDEX `indexa` ON `my_project.my_dataset.table1`(embeddings)                      | 100                 |
|            |            | OPTIONS (distance_type = 'EUCLIDEAN', index_type = 'IVF', ivf_options = '{"num_lists": 100}')   |                     |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table2     | indexb     | CREATE VECTOR INDEX `indexb` ON `my_project.my_dataset.table2`(vectors)                         | 42                  |
|            |            | OPTIONS (distance_type = 'COSINE', index_type = 'IVF', ivf_options = '{"num_lists": 500}')      |                     |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table3     | indexc     | CREATE VECTOR INDEX `indexc` ON `my_project.my_dataset.table3`(vectors)                         | 98                  |
|            |            | OPTIONS (distance_type = 'DOT_PRODUCT', index_type = 'TREE_AH',                                 |                     |
|            |            |          tree_ah_options = '{"leaf_node_embedding_count": 1000, "normalization_type": "NONE"}') |                     |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+

ベクトル インデックス列の例

次のクエリは、ベクトル インデックスを持つ列の情報を抽出します。

SELECT table_name, index_name, index_column_name, index_field_path
FROM my_project.dataset.INFORMATION_SCHEMA.VECTOR_INDEX_COLUMNS;

次のような結果になります。

+------------+------------+-------------------+------------------+
| table_name | index_name | index_column_name | index_field_path |
+------------+------------+-------------------+------------------+
| table1     | indexa     | embeddings        | embeddings       |
| table2     | indexb     | vectors           | vectors          |
| table3     | indexc     | vectors           | vectors          |
+------------+------------+-------------------+------------------+

ベクトル インデックス オプションの例

次のクエリは、ベクトル インデックス オプションに関する情報を抽出します。

SELECT table_name, index_name, option_name, option_type, option_value
FROM my_project.dataset.INFORMATION_SCHEMA.VECTOR_INDEX_OPTIONS;

次のような結果になります。

+------------+------------+------------------+------------------+-------------------------------------------------------------------+
| table_name | index_name | option_name      | option_type      | option_value                                                      |
+------------+------------+------------------+------------------+-------------------------------------------------------------------+
| table1     | indexa     | index_type       | STRING           | IVF                                                               |
| table1     | indexa     | distance_type    | STRING           | EUCLIDEAN                                                         |
| table1     | indexa     | ivf_options      | STRING           | {"num_lists": 100}                                                |
| table2     | indexb     | index_type       | STRING           | IVF                                                               |
| table2     | indexb     | distance_type    | STRING           | COSINE                                                            |
| table2     | indexb     | ivf_options      | STRING           | {"num_lists": 500}                                                |
| table3     | indexc     | index_type       | STRING           | TREE_AH                                                           |
| table3     | indexc     | distance_type    | STRING           | DOT_PRODUCT                                                       |
| table3     | indexc     | tree_ah_options  | STRING           | {"leaf_node_embedding_count": 1000, "normalization_type": "NONE"} |
+------------+------------+------------------+------------------+-------------------------------------------------------------------+

ベクトル インデックスの使用状況を確認する

ベクトル インデックスの使用状況に関する情報は、ベクトル検索クエリを実行したジョブのジョブ メタデータで確認できます。ジョブ メタデータを表示するには、 Google Cloud コンソール、bq コマンドライン ツール、BigQuery API、またはクライアント ライブラリを使用します。

Google Cloud コンソールを使用する場合、[ベクトル インデックス使用モード] フィールドと [ベクトル インデックスが使用されない理由] フィールドにベクトル インデックスの使用状況に関する情報が表示されます。

bq ツールまたは BigQuery API を使用する場合は、Job リソースの VectorSearchStatistics セクションでベクトル インデックスの使用状況に関する情報を確認できます。

インデックス使用モードは、次のいずれかの値により、ベクトル インデックスが使用されたかどうかを示します。

  • UNUSED: ベクトル インデックスは使用されていません。
  • PARTIALLY_USED: クエリ内の VECTOR_SEARCH 関数の中には、ベクトル インデックスを使用したものと使用しなかったものがあります。
  • FULLY_USED: クエリ内のすべての VECTOR_SEARCH 関数でベクトル インデックスが使用されました。

インデックス使用モードの値が UNUSED または PARTIALLY_USED の場合、「インデックスが使用されない理由」にクエリでベクトル インデックスが使用されなかった理由が示されます。

たとえば、bq show --format=prettyjson -j my_job_id から返された次の結果は、インデックスが使用されなかった理由が、VECTOR_SEARCH 関数で use_brute_force オプションが指定されているからであることが示されています。

"vectorSearchStatistics": {
  "indexUnusedReasons": [
    {
      "baseTable": {
        "datasetId": "my_dataset",
        "projectId": "my_project",
        "tableId": "my_table"
      },
      "code": "INDEX_SUPPRESSED_BY_FUNCTION_OPTION",
      "message": "No vector index was used for the base table `my_project:my_dataset.my_table` because use_brute_force option has been specified."
    }
  ],
  "indexUsageMode": "UNUSED"
}

インデックス管理オプション

インデックスを作成して BigQuery に維持させるには、次の 2 つの方法があります。

  • デフォルトの共有スロットプールを使用する: インデックスが付く予定のデータが組織ごとの上限を下回っている場合は、インデックス管理用に無料の共有スロットプールを使用できます。
  • 独自の予約を使用する: 大規模な本番環境ワークロードでより予測可能かつ一貫したインデックス付けを進めるため、インデックス管理用に独自の予約を使用できます。

共有スロットを使用する

インデックス専用の予約を使用するようプロジェクトをまだ構成していない場合、インデックス管理は次の制約に従い、無料の共有スロットプールで処理されます。

テーブルにデータを追加したことによってインデックス付きテーブルの合計サイズが組織の上限を超えた場合、BigQuery はそのテーブルに対するインデックス管理を一時停止します。この場合、INFORMATION_SCHEMA.VECTOR_INDEXES ビューindex_status フィールドに PENDING DISABLEMENT が表示され、インデックスは削除対象のキューに入ります。インデックスの無効化が保留状態になっている間、そのインデックスは引き続き検索クエリで使用され、インデックス ストレージに対して料金が発生します。インデックスが削除されると、index_status フィールドにはインデックスが TEMPORARILY DISABLED として表示されます。この状態では、クエリはインデックスを使用せず、インデックス ストレージに対して料金は発生しません。この場合、IndexUnusedReason コードBASE_TABLE_TOO_LARGE です。

テーブルからデータを削除した結果、インデックス付きテーブルの合計サイズが組織ごとの上限を下回ると、インデックス管理が再開します。INFORMATION_SCHEMA.VECTOR_INDEXES ビューの index_status フィールドは ACTIVE になり、クエリでインデックスを使用でき、インデックス ストレージに対して料金が発生します。

INFORMATION_SCHEMA.SEARCH_INDEXES_BY_ORGANIZATION ビューを使用すると、特定のリージョンで組織ごとの上限に対する現在の使用量を、プロジェクトとテーブルごとに分類して確認できます。

BigQuery は、共有プールの使用可能容量や確認されるインデックス処理スループットを保証することはありません。本番環境アプリケーションでは、インデックス処理に専用スロットを使用することをおすすめします。

独自の予約を使用する

デフォルトの共有スロットプールを使用する代わりに、必要に応じて、独自の予約を指定してテーブルにインデックスを付けることもできます。独自の予約を使用すると、作成、更新、バックグラウンド最適化などのインデックス管理ジョブのパフォーマンスが予測可能で一貫したものになります。

  • 予約でインデックス ジョブが実行されるときには、テーブルサイズの上限がありません。
  • 独自の予約を使用すると、インデックスを柔軟に管理できます。非常に大きなインデックスを作成する必要がある場合、またはインデックス付きテーブルを大幅に更新する必要がある場合には、より多くのスロットを一時的に割り当てに追加できます。

指定された予約を使用してプロジェクト内のテーブルにインデックスを付けるには、テーブルが配置されているリージョンで予約を作成します。次に、job_typeBACKGROUND に設定して、プロジェクトを予約に割り当てます。

SQL

CREATE ASSIGNMENT DDL ステートメントを使用します。

  1. Google Cloud コンソールで、[BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタで次のステートメントを入力します。

    CREATE ASSIGNMENT
      `ADMIN_PROJECT_ID.region-LOCATION.RESERVATION_NAME.ASSIGNMENT_ID`
    OPTIONS (
      assignee = 'projects/PROJECT_ID',
      job_type = 'BACKGROUND');

    次のように置き換えます。

    • ADMIN_PROJECT_ID: 予約リソースを所有する管理プロジェクトのプロジェクト ID
    • LOCATION: 予約のロケーション
    • RESERVATION_NAME: 予約の名前
    • ASSIGNMENT_ID: 割り当ての ID

      ID はプロジェクトとロケーションごとに一意でなければならず、先頭と末尾を英小文字または数字にする必要があり、英小文字、数字、ダッシュのみを使用できます。

    • PROJECT_ID: インデックスを付けるテーブルを含むプロジェクトの ID。このプロジェクトは予約に割り当てられます。

  3. [実行] をクリックします。

クエリの実行方法については、インタラクティブ クエリを実行するをご覧ください。

bq

bq mk コマンドを使用します。

bq mk \
    --project_id=ADMIN_PROJECT_ID \
    --location=LOCATION \
    --reservation_assignment \
    --reservation_id=RESERVATION_NAME \
    --assignee_id=PROJECT_ID \
    --job_type=BACKGROUND \
    --assignee_type=PROJECT

次のように置き換えます。

  • ADMIN_PROJECT_ID: 予約リソースを所有する管理プロジェクトのプロジェクト ID
  • LOCATION: 予約のロケーション
  • RESERVATION_NAME: 予約の名前
  • PROJECT_ID: この予約に割り当てられるプロジェクトの ID

インデックス ジョブの表示

単一のテーブルでインデックスが作成または更新されるたびに、新しいインデックス ジョブが作成されます。ジョブに関する情報を表示するには、INFORMATION_SCHEMA.JOBS* ビューをクエリします。クエリの WHERE 句で job_type IS NULL AND SEARCH(job_id, '`search_index`') を設定すると、インデックス ジョブをフィルタリングできます。次の例では、プロジェクト my_project 内の最新の 5 つのインデックス ジョブが一覧表示されます。

SELECT *
FROM
 region-us.INFORMATION_SCHEMA.JOBS
WHERE
  project_id  = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
ORDER BY
 creation_time DESC
LIMIT 5;

予約サイズの選択

予約に適切なスロット数を選択するには、インデックス管理ジョブが実行されるタイミング、使用されるスロット数、時間の経過に伴う使用状況の推移を考慮する必要があります。BigQuery は、次のようなときにインデックス管理ジョブをトリガーします。

  • テーブルにインデックスを作成したとき。
  • インデックス付きテーブルでデータが変更されたとき。
  • テーブルのスキーマが変更された影響として、どの列がインデックスに登録されるかが変化したとき。
  • インデックスのデータとメタデータが定期的に最適化または更新されるとき。

テーブルに対するインデックス管理ジョブに必要なスロット数は、次の要因に依存します。

  • テーブルのサイズ
  • テーブルへのデータ取り込みのレート
  • テーブルに適用される DML ステートメントのレート
  • インデックスを構築および維持するときに許容される遅延
  • インデックスの複雑さ(通常は、重複する語の数などのデータ属性によって決まります)
使用状況と進捗状況のモニタリング

インデックス管理ジョブを効率的に実行するのに必要なスロット数を評価する最良の方法は、スロットの使用状況をモニタリングし、それに応じて予約サイズを調整することです。次のクエリは、インデックス管理ジョブの日次スロット使用状況を生成します。リージョン us-west1 では過去 30 日間のみが含まれます。

SELECT
  TIMESTAMP_TRUNC(job.creation_time, DAY) AS usage_date,
  -- Aggregate total_slots_ms used for index-management jobs in a day and divide
  -- by the number of milliseconds in a day. This value is most accurate for
  -- days with consistent slot usage.
  SAFE_DIVIDE(SUM(job.total_slot_ms), (1000 * 60 * 60 * 24)) AS average_daily_slot_usage
FROM
  `region-us-west1`.INFORMATION_SCHEMA.JOBS job
WHERE
  project_id = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
GROUP BY
  usage_date
ORDER BY
  usage_date DESC
limit 30;

インデックス管理ジョブを実行するための十分なスロットがない場合、インデックスがテーブルと非同期状態になってインデックス ジョブが失敗することがあります。その場合、BigQuery はインデックスをまったくゼロの状態から再構築します。非同期インデックスを回避するには、データ取り込みと最適化によるインデックスの更新をサポートできるような十分なスロットがあることを確認してください。スロット使用状況のモニタリングの詳細については、管理リソースグラフをご覧ください。

ベクトル インデックスを再構築する

ベクトル インデックスの作成後にテーブルデータが大幅に変更されると、ベクトル インデックスの効率が低下する可能性があります。ベクトル インデックスの効率が低い場合、インデックスを使用すると、最初は再現率が高かったベクトル検索クエリの再現率が低下します。これは、ベーステーブルのデータ分布のシフトがベクトル インデックスに反映されないためです。

検索クエリのレイテンシを増やさずに再現率を向上させる場合は、ベクトル インデックスを再構築します。また、ベクトル検索の fraction_lists_to_search オプションの値を大きくして再現率を向上させることもできますが、通常、検索クエリの速度が低下します。

VECTOR_INDEX.STATISTICS 関数を使用すると、ベクトル インデックスが作成された時点から現在までの間に、インデックス付きテーブルのデータがどの程度ドリフトしたかを計算できます。テーブルデータが変更され、ベクトル インデックスの再構築が必要になった場合は、ALTER VECTOR INDEX REBUILD ステートメントを使用してベクトル インデックスを再構築できます。

ベクトル インデックスを再構築する手順は次のとおりです。

  1. [BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタで、次の SQL ステートメントを実行して、インデックス付きテーブルのデータドリフトを確認します。

    SELECT * FROM VECTOR_INDEX.STATISTICS(TABLE DATASET_NAME.TABLE_NAME);

    次のように置き換えます。

    • DATASET_NAME: インデックス付きテーブルを含むデータセットの名前。
    • TABLE_NAME: ベクトル インデックスを含むテーブルの名前。

    この関数は、範囲 [0,1)FLOAT64 値を返します。値が小さいほど、ドリフトが少ないことを示します。通常、0.3 以上の値は、ベクトル インデックスの再構築が有益である可能性を示すのに十分な変化と見なされます。

  3. VECTOR_INDEX.STATISTICS 関数がテーブルのデータドリフトが著しいことを示している場合は、次の SQL ステートメントを実行してベクトル インデックスを再構築します。

    ALTER VECTOR INDEX IF EXISTS INDEX_NAME
    ON DATASET_NAME.TABLE_NAME
    REBUILD;

    次のように置き換えます。

    • INDEX_NAME: 作成するベクトル インデックスの名前。
    • DATASET_NAME: インデックス付きテーブルを含むデータセットの名前。
    • TABLE_NAME: ベクトル インデックスを含むテーブルの名前。

ベクトル インデックスを削除する

ベクトル インデックスが不要になった場合や、テーブル上のインデックスを付ける列を変更する場合は、DROP VECTOR INDEX DDL ステートメントを使用して、そのテーブルのインデックスを削除できます。

  1. [BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタで、次の SQL ステートメントを実行します。

    DROP VECTOR INDEX INDEX_NAME ON DATASET_NAME.TABLE_NAME;

    次のように置き換えます。

    • INDEX_NAME: 削除するベクトル インデックスの名前。
    • DATASET_NAME: インデックス付きテーブルを含むデータセットの名前。
    • TABLE_NAME: ベクトル インデックスを含むテーブルの名前。

インデックスのあるテーブルが削除されると、そのインデックスは自動的に削除されます。

超低レイテンシのオンライン アプリケーションを有効にするには、Vertex AI ベクトル検索との BigQuery 統合を使用して、BigQuery エンベディングをベクトル検索にインポートし、低レイテンシ エンドポイントをデプロイします。詳細については、BigQuery からインデックス データをインポートするをご覧ください。

次のステップ