Skip to main content
多次元 (ベクトル) 空間において、ある点に対して最も近い N 個の点を見つける問題は、最近傍探索 (nearest neighbor search) 、または単にベクトル検索と呼ばれます。 ベクトル検索を実現する方法は、大きく 2 つあります。
  • 厳密ベクトル検索では、与えられた点とベクトル空間内のすべての点との距離を計算します。これにより、可能な限り最高の精度が得られ、返される点が真の最近傍であることが保証されます。ベクトル空間を総当たりで探索するため、厳密ベクトル検索は実運用では遅すぎる場合があります。
  • 近似ベクトル検索は、厳密ベクトル検索よりもはるかに高速に結果を求めるための手法群を指します (例: グラフやランダムフォレストのような特殊なデータ構造) 。結果の精度は通常、実用上は “十分良好” です。多くの近似手法では、結果精度と検索時間のトレードオフを調整するためのパラメータが用意されています。
ベクトル検索 (厳密または近似) は、SQL では次のように記述できます。
ベクトル空間内の点は、配列型のカラム vectors に格納されます。たとえば、Array(Float64)Array(Float32)、または Array(BFloat16) です。 参照ベクトルは定数配列で、共通テーブル式として指定します。 <DistanceFunction> は、参照点と格納されているすべての点の間の距離を計算します。 これには、利用可能な任意の距離関数を使用できます。 <N> は、返す近傍の数を指定します。 厳密ベクトル検索は、上記の SELECT クエリをそのまま実行することで行えます。 このようなクエリの実行時間は、一般に格納されているベクトルの数とその次元、つまり配列要素数に比例します。 また、ClickHouse はすべてのベクトルを総当たりでスキャンするため、実行時間はクエリで使用されるスレッド数にも依存します (max_threads 設定を参照) 。

戻り値

ベクトル類似度索引

ClickHouse には、近似ベクトル検索を実行するための特別な「ベクトル類似度」索引が用意されています。
ベクトル類似度索引は、ClickHouse バージョン 25.8 以降で利用できます。 問題が発生した場合は、ClickHouse リポジトリ で issue を登録してください。

ベクトル類似度索引の作成

新しいテーブルには、次のようにベクトル類似度索引を作成できます。
また、既存のテーブルにベクトル類似度索引を追加する場合は、次のようにします。
ベクトル類似度索引は、特殊なデータスキッピング索引の一種です (こちらおよびこちらを参照) 。 したがって、上記のALTER TABLEステートメントでは、今後テーブルに挿入される新しいデータに対してのみ索引が構築されます。 既存のデータに対しても索引を構築するには、これをマテリアライズする必要があります。
関数 <distance_function> には、次のいずれかを指定する必要があります。
  • L2Distance:ユークリッド空間における 2 点間を結ぶ線分の長さを表す ユークリッド距離
  • cosineDistance:2 つの非ゼロベクトルの間の角度を表す cosine distance
  • dotProduct:2 つのベクトルの要素ごとの積の総和を表す 内積 (inner product) 。正規化されたデータでは cosineDistance と等価です。
正規化されたデータでは通常 L2Distance が最適です。それ以外の場合は、スケールの違いを補正するために cosineDistance を推奨します。
距離関数 L2DistancecosineDistance では、値が小さいほど類似度が高くなります。一方、dotProduct では、値が大きいほど類似度が高くなります。 そのため、L2DistancecosineDistance を使用するベクトル索引は SELECT [...] ORDER BY [...] ASC クエリでのみ使用でき (ASCORDER BY のデフォルトです) 、dotProduct 用に構築されたベクトル索引は SELECT [...] ORDER BY [...] DESC クエリでのみ使用できます。
<dimensions> は、基になるカラム内の配列のカーディナリティ (要素数) を指定します。 ClickHouse が索引の作成中に異なるカーディナリティの配列を見つけた場合、その索引は破棄され、エラーが返されます。 省略可能な GRANULARITY パラメーター <N> は、インデックスグラニュールのサイズを表します (こちらを参照) 。 通常のスキップ索引ではデフォルトのインデックスグラニュールは 1 ですが、ベクトル類似度索引ではデフォルトのインデックスグラニュールとして 1 億を使用します。 この値により、大きなパーツであっても内部的に構築される索引の数を少なく抑えられます。 インデックスグラニュールを変更するのは、その影響を十分に理解している上級ユーザーに限ることを推奨します (下記を参照) 。 ベクトル類似度索引は、異なる近似検索手法に対応できるという意味で汎用的です。 実際に使用される手法は、パラメーター <type> で指定します。 現時点で利用できる手法は HNSW のみです (academic paper) 。これは、階層近傍グラフに基づく、近似ベクトル検索のための一般的かつ最先端の手法です。 型として HNSW を使用する場合、ユーザーは必要に応じて HNSW 固有の追加パラメーターを指定できます。
次のHNSW固有のパラメーターを使用できます。
  • <quantization> は、近傍グラフ内のベクトルの量子化を制御します。設定可能な値は f64f32f16bf16i8b1 です。デフォルト値は bf16 です。なお、このパラメーターは、基になるカラム内でのベクトル表現には影響しません。
  • <hnsw_max_connections_per_layer> は、グラフの各ノードの近傍数 (HNSW ハイパーパラメーター M とも呼ばれます) を制御します。デフォルト値は 32 です。値 0 はデフォルト値を使用することを意味します。
  • <hnsw_candidate_list_size_for_construction> は、HNSW グラフの構築時に使用される動的候補リストのサイズ (HNSW ハイパーパラメーター ef_construction とも呼ばれます) を制御します。デフォルト値は 128 です。値 0 はデフォルト値を使用することを意味します。
