存算分离内表向量检索

更新时间:
复制 MD 格式

EMR Serverless StarRocks 支持在内表的向量列上创建向量索引,将近似最近邻(ANN)检索与结构化过滤、关联、聚合放在同一条 SQL 中完成。本文介绍如何创建、查询和管理内表向量索引。

背景信息

文本、图片、音频等非结构化数据经 Embedding 模型编码为特征向量后,语义相似度只能通过向量距离表达,无法用等值或范围条件描述。在向量列上创建向量索引后,StarRocks 可以按索引结构完成近似检索,避免逐行计算距离。

在存算分离架构下,每个 Segment 对应一个独立的向量索引文件,索引文件存放在共享存储中,由计算节点按需加载并缓存。因此首次查询通常包含索引文件的远端读取耗时,预热后延迟会明显下降。

前提条件

  • 已创建 EMR Serverless StarRocks 3.5.16-2.2.0 及以上版本的实例。

  • 已完成向量数据的 Embedding 生成。如果需要在库内直接生成向量,请参见AI Function。

  • 实例已开启向量索引能力。该能力由 FE 配置项 enable_experimental_vector 控制,如果实例未默认开启,请联系管理员启用。

开启向量索引能力

如果实例尚未开启向量索引能力,由具有管理权限的用户执行以下语句。

ADMIN SET FRONTEND CONFIG ("enable_experimental_vector" = "true");

该语句为动态配置。实例是否允许直接修改该配置、修改后是否持久化,以实例的控制台能力为准。

使用限制

创建内表向量索引前,请确认满足以下条件。

  • 表模型为主键模型(Primary Key)或明细模型(Duplicate Key)。

  • 向量列的数据类型为 ARRAY<FLOAT>,且必须为 NOT NULL。

  • 一张表最多创建一个向量索引。

  • 索引类型支持 HNSW 和 IVFPQ,索引度量支持 l2_distance 和 cosine_similarity。

  • 查询向量的维度必须与索引的 dim 一致,且不能包含 NULL、NaN 或无穷值。

  • 计算节点默认在单个 Segment 达到 10000 行后才构建向量索引,未达到阈值的 Segment 在查询时使用逐行距离计算。可以通过索引属性 index_build_threshold 调整该阈值。

创建向量索引

StarRocks 支持 HNSW 和 IVFPQ 两种索引类型,适用场景和调优参数如下表所示。

索引类型

适用场景

特点

主要调优参数

HNSW

低延迟、高召回的在线检索。

基于图搜索,检索速度快,索引体积和构建内存相对较高。

构建:M、efconstruction;查询:efSearch。

IVFPQ

数据规模较大,需要降低索引存储和内存成本。

通过倒排聚类和乘积量化压缩索引,通常需要扩大候选集并精排。

构建:nlist、M_IVFPQ、nbits;查询:nprobe 等。

如果没有明确的索引体积约束,建议先使用 HNSW 建立性能和召回率基线,再根据资源成本评估是否切换到 IVFPQ。

创建 HNSW 索引

以下示例在建表时创建一个使用余弦相似度的 HNSW 索引。示例维度仅用于演示,实际的 dim 必须与 Embedding 模型的输出维度一致。

CREATE TABLE item_embeddings (
      id        BIGINT NOT NULL,
      category  VARCHAR(64),
      content   VARCHAR(65533),
      embedding ARRAY<FLOAT> NOT NULL,
      INDEX idx_embedding (embedding) USING VECTOR (
          "index_type" = "hnsw",
          "dim" = "4",
          "metric_type" = "cosine_similarity",
          "is_vector_normed" = "false",
          "M" = "16",
          "efconstruction" = "40"
      )
  ) ENGINE=OLAP
  DUPLICATE KEY(id)
  DISTRIBUTED BY HASH(id) BUCKETS 1;

参数说明如下。

参数

是否必选

说明

index_type

是

索引类型,HNSW 填写 hnsw。

dim

是

向量维度,必须为正整数。

metric_type

是

