概要
生成AI アプリケーションにおいて、大規模モデルの内部パラメータ「記憶」のみに依存することには明確な限界があります: (1) モデルの知識が古くなり、最新の情報をカバーできない; (2) モデルに直接答えを「生成」させることで、幻覚のリスクが増大する。これがRAG (Retrieval-Augmented Generation) を生み出します。RAG の主要な課題は、モデルに何もないところから答えをでっち上げさせることではなく、外部の知識ベースから最も関連性の高い Top-K の情報チャンクを取得し、それらを根拠となるコンテキストとしてモデルに供給することです。
これを実現するために、ユーザークエリと知識ベース内の文書間の意味的関連性を測定するメカニズムが必要です。ベクトル表現は標準的なツールです: クエリと文書の両方を意味ベクトルにエンコードすることで、ベクトル類似度を使用して関連性を測定できます。事前訓練済み言語モデルの進歩により、高品質な埋め込みの生成が主流となっています。したがって、RAG の検索段階は典型的なベクトル類似検索問題となります: 大きなベクトルコレクションから、クエリに最も類似した K 個のベクトル(つまり、候補知識断片)を見つけます。
RAG におけるベクトル検索はテキストに限定されず、自然にマルチモーダルシナリオに拡張されます。マルチモーダル RAG システムでは、画像、音声、動画、その他のデータタイプもベクトルにエンコードして検索し、その後生成モデルにコンテキストとして提供できます。例えば、ユーザーが画像をアップロードした場合、システムは最初に関連する説明や知識スニペットを検索し、その後説明的なコンテンツを生成できます。医療 QA では、RAG は患者記録と文献を検索して、より正確な診断提案をサポートできます。
Approximate Nearest Neighbor Search
バージョン 4.0 から、Apache Doris は ANN search を正式にサポートしています。追加のデータタイプは導入されません: ベクトルは固定長配列として格納されます。距離ベースのインデクシングでは、Faiss をベースとした新しいインデックスタイプ ANN が実装されています。
一般的な SIFT データセットを例として、次のようなテーブルを作成できます:
CREATE TABLE sift_1M (
id int NOT NULL,
embedding array<float> NOT NULL COMMENT "",
INDEX ann_index (embedding) USING ANN PROPERTIES(
"index_type"="hnsw",
"metric_type"="l2_distance",
"dim"="128",
"quantizer"="flat"
)
) ENGINE=OLAP
DUPLICATE KEY(id) COMMENT "OLAP"
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1"
);
- index_type:
hnsw(Hierarchical Navigable Small World)、ivf(inverted file)、またはivf_on_disk(転置リストをディスクに格納し、キャッシュ経由で提供するIVF) - metric_type:
l2_distanceは距離関数としてL2距離を使用することを意味します - dim:
128はベクトル次元が128であることを意味します - quantizer:
flatは各ベクトル次元が元のfloat32として格納されることを意味します
| パラメータ | 必須 | サポート/オプション | デフォルト | 説明 |
|---|---|---|---|---|
index_type | はい | hnsw, ivf, ivf_on_disk | (なし) | ANNインデックスアルゴリズム。HNSW、インメモリIVF、およびIVF On-Diskをサポートします。 |
metric_type | はい | l2_distance, inner_product | (なし) | ベクトル類似度/距離メトリック。L2 = ユークリッド距離;inner_productはベクトルが正規化されている場合、コサイン類似度を近似できます。 |
dim | はい | 正の整数(> 0) | (なし) | ベクトル次元。インポートされるすべてのベクトルが一致する必要があり、そうでなければエラーが発生します。 |
nlist | いいえ | 正の整数 | 1024 | IVF転置リスト数。index_type=ivfまたはindex_type=ivf_on_diskの場合に有効;大きな値は再現率/速度のトレードオフを改善する可能性がありますが、構築オーバーヘッドが増加します。 |
max_degree | いいえ | 正の整数 | 32 | HNSW M(ノードあたりの最大近傍数)。インデックスメモリと検索パフォーマンスに影響します。 |
ef_construction | いいえ | 正の整数 | 40 | HNSW efConstruction(構築時の候補キューサイズ)。大きいほど品質が向上しますが、構築が遅くなります。 |
quantizer | いいえ | flat, sq8, sq4, pq | flat | ベクトルエンコーディング/量子化:flat = 生データ;sq8/sq4 = スカラー量子化(8/4ビット)、pq = メモリを削減するプロダクト量子化。 |
pq_m | 'quantizer=pq'の場合必須 | 正の整数 | (なし) | 使用するサブベクトル数を指定します(ベクトル次元dimはpq_mで割り切れる必要があります)。 |
pq_nbits | 'quantizer=pq'の場合必須 | 正の整数 | (なし) | 各サブベクトルを表現するために使用されるビット数。faissではpq_nbitsは一般的に24以下である必要があります。 |
コサイン類似度が必要な場合
DorisのANNインデックスmetric_typeはl2_distanceとinner_productをサポートしますが、cosineを直接はサポートしません。
ビジネスメトリックがコサイン類似度の場合、推奨されるアプローチは:
- データを書き込む前にベクトルを単位長にL2正規化します。
metric_type="inner_product"でANNインデックスを構築します。inner_product_approximate(...)とORDER BY ... DESCでクエリします。
DDLの例:
CREATE INDEX idx_emb_cosine ON your_table (embedding) USING ANN PROPERTIES (
"index_type"="hnsw",
"metric_type"="inner_product",
"dim"="768"
);
なぜこれが機能するのか:
- コサイン類似度:
cos(x, y) = (x · y) / (||x|| ||y||) - L2正規化後 (
||x|| = ||y|| = 1):cos(x, y) = x · y
つまり、コサイン類似度の最大化は、正規化されたベクトルの内積の最大化と等価です。 ベクトルが正規化されていない場合、内積はもはやコサインと等価ではありません。
S3 TVF経由でインポート:
INSERT INTO sift_1M
SELECT *
FROM S3(
"uri" = "https://selectdb-customers-tools-bj.oss-cn-beijing.aliyuncs.com/sift_database.tsv",
"format" = "csv");
SELECT count(*) FROM sift_1M
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
l2_distance_approximate / inner_product_approximateを使用するとANNインデックスパスがトリガーされます。関数はインデックスのmetric_typeと正確に一致する必要があります(例:metric_type=l2_distance → l2_distance_approximateを使用、metric_type=inner_product → inner_product_approximateを使用)。順序について:L2は距離の昇順を使用し(小さいほど近い)、inner productはスコアの降順を使用します(大きいほど近い)。
SELECT id,
l2_distance_approximate(
embedding,
[0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2]
) AS distance
FROM sift_1M
ORDER BY distance
LIMIT 10;
--------------
+--------+----------+
| id | distance |
+--------+----------+
| 178811 | 210.1595 |
| 177646 | 217.0161 |
| 181997 | 218.5406 |
| 181605 | 219.2989 |
| 821938 | 221.7228 |
| 807785 | 226.7135 |
| 716433 | 227.3148 |
| 358802 | 230.7314 |
| 803100 | 230.9112 |
| 866737 | 231.6441 |
+--------+----------+
10 rows in set (0.02 sec)
正確なグラウンドトゥルースと比較するには、l2_distance または inner_product(_approximate サフィックスなし)を使用します。この例では、完全検索は約290ミリ秒かかります:
10 rows in set (0.29 sec)
ANNインデックスを使用することで、この例ではクエリのレイテンシが約290msから約20msに短縮されます。
ANNインデックスはセグメント単位で構築されます。分散テーブルでは、各セグメントがローカルのTopNを返し、TopNオペレータがタブレットやセグメント間の結果をマージして、グローバルなTopNを生成します。
順序に関する注意事項:
metric_type = l2_distanceの場合、距離が小さいほど近いベクトルを表すため、ORDER BY dist ASCを使用します。metric_type = inner_productの場合、値が大きいほど近いベクトルを表すため、インデックスを通じてTopNを取得するにはORDER BY dist DESCを使用します。
近似範囲検索
一般的なTopN最近傍検索(最も近いN件のレコードを返す)以外に、もう一つの典型的なパターンとして閾値ベースの範囲検索があります。固定された数の結果を返すのではなく、対象ベクトルまでの距離が述語(>、>=、<、<=)を満たすすべての点を返します。例えば、距離が閾値より大きいまたは小さいベクトルを求める場合があります。これは「十分に類似している」または「十分に非類似である」候補が必要な場合に有用です。推薦システムでは、多様性を向上させるために近いが同一でないアイテムを取得する場合があり、異常検知では正常分布から遠い点を探します。
SQLの例:
SELECT count(*)
FROM sift_1M
WHERE l2_distance_approximate(
embedding,
[0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2])
> 300
--------------
+----------+
| count(*) |
+----------+
| 999271 |
+----------+
1 row in set (0.19 sec)
これらの範囲ベースのベクトル検索もANNインデックスによって高速化されます。インデックスがまず候補を絞り込み、その後近似距離が計算されることで、コストを削減し、レイテンシーを改善します。サポートされる述語:>、>=、<、<=。
Compound Search
Compound Searchは、同一のSQL文でANN TopN検索と範囲述語を組み合わせ、距離制約も満たすTopN結果を返します。
SELECT id,
l2_distance_approximate(
embedding, [0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2]) as dist
FROM sift_1M
WHERE l2_distance_approximate(
embedding, [0,11,77,24,3,0,0,0,28,70,125,8,0,0,0,0,44,35,50,45,9,0,0,0,4,0,4,56,18,0,3,9,16,17,59,10,10,8,57,57,100,105,125,41,1,0,6,92,8,14,73,125,29,7,0,5,0,0,8,124,66,6,3,1,63,5,0,1,49,32,17,35,125,21,0,3,2,12,6,109,21,0,0,35,74,125,14,23,0,0,6,50,25,70,64,7,59,18,7,16,22,5,0,1,125,23,1,0,7,30,14,32,4,0,2,2,59,125,19,4,0,0,2,1,6,53,33,2])
> 300
ORDER BY dist limit 10
--------------
+--------+----------+
| id | dist |
+--------+----------+
| 243590 | 300.005 |
| 549298 | 300.0317 |
| 429685 | 300.0533 |
| 690172 | 300.0916 |
| 123410 | 300.1333 |
| 232540 | 300.1649 |
| 547696 | 300.2066 |
| 855437 | 300.2782 |
| 589017 | 300.3048 |
| 930696 | 300.3381 |
+--------+----------+
10 rows in set (0.12 sec)
重要な問題は、述語フィルタリングがTopNの前に発生するか後に発生するかです。述語が最初にフィルタリングし、TopNが縮小されたセットに適用される場合、これは事前フィルタリングです。そうでなければ、事後フィルタリングです。事後フィルタリングはより高速ですが、再現率を劇的に低下させる可能性があります。Dorisは再現率を保持するために事前フィルタリングを使用します。
Dorisはインデックスで両方のフェーズを高速化できます。ただし、第1フェーズ(範囲フィルタ)の選択性が高すぎる場合、両方のフェーズをインデックス化すると再現率に悪影響を与える可能性があります。Dorisは述語の選択性とインデックスタイプに基づいて、インデックスを2回使用するかどうかを適応的に決定します。
追加フィルタを使用したANN検索
これは、ANN TopNの前に他の述語を適用し、それらの制約下でTopNを返すことを指します。
小さな8次元ベクトルとテキストフィルタを使用した例:
CREATE TABLE ann_with_fulltext (
id int NOT NULL,
embedding array<float> NOT NULL,
comment String NOT NULL,
value int NULL,
INDEX idx_comment(`comment`) USING INVERTED PROPERTIES("parser" = "english") COMMENT 'inverted index for comment',
INDEX ann_embedding(`embedding`) USING ANN PROPERTIES("index_type"="hnsw","metric_type"="l2_distance","dim"="8")
) DUPLICATE KEY (`id`)
DISTRIBUTED BY HASH(`id`) BUCKETS 1
PROPERTIES("replication_num"="1");
サンプルデータを挿入し、commentに"music"が含まれる行のみを検索する:
INSERT INTO ann_with_fulltext VALUES
(1, [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8], 'this is about music', 10),
(2, [0.2,0.1,0.5,0.3,0.9,0.4,0.7,0.1], 'sports news today', 20),
(3, [0.9,0.8,0.7,0.6,0.5,0.4,0.3,0.2], 'latest music trend', 30),
(4, [0.05,0.06,0.07,0.08,0.09,0.1,0.2,0.3], 'politics update',40);
SELECT id, comment,
l2_distance_approximate(embedding, [0.1,0.1,0.2,0.2,0.3,0.3,0.4,0.4]) AS dist
FROM ann_with_fulltext
WHERE comment MATCH_ANY 'music' -- Filter using inverted index
ORDER BY dist ASC -- Ann topn calculation after predicates evaluate.
LIMIT 2;
+------+---------------------+----------+
| id | comment | dist |
+------+---------------------+----------+
| 1 | this is about music | 0.663325 |
| 3 | latest music trend | 1.280625 |
+------+---------------------+----------+
2 rows in set (0.04 sec)
TopNがベクトルインデックスによって高速化されることを保証するため、すべての述語列には適切なセカンダリインデックス(例:転置インデックス)が必要です。
ANN検索に関連するセッション変数
HNSWのビルド時パラメータに加えて、セッション変数を介して検索時パラメータを渡すことができます:
- hnsw_ef_search: EF検索パラメータ。候補キューの最大長を制御します;大きいほど精度が高くなりますが、レイテンシも高くなります。デフォルトは32です。
- hnsw_check_relative_distance: 精度を向上させるために相対距離チェックを有効にするかどうか。デフォルトはtrueです。
- hnsw_bounded_queue: パフォーマンスを最適化するために境界付き優先キューを使用するかどうか。デフォルトはtrueです。
ベクトル量子化
FLATエンコーディングでは、HNSWインデックス(生ベクトルとグラフ構造)が大量のメモリを消費する可能性があります。HNSWが機能するためには完全にメモリに常駐している必要があるため、大規模環境ではメモリがボトルネックになる可能性があります。
スカラー量子化(SQ)はfloat32ストレージを圧縮してメモリを削減します。プロダクト量子化(PQ)は高次元ベクトルをより小さなサブベクトルに圧縮し、各サブベクトルを独立して量子化することでメモリオーバーヘッドを削減します。スカラー量子化について、Dorisは現在2つのスカラー量子化スキームをサポートしています:INT8とINT4(SQ8 / SQ4)。SQ8を使用する例:
CREATE TABLE sift_1M (
id int NOT NULL,
embedding array<float> NOT NULL COMMENT "",
INDEX ann_index (embedding) USING ANN PROPERTIES(
"index_type"="hnsw",
"metric_type"="l2_distance",
"dim"="128",
"quantizer"="sq8"
)
) ENGINE=OLAP
DUPLICATE KEY(id) COMMENT "OLAP"
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1"
);
768次元のCohere-MEDIUM-1MおよびCohere-LARGE-10Mデータセットにおいて、SQ8はFLATと比較してインデックスサイズを約3分の1に削減します。
| Dataset | Dim | Storage/Index Scheme | Total Disk | Data Part | Index Part | Notes |
|---|---|---|---|---|---|---|
| Cohere-MEDIUM-1M | 768D | Doris (FLAT) | 5.647 GB (2.533 + 3.114) | 2.533 GB | 3.114 GB | 1M vectors |
| Cohere-MEDIUM-1M | 768D | Doris SQ INT8 | 3.501 GB (2.533 + 0.992) | 2.533 GB | 0.992 GB | INT8 symmetric quantization |
| Cohere-MEDIUM-1M | 768D | Doris PQ(pq_m=384,pq_nbits=8) | 3.149 GB (2.535 + 0.614) | 2.535 GB | 0.614 GB | product quantization |
| Cohere-LARGE-10M | 768D | Doris (FLAT) | 56.472 GB (25.328 + 31.145) | 25.328 GB | 31.145 GB | 10M vectors |
| Cohere-LARGE-10M | 768D | Doris SQ INT8 | 35.016 GB (25.329 + 9.687) | 25.329 GB | 9.687 GB | INT8 quantization |
量子化は、各距離計算で量子化された値をデコードする必要があるため、追加のビルド時間オーバーヘッドが発生します。128次元ベクトルの場合、ビルド時間は行数と共に増加し、SQとFLATの比較でビルド時間が最大約10倍遅くなることがあります。
同様に、Dorisはproduct quantizationもサポートしていますが、PQを使用する場合は追加のパラメータを提供する必要があることに注意してください:
pq_m:元の高次元ベクトルを分割するサブベクトルの数を示します(ベクトル次元dimはpq_mで割り切れる必要があります)。pq_nbits:各サブベクトル量子化のビット数を示し、各サブスペースコードブックのサイズを決定します。faissではpq_nbitsは一般的に24以下である必要があります。
PQ量子化は訓練中に十分なデータが必要であり、訓練ポイント数はクラスタ数以上である必要があります(n >= 2 ^ pq_nbits)。
CREATE TABLE sift_1M (
id int NOT NULL,
embedding array<float> NOT NULL COMMENT "",
INDEX ann_index (embedding) USING ANN PROPERTIES(
"index_type"="hnsw",
"metric_type"="l2_distance",
"dim"="128",
"quantizer"="pq", -- Specify using PQ for quantization
"pq_m"="2", -- Required when using PQ, indicates splitting high-dimensional vector into pq_m low-dimensional sub-vectors
"pq_nbits"="2" -- Required when using PQ, indicates the number of bits for each subspace codebook
)
) ENGINE=OLAP
DUPLICATE KEY(id) COMMENT "OLAP"
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"replication_num" = "1"
);