HNSW 固有パラメーターのデフォルト値は、ほとんどのユースケースで十分良好に機能します。 そのため、HNSW 固有パラメーターをカスタマイズすることは推奨していません。 さらに、次の制限があります。
  • ベクトル類似度索引は、型 Array(Float32)Array(Float64)、または Array(BFloat16) のカラムに対してのみ作成できます。Array(Nullable(Float32))Array(LowCardinality(Float32)) のような、Nullable や low-cardinality の float の Array は使用できません。
  • ベクトル類似度索引は、単一のカラムに対してのみ作成できます。
  • ベクトル類似度索引は計算式 (例: INDEX index_name arraySort(vectors) TYPE vector_similarity([...])) に対して作成することもできますが、そのような索引は後で近似近傍探索には使用できません。
  • ベクトル類似度索引では、基になるカラム内のすべての配列が <dimension> 個の要素を持っている必要があります。これは索引作成時に検査されます。この要件への違反をできるだけ早く検出するために、ユーザーはベクトルカラムに 制約 を追加できます。たとえば CONSTRAINT same_length CHECK length(vectors) = 256 です。
  • 同様に、基になるカラム内の配列値は空 ([]) であってはならず、デフォルト値 (これも []) であってもなりません。
ストレージ使用量とメモリ使用量の見積もり 一般的な AI モデル (例: Large Language Model、LLMs) で使用するために生成されるベクトルは、数百から数千の浮動小数点値で構成されます。 そのため、1 つのベクトル値であっても、複数キロバイトのメモリを消費することがあります。 テーブル内の基になるベクトルカラムに必要なストレージ量と、ベクトル類似度索引に必要なメインメモリ量を見積もりたい場合は、以下の 2 つの式を使用できます。 テーブル内のベクトルカラムのストレージ使用量 (非圧縮) :
dbpedia データセットの例:
検索を実行するには、ベクトル類似度索引をディスクから主記憶に完全に読み込む必要があります。 同様に、ベクトル索引もまずメモリ上に完全に構築してから、ディスクに保存されます。 ベクトル索引の読み込みに必要なメモリ使用量:
dbpedia データセットの例:
上記の式では、事前割り当てバッファやcacheなどの実行時データ構造をベクトル類似度索引が確保するために必要な追加メモリは考慮されていません。

ベクトル類似度索引を使う

ベクトル類似度索引を使用するには、設定 compatibility'' (デフォルト値) にするか、'25.1' 以降に設定する必要があります。
ベクトル類似度索引は、次の形式のSELECTクエリをサポートしています:
ClickHouseのクエリオプティマイザーは、上記のクエリテンプレートとの照合を試み、利用可能なベクトル類似度索引を活用します。 クエリがベクトル類似度索引を使用できるのは、SELECTクエリの距離関数と索引定義の距離関数が一致する場合に限ります。 上級ユーザーは、設定 hnsw_candidate_list_size_for_search (HNSWハイパーパラメーター “ef_search” とも呼ばれます) にカスタム値を指定することで、検索時の候補リストのサイズを調整できます (例:SELECT [...] SETTINGS hnsw_candidate_list_size_for_search = <value>) 。 この設定のデフォルト値は 256 であり、大多数のユースケースで適切に機能します。 設定値を大きくするほど精度は向上しますが、その分パフォーマンスは低下します。 クエリがベクトル類似度索引を使用できる場合、ClickHouse は SELECT クエリで指定された LIMIT <N> が妥当な範囲内にあるかを確認します。 具体的には、<N> が設定 max_limit_for_vector_search_queries の値 (デフォルト値は 100) を超えている場合、エラーが返されます。 LIMIT の値が大きすぎると検索が遅くなる可能性があり、通常は使用方法の誤りを示しています。 SELECT クエリがベクトル類似度索引を使用しているかどうかを確認するには、クエリの先頭に EXPLAIN indexes = 1 を追加します。 例として、次のクエリ
返す可能性があります
この例では、次元数1536のdbpediaデータセットのベクトル100万件が575個のグラニュールに格納されており、1グラニュールあたり約1.7k行になります。 クエリは10個の近傍を求めており、ベクトル類似度インデックスはそれら10個の近傍を10個の別々のグラニュールで見つけます。 これら10個のグラニュールはクエリ実行時に読み取られます。 出力に Skip およびベクトル索引の名前とタイプ (この例では idxvector_similarity) が含まれている場合、ベクトル類似度索引が使用されています。 この場合、ベクトル類似度索引は4つのgranuleのうち2つ、つまりデータの50%をスキップしました。 スキップできるgranuleが多いほど、索引の使用効率が向上します。
索引の使用を強制するには、設定 force_data_skipping_indexes を指定して SELECT クエリを実行できます (設定値として索引名を指定します) 。
ポストフィルタリングとプレフィルタリング ユーザーはオプションで、SELECT クエリに追加のフィルタ条件を含む WHERE 句を指定できます。 ClickHouse はこれらのフィルタ条件を、ポストフィルタリングまたはプリフィルタリングの戦略で評価します。 どちらの戦略も、フィルタが評価される順序を決定するものです。
  • ポストフィルタリングでは、まずベクトル類似度索引が評価され、その後 ClickHouse が WHERE 句で指定された追加のフィルタを評価します。
  • 事前フィルタリングとは、フィルタの評価順序が通常とは逆になることを意味します。