距离度量,取值为 l2_distance 或 cosine_similarity。创建后,查询必须使用与该度量匹配的近似距离函数。

is_vector_normed

否

默认值为 false。仅在余弦相似度下生效,只有数据已完成归一化时才设置为 true。

M

否

每个节点的最大连接数,默认值为 16。增大该值通常可以改善图质量,但会增加构建时间和索引体积。

efconstruction

否

构建时的候选列表大小,默认值为 40。增大该值通常可以提高召回率,但会增加构建的 CPU、内存和耗时。

index_build_threshold

否

单个 Segment 达到指定行数后才构建索引,默认值为 10000。未达到阈值的 Segment 在查询时使用逐行距离计算。

创建 IVFPQ 索引

以下示例在建表时创建一个使用 L2 距离的 IVFPQ 索引。

CREATE TABLE item_embeddings_ivfpq (
      id        BIGINT NOT NULL,
      embedding ARRAY<FLOAT> NOT NULL,
      INDEX idx_embedding_ivfpq (embedding) USING VECTOR (
          "index_type" = "ivfpq",
          "dim" = "4",
          "metric_type" = "l2_distance",
          "is_vector_normed" = "false",
          "nlist" = "16",
          "M_IVFPQ" = "2",
          "nbits" = "8"
      )
  ) ENGINE=OLAP
  DUPLICATE KEY(id)
  DISTRIBUTED BY HASH(id) BUCKETS 1;

除 index_type、dim、metric_type 和 is_vector_normed 外,IVFPQ 特有参数说明如下。

参数

是否必选

说明

nlist

否

倒排列表数量,默认值为 16。增大该值可以细化聚类,但会增加训练和搜索开销。

M_IVFPQ

是

子向量数量。dim 必须能被 M_IVFPQ 整除。

nbits

否

每个子向量的量化位数,当前仅支持 8。

index_build_threshold

否

单个 Segment 达到指定行数后才构建索引,未显式配置时取 10000 与 nlist 中的较大值。显式配置会覆盖该默认值,取值不应低于索引训练所需的数据量,否则训练样本不足可能导致索引构建失败。

为已有表添加索引

对于已经存在的内表,可以执行 CREATE INDEX 添加向量索引。

CREATE INDEX idx_embedding
  ON item_embeddings (embedding)
  USING VECTOR (
      "index_type" = "hnsw",
      "dim" = "4",
      "metric_type" = "cosine_similarity",
      "is_vector_normed" = "false",
      "M" = "16",
      "efconstruction" = "40"
  );

添加索引会触发异步 Schema Change。执行以下语句确认任务完成后,再开始性能测试。

SHOW ALTER TABLE COLUMN
  WHERE TableName = 'item_embeddings'
  ORDER BY CreateTime DESC
  LIMIT 1;

任务状态为 FINISHED 表示索引变更完成。

索引构建期间需要为计算节点预留足够的 CPU 和内存,建议在业务低峰期执行。

导入向量数据

向量数据可以通过 INSERT、Stream Load、Broker Load 等方式导入。以下示例插入少量演示数据。

INSERT INTO item_embeddings VALUES
    (1, 'document', '向量检索介绍', [0.12, 0.34, 0.56, 0.78]),
    (2, 'document', '混合检索实践', [0.11, 0.31, 0.59, 0.74]),
    (3, 'image',    '图片内容描述', [0.75, 0.18, 0.09, 0.62]);

导入前请确认以下内容。

  • 每行向量的长度与索引的 dim 一致。

  • 库内向量和查询向量使用同一个 Embedding 模型和相同的预处理流程生成。

  • 如果 is_vector_normed 为 true,每条向量均已完成 L2 归一化。

  • 大批量导入时合理控制批次大小,并观察导入、Compaction(数据合并)和索引构建的资源使用情况。

查询向量数据

近似检索使用 approx_ 前缀的距离函数,函数必须与索引的 metric_type 匹配。不带该前缀的函数会逐行计算距离,不使用向量索引。

TopK 近似检索

余弦相似度越大表示越相似,因此使用降序排序。