パフォーマンスチューニング
ベクトル検索は典型的なセカンダリインデックスのポイントルックアップシナリオです。高QPSと低レイテンシーを実現するには、以下を検討してください:
チューニングにより、ハードウェア FE 32C 64GB + BE 32C 64GB において、Dorisは3000+のQPSを達成できます(データセット:Cohere-MEDIUM-1M)。
クエリパフォーマンス
| 同時実行数 | スキーム | QPS | 平均レイテンシー (s) | P99 (s) | CPU使用率 | Recall |
|---|---|---|---|---|---|---|
| 240 | Doris | 3340.4399 | 0.071368168 | 0.163399825 | 40% | 91.00% |
| 240 | Doris SQ INT8 | 3188.6359 | 0.074728852 | 0.160370195 | 40% | 88.26% |
| 240 | Doris SQ INT4 | 2818.2291 | 0.084663868 | 0.174826815 | 43% | 80.38% |
| 240 | Doris brute force | 3.6787 | 25.554878826 | 29.363227973 | 100% | 100.00% |
| 480 | Doris | 4155.7220 | 0.113387271 | 0.261086075 | 60% | 91.00% |
| 480 | Doris SQ INT8 | 3833.1130 | 0.123040214 | 0.276912867 | 50% | 88.26% |
| 480 | Doris SQ INT4 | 3431.0538 | 0.137636995 | 0.281631249 | 57% | 80.38% |
| 480 | Doris brute force | 3.6787 | 25.554878826 | 29.363227973 | 100% | 100.00% |
Prepared Statementの使用
最新の埋め込みモデルは多くの場合768次元以上のベクトルを出力します。768次元のリテラルをSQLにインラインで記述すると、パース時間が実行時間を超える可能性があります。Prepared Statementを使用してください。現在DorisはMySQLクライアントのprepareコマンドを直接サポートしていないため、JDBCを使用してください:
- JDBC URLでサーバーサイドprepared statementを有効化:
jdbc:mysql://127.0.0.1:9030/demo?useServerPrepStmts=true - プレースホルダー(
?)付きのPreparedStatementを使用し、再利用する。
セグメント数の削減
ANNインデックスはセグメントごとに構築されます。セグメントが多すぎるとオーバーヘッドが発生します。理想的には、各タブレットはANNインデックス付きテーブルに対して約5個以下のセグメントを持つべきです。be.confでwrite_buffer_sizeとvertical_compaction_max_segment_sizeを調整してください(例:両方とも10737418240)。
Rowset数の削減
セグメント削減と同じ動機:スケジューリングオーバーヘッドを最小化します。各ロードはrowsetを作成するため、バッチ取り込みにはstream loadまたはINSERT INTO SELECTを推奨します。
ANNインデックスをメモリに保持
現在のANNアルゴリズムはメモリベースです。セグメントのインデックスがメモリにない場合、ディスクI/Oが発生します。ANNインデックスを常駐させるために、be.confでenable_segment_cache_prune=falseを設定してください。
parallel_pipeline_task_num = 1
ANN TopNクエリは各スキャナーから非常に少ない行を返すため、高いパイプラインタスク並列性は不要です。parallel_pipeline_task_num = 1を設定してください。
enable_profile = false
超低レイテンシーが重要なクエリでは、クエリプロファイリングを無効化してください。
Python SDK
AIの時代において、Pythonはデータ処理とインテリジェントアプリケーション開発の主流言語となっています。開発者がPython環境でDorisのベクトル検索機能をより簡単に使用できるように、コミュニティの貢献者がDoris用のPython SDKを開発しています。
- https://github.com/uchenily/doris_vector_search: ベクトル距離検索に最適化されており、現在利用可能な最高性能のDorisベクトル検索Python SDKです。
使用制限
-
ANNインデックス列は NOT NULL の
Array<Float>である必要があり、インポートされるすべてのベクトルは宣言されたdimと一致する必要があります。そうでなければエラーがスローされます。 -
ANNインデックスはDuplicateKeyテーブルモデルでのみサポートされます。
-
Dorisはプリフィルターセマンティクス(ANN TopNの前に述語が適用される)を使用します。述語に行を正確に特定できるセカンダリインデックスのない列(例:転置インデックスなし)が含まれる場合、Dorisは正確性を保つためにbrute forceにフォールバックします。 例:
SELECT id, l2_distance_approximate(embedding, [xxx]) AS distance
FROM sift_1M
WHERE round(id) > 100
ORDER BY distance LIMIT 10;
idはキーですが、セカンダリインデックス(転置インデックスなど)がないため、その述語はインデックス解析後に適用され、Dorisはpre-filterセマンティクスを遵守するためにブルートフォースにフォールバックします。
- SQLの距離関数がインデックスDDLで定義されたmetric typeと一致しない場合、
l2_distance_approximate/inner_product_approximateを呼び出しても、DorisはTopNにANNインデックスを使用できません。 - metric type
inner_productの場合、ORDER BY inner_product_approximate(...) DESC LIMIT N(DESCが必須)のみがANNインデックスによって高速化できます。 xxx_approximate()の最初のパラメータはColumnArrayである必要があり、2番目はCASTまたはArrayLiteralである必要があります。これらを逆にするとブルートフォース検索がトリガーされます。