これらの戦略にはそれぞれ異なるトレードオフがあります。
  • ポストフィルタリングの一般的な問題は、LIMIT <N> 句で要求した行数より少ない結果しか返されない可能性があることです。これは、ベクトル類似度索引が返した結果行のうち1行以上が追加のフィルタを満たさない場合に発生します。
  • プリフィルタリングは、一般には未解決の問題です。一部の特化型ベクトルデータベースはプリフィルタリングのアルゴリズムを提供していますが、ほとんどのリレーショナルデータベース (ClickHouse を含む) では、正確な近傍探索、つまり索引を使わない総当たりスキャンにフォールバックします。
どの戦略が使用されるかは、フィルタ条件によって異なります。 追加フィルタがパーティションキーの一部である場合 追加のフィルタ条件がパーティションキーの一部である場合、ClickHouse はパーティションプルーニングを適用します。 例として、テーブルがカラム year で範囲パーティション分割されていて、次のクエリを実行するとします。
ClickHouse は、2025 年のものを除くすべてのパーティションをプルーニングします。 追加のフィルタは索引を使って評価できない 追加のフィルタ条件を索引 (主キー索引、スキッピング索引) で評価できない場合、ClickHouse はポストフィルタリングを適用します。 追加のフィルタは主キー索引を使って評価できる 追加のフィルタ条件を 主キー で評価できる場合 (つまり、それらが主キーのプレフィックスを構成する場合) で、かつ
  • フィルタ条件によってパート内で少なくとも 1 行が除外される場合、ClickHouse はそのパート内の「残った」範囲に対してプリフィルタリングにフォールバックします。
  • フィルタ条件によってパート内で 1 行も除外されない場合、ClickHouse はそのパートに対してポストフィルタリングを行います。