SELECT
      id,
      content,
      approx_cosine_similarity(
          embedding,
          CAST('[0.10, 0.30, 0.60, 0.75]' AS ARRAY<FLOAT>)
      ) AS score
  FROM item_embeddings
  ORDER BY score DESC
  LIMIT 10;

L2 距离越小表示越相似,因此使用升序排序。

SELECT
      id,
      approx_l2_distance(
          embedding,
          CAST('[0.10, 0.30, 0.60, 0.75]' AS ARRAY<FLOAT>)
      ) AS distance
  FROM item_embeddings_ivfpq
  ORDER BY distance ASC
  LIMIT 10;

要命中向量索引,TopK 查询需要同时满足以下条件。

  • 使用与索引 metric_type 匹配的近似函数。

  • 函数的一个参数是创建索引的向量列,另一个参数是优化阶段可确定的常量浮点数组。

  • ORDER BY 中只有一个向量评分表达式。

  • L2 距离使用 ASC,余弦相似度使用 DESC。

  • 查询指定了 LIMIT。

标量与向量联合检索

可以先使用业务字段限定检索范围,再返回向量 TopK 结果。

SELECT
      id,
      content,
      approx_cosine_similarity(
          embedding,
          CAST('[0.10, 0.30, 0.60, 0.75]' AS ARRAY<FLOAT>)
      ) AS score
  FROM item_embeddings
  WHERE category = 'document'
  ORDER BY score DESC
  LIMIT 10;

该 SQL 可以正常执行,但普通标量谓词可能使查询不满足 TopK 向量索引的重写条件,导致查询回退为逐行距离计算。不要根据 SQL 的书写形式判断过滤发生在向量检索之前还是之后,正式使用前请通过 EXPLAIN 和 Query Profile(查询执行详情)确认实际的扫描与过滤行为。

范围检索

范围检索需要使用与排序表达式相同的近似函数。以下示例只返回余弦相似度不低于阈值的结果。

SELECT *
  FROM (
      SELECT
          id,
          content,
          approx_cosine_similarity(
              embedding,
              CAST('[0.10, 0.30, 0.60, 0.75]' AS ARRAY<FLOAT>)
          ) AS score
      FROM item_embeddings
  ) t
  WHERE score >= 0.80
  ORDER BY score DESC
  LIMIT 10;

余弦相似度的范围条件使用「大于或等于阈值」,L2 距离的范围条件使用「小于或等于阈值」。

该查询仍然必须包含 LIMIT,返回结果不超过指定的 TopK 数量。因此该用法是在 TopK 检索的基础上叠加阈值过滤,不等同于返回阈值范围内全部数据的 RNN 查询。

精确检索

使用不带 approx_ 前缀的距离函数会逐行计算距离,不使用向量索引。精确检索适用于小数据集查询,也可以作为 ANN 召回率的 ground truth。

SELECT
      id,
      cosine_similarity(
          embedding,
          CAST('[0.10, 0.30, 0.60, 0.75]' AS ARRAY<FLOAT>)
      ) AS score
  FROM item_embeddings
  ORDER BY score DESC
  LIMIT 10;

调优与验证

索引创建完成后,可以通过会话变量调整搜索宽度,并通过执行计划和 Query Profile 确认索引是否生效。

调整查询参数

查询参数通过会话变量 ann_params 设置。连接池中的每个连接都需要单独设置该变量,建议在连接初始化 SQL 中统一配置。

HNSW 通过 efSearch 控制搜索宽度。

SET ann_params = '{"efSearch": 100}';

efSearch 越大,搜索的候选节点越多,召回率可能提高,但查询 CPU 消耗和延迟也会增加。

IVFPQ 常用的搜索参数如下。

SET ann_params = '{
    "nprobe": 16,
    "max_codes": 0,
    "scan_table_threshold": 0,
    "polysemous_ht": 0,
    "range_search_confidence": 0.1
  }';

建议使用代表性查询集同时评估 QPS、P95/P99 延迟和 Recall@K,不要仅根据单次查询耗时选择参数。每次只调整一个参数并重新测量,避免同时修改多个参数导致无法定位收益来源。

检查索引是否生效

执行 EXPLAIN 查看执行计划。

EXPLAIN
  SELECT id
  FROM item_embeddings
  ORDER BY approx_cosine_similarity(
      embedding,
      CAST('[0.10, 0.30, 0.60, 0.75]' AS ARRAY<FLOAT>)
  ) DESC
  LIMIT 10;

如果 OlapScanNode 中出现以下内容,表示优化器已生成向量索引查询计划。

VECTORINDEX: ON

Query Profile 中可以重点关注以下指标。

指标

说明

VectorSearchTime

向量索引检索耗时。

VectorIndexFilterRows

向量索引过滤的行数。

VectorIndexLoadTime

加载索引文件的耗时。

VectorIndexCacheHitSegments

命中向量索引缓存的 Segment 数量。

VectorIndexCacheMissSegments

未命中缓存、需要加载索引的 Segment 数量。

首次查询可能包含共享存储读取和索引加载耗时。性能评测时应分别记录冷缓存和预热后的结果。

查看和删除索引

执行以下语句查看索引定义。

SHOW CREATE TABLE item_embeddings;
SHOW INDEX FROM item_embeddings;

执行以下语句删除索引。

DROP INDEX idx_embedding ON item_embeddings;

删除索引同样属于表结构变更。请确认目标索引名称,并在业务低峰期操作。

常见问题

EXPLAIN 显示未使用向量索引,如何排查?

按以下顺序检查。

  1. FE 是否已开启 enable_experimental_vector。

  2. 索引是否已创建完成,SHOW ALTER TABLE COLUMN 中任务状态是否为 FINISHED。

  3. 查询函数是否与索引的 metric_type 匹配。

  4. L2 距离是否按升序排列,余弦相似度是否按降序排列。

  5. ORDER BY 是否只包含一个向量评分表达式,并且存在 LIMIT。

  6. 查询向量是否为常量浮点数组,维度是否与 dim 一致。

  7. 查询列是否为创建索引的向量列。

  8. 查询是否包含普通标量谓词、关联、聚合或多层子查询等会改变计划形态的操作。这些操作可能使查询不满足向量索引的重写条件。

已生成向量索引计划,但查询仍然较慢,如何处理?

  • 查看 VectorIndexLoadTime 和缓存命中指标,判断是否为冷缓存加载导致。

  • 检查是否存在大量小 Segment。Segment 过多会放大索引加载和合并开销,可以在测试环境先完成 Compaction 再对比。

  • 检查 Segment 行数是否低于 index_build_threshold。未构建索引的 Segment 会回退为逐行计算。

  • 降低 HNSW 的 efSearch 或 IVFPQ 的 nprobe 后重新验证 Recall@K。

  • 检查查询是否返回了不必要的大字段。回表和网络传输开销会掩盖向量检索的实际耗时。

索引构建失败或耗时较长,如何处理?

  • 确认向量数据不存在维度不一致、空值或非法浮点数。

  • 确认 IVFPQ 的 dim 可以被 M_IVFPQ 整除,且训练数据量足以支撑 nlist。

  • 查看计算节点的剩余内存和索引构建并发。提高构建并发会同时增加 CPU 和内存峰值。

  • 大批量导入后先完成 Compaction,可以减少 Segment 数量和后续的检索放大。是否执行需结合业务窗口评估。

最佳实践

  • 表内向量和查询向量使用相同的 Embedding 模型版本、分词方式和归一化策略。

  • 先用精确检索生成 ground truth,再通过参数矩阵选择满足召回率目标的最低成本配置。

  • 在连接池初始化时设置 ann_params,避免不同连接使用不同的搜索参数。

  • 性能测试时固定实例版本、数据快照、Segment 数量、缓存状态、查询集和并发口径。

  • 生产变更前保留 SHOW CREATE TABLE 输出、测试参数和结果文件,便于回归与问题定位。

相关文档