実運用のユースケースでは、後者のケースが発生する可能性はかなり低いです。 追加のフィルタはスキッピング索引を使って評価できる 追加のフィルタ条件を スキッピング索引 (minmax 索引、set 索引など) で評価できる場合、ClickHouse はポストフィルタリングを行います。 このような場合、ベクトル類似度索引は他のスキッピング索引と比べて最も多くの行を除外すると見込まれるため、最初に評価されます。 ポストフィルタリングとプリフィルタリングをより細かく制御するには、2 つの設定を使用できます。 設定 vector_search_filter_strategy (デフォルト: 上記のヒューリスティックを実装する auto) は prefilter に設定できます。 これは、追加のフィルタ条件の選択性が極めて高い場合に、プリフィルタリングを強制するのに役立ちます。 たとえば、次のクエリはプリフィルタリングの恩恵を受ける可能性があります。
2ドル未満の本がごくわずかしかないとすると、ポストフィルタリングでは 0 行しか返らない可能性があります。これは、ベクトル索引から返される上位 10 件の一致結果がすべて 2 ドルを超えている可能性があるためです。 プリフィルタリングを強制すると (クエリに SETTINGS vector_search_filter_strategy = 'prefilter' を追加) 、ClickHouse はまず価格が 2 ドル未満の本をすべて見つけ、その後、見つかった本に対して総当たりのベクトル検索を実行します。 上記の問題を解決する別の方法として、vector_search_index_fetch_multiplier (デフォルト: 1.0、最大: 1000.0) を 1.0 より大きい値 (たとえば 2.0) に設定することもできます。 ベクトル索引から取得する最近傍の数はこの設定値に応じて増加し、その後、それらの行に追加のフィルタが適用されて LIMIT 件の行が返されます。 たとえば、乗数を 3.0 にして再度クエリできます。
ClickHouse は各パートのベクトル索引から 3.0 x 10 = 30 件の最近傍を取得し、その後で追加のフィルタ条件を適用します。 返されるのは、そのうち最も近い 10 件の近傍のみです。 vector_search_index_fetch_multiplier を設定することでこの問題を軽減できますが、極端な場合 (WHERE 条件の選択性が非常に高い場合) には、要求した N 行より少ない行しか返されない可能性が依然としてあります。 再スコアリング ClickHouse のスキップ索引は通常、グラニュール単位でフィルタリングを行います。つまり、スキップ索引でのルックアップは (内部的には) 一致する可能性のあるグラニュールのリストを返し、後続のスキャンで読み込むデータ量を減らします。 これはスキップ索引全般では有効に機能しますが、ベクトル類似度索引では “granularity mismatch” を引き起こします。 もう少し詳しく言うと、ベクトル類似度索引は、指定された参照ベクトルに対して最も類似する N 個のベクトルの行番号を特定します。 設定 vector_search_with_rescoring = 1 では、ClickHouse は候補行の元のフル精度ベクトルを読み取り、通常の SQL パイプラインで最終的な距離を計算します。 クエリプランで可能な場合、ClickHouse は最終的な距離計算の前に、ベクトル索引が返した候補行にスキャンを絞り込みます。 このステップは rescoring と呼ばれ、特に量子化されたベクトル索引では精度を向上させることができます。これは、最終的なランキングで索引距離ではなく保存されたベクトルを使用するためです。 追加のフィルタによって候補が削られすぎる場合や、より高い再現率が必要な場合は、設定 vector_search_index_fetch_multiplier を増やして、ベクトル索引が rescoring 用により多くの候補行を返すようにしてください。 そこで ClickHouse は、rescoring を無効にして、最も類似するベクトルとその距離を索引から直接返す最適化を提供しています。 この最適化はデフォルトで有効になっています。設定 vector_search_with_rescoring を参照してください。 大まかな仕組みとしては、ClickHouse は最も類似するベクトルとその距離を仮想カラム _distance として利用できるようにします。 これを確認するには、EXPLAIN header = 1 を付けてベクトル検索クエリを実行します。
rescoring なし (vector_search_with_rescoring = 0) で実行し、かつ並列レプリカが有効なクエリでは、rescoring にフォールバックする場合があります。

パフォーマンスチューニング

圧縮の調整 ほぼすべてのユースケースで、基盤となるカラム内のベクトルは高密度であり、圧縮してもあまり効果がありません。 その結果、圧縮 を有効にすると、ベクトルカラムへの書き込みや読み取りが遅くなります。 そのため、圧縮は無効にすることを推奨します。 そのためには、次のようにベクトルカラムに CODEC(NONE) を指定します。
索引作成のチューニング ベクトル類似度索引のライフサイクルは、パーツのライフサイクルと連動しています。 つまり、ベクトル類似度索引が定義された新しいパーツが作成されるたびに、その索引も作成されます。 これは通常、データが挿入されるとき、またはマージ中に発生します。 残念ながら、HNSW は索引の作成に時間がかかることで知られており、挿入やマージを大幅に遅くする可能性があります。 ベクトル類似度索引は、データが不変である場合や、変更頻度が低い場合にのみ使用するのが理想的です。 索引作成を高速化するには、次の手法を使用できます。 まず、索引作成は並列化できます。 索引作成スレッドの最大数は、サーバー設定max_build_vector_similarity_index_thread_pool_sizeで設定できます。 最適な性能を得るには、この設定値を CPU コア数に合わせて設定してください。 次に、INSERT ステートメントを高速化するために、ユーザーはセッション設定materialize_skip_indexes_on_insertを使用して、新たに挿入されたパーツでのスキッピング索引の作成を無効にできます。 そのようなパーツに対する SELECT クエリは、厳密検索にフォールバックします。 挿入されたパーツはテーブル全体のサイズと比べて小さい傾向があるため、性能への影響はごくわずかであると見込まれます。 3 つ目に、マージを高速化するために、ユーザーはセッション設定materialize_skip_indexes_on_mergeを使用して、マージ後のパーツでのスキッピング索引の作成を無効にできます。 これにより、ステートメントALTER TABLE […] MATERIALIZE INDEX […]と組み合わせて、ベクトル類似度索引のライフサイクルを明示的に制御できます。 たとえば、すべてのデータが取り込まれるまで、または週末のようにシステム負荷が低い時間帯まで、索引作成を遅らせることができます。 索引利用のチューニング SELECT クエリでベクトル類似度索引を使用するには、それをメインメモリに読み込む必要があります。 同じベクトル類似度索引がメインメモリに繰り返し読み込まれるのを防ぐため、ClickHouse はこの種の索引専用のインメモリ cache を提供しています。 この cache が大きいほど、不要な読み込みは少なくなります。 cache の最大サイズは、サーバー設定vector_similarity_index_cache_sizeで設定できます。 デフォルトでは、cache は最大 5 GB まで拡張できます。 次のログメッセージ (system.text_log) は、ベクトル類似度索引が読み込まれていることを示します。 異なるベクトル検索クエリに対してこのようなメッセージが繰り返し表示される場合は、cache サイズが小さすぎることを示しています。
ベクトル類似度索引キャッシュには、ベクトル索引のインデックスグラニュールが格納されます。 個々のベクトル索引のインデックスグラニュールがキャッシュサイズより大きい場合は、キャッシュされません。 したがって、ベクトル索引のサイズ (「ストレージとメモリ消費量の見積もり」の式、または system.data_skipping_indices に基づく) を必ず計算し、それに応じてキャッシュサイズを設定してください。
ベクトル検索クエリが遅い場合は、まずベクトル索引キャッシュを確認し、必要に応じて増やすことが最初のステップであることを、改めて強調しておきます。 現在のベクトル類似度索引キャッシュのサイズは、system.metrics に表示されます。
特定のクエリIDを持つクエリのキャッシュヒット数とミス数は、system.query_log から取得できます。
本番環境でのユースケースでは、すべてのベクトル索引が常時メモリ上に保持されるよう、cache のサイズを十分に大きくすることを推奨します。 量子化の調整 量子化は、ベクトルのメモリ使用量と、ベクトル索引の構築および走査にかかる計算コストを削減する手法です。 ClickHouse のベクトル索引は、以下の量子化オプションをサポートしています。 量子化を行うと、元のフル精度の浮動小数点値 (f32) を用いた検索と比べて、ベクトル検索の精度は低下します。 ただし、ほとんどのデータセットでは、半精度の brain float 量子化 (bf16) による精度低下はごくわずかであるため、ベクトル類似度索引ではこの量子化手法がデフォルトで使用されます。 4分の1精度 (i8) およびバイナリ (b1) の量子化では、ベクトル検索の精度が目に見えて低下します。 これら 2 つの量子化は、ベクトル類似度索引のサイズが利用可能な DRAM 容量を大幅に上回る場合にのみ推奨します。 この場合は、精度を向上させるために、rescoring (vector_search_index_fetch_multipliervector_search_with_rescoring) も有効にすることを推奨します。 バイナリ量子化を推奨するのは、1) 正規化された埋め込み (つまりベクトル長 = 1。OpenAI のモデルは通常正規化されています) であり、かつ 2) 距離関数としてコサイン距離を使用する場合に限られます。 バイナリ量子化では、内部的にハミング距離を使用して近接グラフを構築し、検索を行います。 rescoring のステップでは、テーブルに格納された元のフル精度ベクトルを使用して、コサイン距離により最近傍を特定します。 データ転送の調整 ベクトル検索クエリにおける参照ベクトルはユーザーから与えられ、通常は大規模言語モデル (LLM) を呼び出して取得します。 ClickHouse でベクトル検索を実行する一般的な Python コードは、次のようになります
埋め込みベクトル (上記のスニペットの search_v) は、非常に大きな次元数になることがあります。 たとえば OpenAI は、1536 次元、さらには 3072 次元の埋め込みベクトルを生成するモデルを提供しています。 上記のコードでは、ClickHouse Python ドライバーが埋め込みベクトルを人間が読める文字列に変換し、その後 SELECT クエリ全体を文字列として送信します。 埋め込みベクトルが 1536 個の単精度浮動小数点値で構成されているとすると、送信される文字列の長さは 20 kB に達します。 その結果、トークン化、パース、および数千回に及ぶ文字列から浮動小数点値への変換によって、CPU 使用率が高くなります。 また、ClickHouse サーバーのログファイルにもかなりの容量が必要になり、system.query_log も肥大化します。 なお、ほとんどの LLM モデルは、埋め込みベクトルをネイティブな浮動小数点数のリストまたは NumPy 配列として返します。 そのため、Python アプリケーションでは、次のスタイルを使用して参照ベクトルのパラメータをバイナリ形式でバインドすることを推奨します。
この例では、参照ベクトルはバイナリ形式のまま送信され、サーバー上で浮動小数点数の配列として再解釈されます。 これにより、サーバー側のCPU時間を節約でき、サーバーログや system.query_log の肥大化も防げます。

管理と監視

ベクトル類似度索引のディスク上のサイズは、system.data_skipping_indices で確認できます。
出力例:

通常の スキッピング索引 との違い

通常の スキッピング索引 と同様に、ベクトル類似度索引は グラニュール 単位で構築され、各 index block は GRANULARITY = [N] 個の グラニュール で構成されます (通常の スキッピング索引 のデフォルトでは [N] = 1) 。 たとえば、table のプライマリインデックスの granularity が 8192 (設定 index_granularity = 8192) で、GRANULARITY = 2 の場合、各 index block には 16384 行が含まれます。 しかし、近似近傍探索のための data structure と algorithm は、本質的に row-oriented です。 これらは行の集合を compact に表現して保持し、ベクトル検索 queries に対して行を返します。 そのため、ベクトル類似度索引の動作には、通常の スキッピング索引 と比べてやや直感に反する違いがあります。 ユーザーが column にベクトル類似度索引を定義すると、ClickHouse は内部的に各 index block ごとにベクトル類似度の「sub-index」を作成します。 この sub-index は、自身が属する index block 内の行しか認識しないという意味で「local」です。 前述の例で、ある column に 65536 行あるとすると、4 つの index blocks (8 つの グラニュール にまたがる) と、各 index block に対応するベクトル類似度 sub-index が作成されます。 理論上、sub-index は、その index block 内で最も近い N 個の points に対応する行を直接返せます。 vector_search_with_rescoring = 1 のクエリでは、クエリプラン がこの最適化を許可している場合、ClickHouse はこれらの行位置を使用して、保存されているベクトルから最終的な距離を計算する前に行を filter できます。 rescoring を行わない場合、ClickHouse は virtual column _distance を介してベクトル索引からの距離を直接使用します。 どちらのモードでも、読み取りのスケジューリングには周囲の グラニュール 範囲が引き続き使用されます。これは、通常の スキッピング索引 が index block 単位で data をスキップするのとは異なります。 GRANULARITY parameter は、作成されるベクトル類似度 sub-index の数を決定します。 GRANULARITY の値が大きいほど、ベクトル類似度 sub-index の数は少なくなりますが、それぞれは大きくなり、最終的には column (または column の data part) に sub-index が 1 つだけになる場合もあります。 その場合、その sub-index は column のすべての行を「global」に把握できるため、関連する行を含む column (part) の グラニュール を直接すべて返せます (そのような グラニュール の数は最大でも LIMIT [N] 個です) 。 vector_search_with_rescoring = 1 の場合、ClickHouse はその後、該当する行位置を読み取り、それらの行について正確な距離を計算できます。 GRANULARITY の値が小さい場合は、各 sub-index が最大 LIMIT N 個の candidate rows を返せます。 その結果、より多くの candidate rows を読み込んで後段で filter する必要が生じる場合があります。 どちらの場合でも検索精度は同等で、異なるのは processing 性能だけである点に注意してください。 一般に、ベクトル類似度索引では大きな GRANULARITY を使用し、ベクトル類似度 structure の memory consumption が大きすぎるといった問題がある場合にのみ、より小さい GRANULARITY の値を使うことが推奨されます。 ベクトル類似度索引で GRANULARITY が指定されていない場合、デフォルト値は 1 億です。

クエリ:
Query
Response
近似ベクトル検索を使用する、そのほかのサンプルデータセット:

量子化コーデックを使用したベクトル検索

Quantized コーデックは実験的機能です。SET allow_experimental_codecs = 1 を設定して有効にしてください。 問題が発生した場合は、ClickHouseリポジトリで issue を作成してください。

はじめに

ベクトル類似度索引 は、グラフをたどって最近傍クエリに応答する方式で、グラフ全体をメモリ上に保持できる場合には非常に高い性能を発揮します。 ただし、適用範囲を制限する条件が 2 つあります。
  • スケール。 ベクトル自体に加えて、グラフの構築にかかる時間と、それを格納するために必要なメモリが支配的なコストになります。
  • フィルタリング。 選択性の高い WHERE フィルタがあると、グラフ走査は効果的ではなくなります。述語を満たす少数の行に到達できないか、それらを見つけるために不釣り合いに多くの候補を調べる必要があるためです。
これに対して、総当たりスキャンにはこうした制約がありません。補助構造は不要で、パーツは連結によってマージされ、フィルタは単にスキャン対象の行数を減らすだけです。 唯一の欠点は、読み取る必要があるデータ量です。フル Float32 精度で格納されたベクトルをスキャンする場合、ストレージ I/O が支配的になります。ベクトル列全体をディスク (またはオブジェクトストレージ) から読み取る必要があるためです。高密度な埋め込みカラムでは、これはテーブル内で最大のカラムであり、圧縮効率も高くありません。 Quantized カラム コーデック は、この欠点に対処します。 各ベクトルは 2 つの形で格納されます。元のフル精度の値はそのまま保持しつつ、付随するストリームに compact な 量子化コード も保存します。 ベクトル検索クエリでは、まず低コストで SIMD に適した距離関数を使ってコードをスキャンし、最も有望な候補の ショートリスト を作成します。その後、その ショートリスト をフル精度ベクトルに対して再ランキングします。 コードは生のベクトルよりもはるかに小さいため、ショートリスト のスキャンでストレージから読み取るバイト数を大幅に減らせます。また、フル精度カラムにアクセスするのは ショートリスト に残った少数の候補だけで済む一方、最終的なランキング精度は維持されます。

コーデックの宣言

Array(Float32) (または Array(Float64) / Array(BFloat16)) カラムに Quantized(...) コーデックを設定します。 このコーデックは実験的機能のため、まず allow_experimental_codecs を有効にします:
フル精度のデータは通常どおり保存され、コーデックは補助的なコードストリームを追加するだけです。 コーデックはテーブル作成時に固定されるため、ALTER TABLE で追加したり変更したりすることはできません。

量子化方式

各方式は、サイズ / 精度 / 距離指標のトレードオフにおいて、それぞれ異なる特性を持ちます。dimensions 引数はベクトル長です。
  • Quantized('rabitq', dimensions) — 各座標に 1 ビットの符号ビットと、バイアスのないコサイン補正係数 (dimensions/8 + 4 バイト) を持ちます。小さく、popcount での計算負荷が低い、強力なデフォルトです。cosineDistance のみ対応。
  • Quantized('turboquant', dimensions) — 各座標に 2 ビット (1 ビットの MSE コードと 1 ビットの残差コード) を使い、より高忠実度な候補を実現します (dimensions/4 + 4 バイト) 。cosineDistance のみ対応。
  • Quantized('int8', dimensions) — 各座標に 1 つの Int8 コードとベクトルノルムを持ちます (dimensions + 4 バイト) 。サイズは最大ですが、最も忠実なフラットコードです。L2DistancecosineDistance に対応します。
  • Quantized('prefix', dimensions, leading_dimensions, 'int8'|'bf16') — Matryoshka: 先頭の leading_dimensions 個の座標だけを、Int8 (ベクトルごとの scale 付き) または BFloat16 として保持します。Matryoshka Representation Learning で学習した埋め込み向けの非常に小さなコードです。L2DistancecosineDistance に対応します。
  • Quantized('product', dimensions, nbits, m) — Product Quantization: パーツごとのコードブックを k-means で学習し、各ベクトルを nbits ビットの m 個のコードに変換します (そのため dimensionsm の倍数である必要があります) 。最もコンパクトな選択肢で、1 バイトあたりの再現率も最も高い一方、insert 時に学習ステップが必要です。L2DistancecosineDistance に対応します。
rabitqturboquant では、dimensions は 8 の倍数である必要があります。

通常どおりに検索する

特別なクエリ構文は不要で、完全一致検索で使うのと同じ top-k クエリを記述します。
vector_search_use_quantized_codes = 1 を使用すると、オプティマイザはクエリを自動的に 2 段階のプランに書き換えます。まず量子化されたコードをスキャンしてショートリストを作成し、その後そのショートリストをフル精度の vec に対して再スコアリングします。 この設定はデフォルトでオフになっているため、有効にしない限り、同じクエリは通常の厳密スキャンとして実行されます。コーデック が結果を変えることはなく、明示的に有効化した場合にのみ、より高速な経路を提供します。 選択したメソッドがサポートする距離関数を使用してください。すべてのメソッドでは cosineDistanceint8prefixproduct では追加で L2Distance を使用できます。

設定

  • allow_experimental_codecsQuantized コーデックを宣言するには、有効になっている必要があります (デフォルト: 0) 。
  • vector_search_use_quantized_codes — ショートリスト化と再スコアリングからなる 2 段階リライトを有効にします (デフォルト: 0) 。オフの場合、該当するクエリはフル精度ベクトルを厳密にスキャンします。
  • vector_search_index_fetch_multiplier — クエリの LIMIT に対して、候補をどれだけショートリスト化するかを指定します。スキャンでは、再スコアリングの前に上位 LIMIT × multiplier 件の code を保持します。値を大きくすると、再スコアリングのコストは増えますが、再現率は向上します。デフォルトは 1 (オーバーサンプリングなし) なので、良好な再現率を得るには、通常はこれをたとえば 10 以上に引き上げる必要があります。

スケールを前提に設計

このコーデックが ClickHouse に適しているのは、コストの高い処理であるスキャンが、まさに ClickHouse engine の得意分野だからです。
  • ベクトル化。 スキャンカーネルは SIMD 向けに実装されており、CPU がサポートする最も広い命令セットにランタイムディスパッチされます。具体的には、符号コード方式 (rabitqturboquant) ではハードウェア popcount を使い、それ以外では広幅の fused-multiply-add を使います。
  • コアとパーツ全体で並列処理。 フラットスキャンは本質的に並列化しやすく、ClickHouse もそのように扱います。距離計算は利用可能なすべての threads とテーブル内のすべての パーツにわたって同時に実行され、直列化されるのは最後の top-k マージだけです。
  • 分散。 分片化されたクラスターでは、処理は各マシンに分散されます。各分片がそれぞれの slice を並列にスキャンし、マージコーディネーターがショートリストをマージします。
  • 列指向でフィルターとの相性がよい。 量子化コードは専用のカラムに格納され、圧縮されたうえで他のすべてのカラムと同じ I/O パスで読み込まれるため、選択性の高い WHERE を使えば、スキャンするコード数がその分減るだけです。
  • 別途 build は不要。 コードはベクトルの書き込みと同時に生成され、連結によってマージされます。構築・調整・再構築が必要な索引はないため、テーブルはデータが書き込まれた時点ですぐに検索可能になります。
コードはあくまで候補生成に使われるだけで、保持されているフル精度のカラムが最終的な正確な ランキング を担います。

量子化ビット (QBit)

厳密ベクトル検索を高速化する一般的な方法の 1 つは、より低精度の float data type を使用することです。 たとえば、ベクトルを Array(Float32) ではなく Array(BFloat16) として保存すると、データサイズは半分になり、クエリの実行時間もそれに応じて短くなることが期待されます。 この手法は量子化として知られています。計算は高速になりますが、すべてのベクトルを総当たりで走査していても、結果の精度が低下する可能性があります。 従来の量子化では、検索時とデータ保存時の両方で精度が失われます。上の例では、Float32 ではなく BFloat16 を保存することになるため、あとから必要になっても、より高精度な検索は実行できません。別の方法として、量子化済みデータとフル精度データの 2 つのコピーを保存するやり方があります。これは機能しますが、余分なストレージが必要です。たとえば、元データが Float64 で、異なる精度 (16 ビット、32 ビット、または完全な 64 ビット) で検索を実行したいケースを考えてみましょう。この場合、データのコピーを 3 つ別々に保存する必要があります。 ClickHouse は、こうした制約に対処する Quantized Bit (QBit) データ型を提供しており、次のことが可能です。
  1. 元のフル精度データを保存する。
  2. 量子化の精度をクエリ時に指定する。
これは、データをビットごとにグループ化した形式 (つまり、すべてのベクトルの i 番目のビットをまとめて保存する形式) で保存することで実現され、必要な精度レベルでのみ読み取れるようになります。これにより、必要に応じて元のデータをすべて利用できる状態を保ちながら、量子化による I/O と計算量の削減による高速化のメリットを得られます。最大精度を選択した場合、検索は厳密になります。 QBit 型のカラムを宣言するには、次の構文を使用します。
ここで:
  • element_type – 各ベクトル要素の型。サポートされている型は Int8BFloat16Float32Float64 です
  • dimension – 各ベクトルの要素数
  • stride – 任意。dimension の約数で、次元を dimension / stride 個の連続したグループに分割し、それぞれを別々のストリームに格納します。これにより、先頭側の次元だけを対象とする検索では、読み取るストリーム数を減らせます (Matryoshka 埋め込みで有用です)。既定値は dimension で、この場合、この型はストライドなしの QBit とバイト単位で同一です。詳細は QBit data type page を参照してください。

QBit テーブルの作成とデータの追加

L2距離を使って、単語「lemon」を表すベクトルの最近傍を見つけてみましょう。距離関数の3番目のパラメータでは、ビット単位の精度を指定します。値が大きいほど精度は高くなりますが、そのぶん必要な計算量も増えます。 QBit で使用できる距離関数の一覧は、こちらで確認できます。 フル精度検索 (64ビット) :
低精度検索:
12ビットの量子化では、距離を十分に良好に近似しつつ、クエリをより高速に実行できることがわかります。相対的な順序もおおむね保たれており、‘apple’ が引き続き最も近い一致です。

パフォーマンスに関する考慮事項

QBit のパフォーマンス上の利点は、精度を下げるほどストレージから読み込むデータ量が減り、I/O 操作が少なくなることにあります。さらに、QBitFloat32 データが含まれている場合、精度パラメーターが 16 以下であれば、計算量の削減によるさらなる効果も得られます。精度パラメーターは、精度と速度のトレードオフを直接左右します。
  • より高い精度 (元のデータ幅に近い) : 結果はより正確になりますが、クエリは遅くなります
  • より低い精度: 近似結果になる代わりにクエリが高速化し、メモリ使用量も削減されます

参考資料

ブログ:
最終更新日 2026年7月23日