第 28 关 · ★★★★★

全文、向量与混合检索

从 Inverted/BM25 到 HNSW、IVF、IVF_ON_DISK:一条 SQL 完成混合检索。

已点亮 · 最佳 分

3.x / 4.x 新特性巡礼

从存算分离 GA 到 AI 原生:Doris 这两年发生了什么

3.0 存算分离3.1 半结构化4.0 AI 与混合检索
1 / 12

Day 28|Doris Search 全景:倒排索引、BM25、HNSW、IVF、Hybrid Search 与 RRF

课程阶段:第六阶段|湖仓、搜索与 AI 数据基础设施
学习目标: 建立 Apache Doris 统一检索的完整坐标系,能够设计全文检索、向量检索、Hybrid Search 与 RRF 排名融合方案,并掌握表结构、索引构建、质量评测、容量规划、上线治理和故障排查方法。
版本快照: 2026-08-18 核验,Apache Doris 4.1.3 为 Latest,4.0.8 为 Stable。正文以 4.0 稳定分支的倒排索引、BM25、HNSW 和基础 Hybrid Search 为主体,4.1 的 IVF、IVF_ON_DISK 与量化单独标注。
课程承接: Day 11 已经讲过索引体系,Day 14 讲过 VARIANT,Day 26–27 讲过湖仓和存算分离。Day 28 把结构化过滤、全文检索、语义检索和分析计算放到同一条 SQL 链路中。

Day 28 封面
Day 28 封面


一、这次重做的范围与原则

原培训材料第 345–363 页从“索引即过滤”开始,讲了 ZoneMap、BloomFilter、NGram BloomFilter 和 Inverted Index,随后解释 Term Dictionary、Posting List、索引写入成本与索引选型。这套框架适合建立 Data Skipping 的基础认知。

Day 28 沿用其中三条主线:

  1. 查询性能来自尽早缩小候选数据;
  2. 倒排索引通过词典和 Posting List 定位 RowID;
  3. 索引会增加存储、导入 CPU 和后台维护成本。

当前 Doris 4.x 已经把搜索能力扩展到 BM25、SEARCH()、ANN 向量索引、Hybrid Search、IVF、IVF_ON_DISK 和向量量化。本文将原材料的索引基础和这些新能力连接起来,全文只讨论 Doris 官方资料能够支撑的机制、语法和边界。

Day 28 学习地图
Day 28 学习地图


二、统一检索要处理三类信号

一条企业检索请求通常同时包含词法、语义和结构化约束。

用户搜索:

“找出当前租户最近 30 天的 Doris 权限故障文档,关键词要包含权限或 Access Denied,语义上接近‘用户登录成功但查询被拒绝’,返回最相关的 20 条。”

这句话可以拆成三组条件。

2.1 词法信号

“权限”“Access Denied”“Doris”都属于明确词法。错误码、版本号、产品型号、机构名和法律条款也属于这一类。全文检索擅长保证某个词、全部词、短语或模式被命中,结果具有较强可解释性。

2.2 语义信号

“没有查询权限”“RBAC 授权失败”“Access denied”“用户可登录但无法查询”表达方式不同,业务含义接近。Embedding 将这些内容映射到向量空间,ANN 负责从大规模向量中快速找到近邻。

2.3 结构化信号

租户、ACL、时间、文档类型、产品版本、地域和状态决定结果能否返回。RAG 场景中,这组条件直接关系到权限正确性,应该进入数据库 WHERE 或 Row Policy,在候选集合形成之前生效。

Doris 的价值来自三类信号共享一张表、一套权限、一套 SQL 和一套 MPP 执行引擎。检索结果可以继续做 JOIN、聚合、窗口函数和趋势分析。

检索信号坐标系
检索信号坐标系


三、全文检索从倒排索引开始

普通 LIKE '%keyword%' 往往需要逐行读取字符串。倒排索引在写入时完成分词,并维护:

Term Dictionary
    Token → 词典项

Posting List
    Token → 命中的 RowID 列表

假设三条文档经过分词后形成:

doris  → [1, 2]
全文   → [1]
向量   → [2, 3]
rag    → [3]

查询:

WHERE body MATCH_ANY 'doris 向量'

执行器可以读取 doris向量 的 Posting List,执行并集后得到候选 RowID。未命中的行不需要解码完整正文。

Doris 当前全文检索支持 STRINGTEXTVARCHARARRAY<STRING> 等列上的倒排索引。文本谓词会由 Nereids 下推到索引层,然后和普通结构化谓词共同参与后续执行。

倒排索引写入与读取
倒排索引写入与读取

3.1 倒排索引可以处理哪些查询

当前常用操作包括:

操作 语义 适用场景
MATCH_ANY 任意 Token 命中 普通关键词 OR
MATCH_ALL 全部 Token 命中 多关键词 AND
MATCH_PHRASE 词序和邻近关系匹配 短语、日志固定语句
MATCH_PHRASE_PREFIX 短语前缀 输入联想、前缀搜索
MATCH_REGEXP Token 级正则 模式化检索
SEARCH() 跨列与布尔组合 复杂检索 DSL

示例:

SELECT id, title
FROM docs
WHERE body MATCH_PHRASE 'vector search';

跨列组合:

SELECT id, title
FROM docs
WHERE SEARCH(
    'title:Doris AND (category:database OR tags:ANY(vector search))'
);

SEARCH() 从 4.0 起提供统一入口,适合将多个字段、布尔逻辑和全文条件组织成一条表达式。

3.2 全文检索、NGram BloomFilter 与普通 LIKE 的边界

原培训材料同时讲过倒排索引和 NGram BloomFilter。两者都能加速字符串检索,适用范围不同。

NGRAM_BF 针对 LIKE '%abc%' 这类子串匹配,将原字符串切成固定长度的 N-Gram,再用 BloomFilter 判断目标片段是否“确定不存在”。它保留了 LIKE 的子串语义,存在概率型误判,最终仍需读取候选数据完成确认。

倒排索引面向 Token 语义。分词以后,“running shoes”可以按词检索、短语检索和相关性排序,Posting List 还能和其他文本条件组合。日志中的错误码、Trace ID、URL Path 等整串字段,可以使用不分词或 keyword 方案;自然语言正文更适合分词倒排索引。

选型时可以先问三个问题:

  1. 用户输入的是子串,还是词和短语?
  2. 结果只需要过滤,还是需要 BM25 排名?
  3. 查询需要大小写、同义词、停用词和多语言处理吗?

子串语义明确、没有相关性排序时,NGram BloomFilter 可能更轻量。需要词法理解、短语、布尔逻辑和排名时,倒排索引更合适。两类索引都需要用真实数据测存储放大与导入吞吐,不能仅凭功能表选择。


四、Analyzer 决定“什么算同一个词”

全文检索质量首先取决于分词。Doris 3.1 起支持自定义 Analyzer,处理链可以包含:

Character Filter
→ Tokenizer
→ Token Filter

Character Filter 在分词前处理 HTML、标点和业务符号。Tokenizer 决定如何切分,当前常见选择包括 standardngramedge_ngramkeywordicu。Token Filter 用于大小写、ASCII 折叠和复合词处理。

例如:

FE Master
FE leader
主节点

三种表达可能指向同一个概念。Analyzer 只能解决其中的词法归一;跨语言或同义语义通常还需要同义词治理、Query Rewrite 或向量召回。

产品型号和错误码经常需要 keyword,自然语言正文更适合标准或语言相关分词。将所有 STRING 列都套用同一个 Analyzer,会造成索引膨胀和检索质量问题。

Analyzer 流水线
Analyzer 流水线

4.1 Analyzer 需要版本化

修改 Analyzer 会改变 Token 和 Posting List。生产系统应记录:

analyzer_name
analyzer_version
tokenizer
char_filter
token_filter
stopword_version
synonym_version

上线新 Analyzer 时,建议构建影子索引并使用固定 Query Set 做 A/B,保留旧索引作为回滚路径。

4.2 用 Query Log 设计 Analyzer

Analyzer 设计最好从线上 Query Log 开始。可以抽取最近一到四周的查询,按照意图分组:

产品与组件名
错误码与版本号
自然语言问题
中英文混写
路径、URL、IP 和 Trace ID
错别字与输入联想

然后对每组检查“查询 Token”和“文档 Token”能否对齐。

例如用户输入:

Doris FE 选主超时

文档可能写成:

Frontend leader election timeout

仅靠中文标准分词无法建立完整对应关系。工程上可以把“FE / Frontend”“选主 / leader election”放入同义词或 Query Rewrite 层,向量检索负责处理更宽泛的语义表达。

Analyzer 评审表至少记录:字段、语种、Tokenizer、大小写规则、停用词、同义词、是否支持 Phrase、平均 Token 数、索引大小和典型 Query。这样可以避免某次“优化分词”悄悄改变全部搜索结果。

4.3 Phrase、Prefix 和 Regex 的成本意识

MATCH_PHRASE 需要词位置信息,索引体积和写入成本通常高于只保存 Token 命中的模式。MATCH_PHRASE_PREFIX 和 Regex 面向更复杂的匹配,候选范围和 CPU 成本会随 Query 变化。

生产 API 应限制:

  • 最大 Query 长度;
  • 最大 Token 数;
  • Regex 复杂度;
  • 单次候选数量;
  • Query Timeout;
  • 高成本操作的调用角色。

检索能力越丰富,查询治理越重要。开放给内部 Agent 或终端用户时,可以通过固定 Query Template、SQL Block Rule 和 Workload Group 控制资源风险。


五、BM25:从“命中”走到“相关性排序”

倒排索引可以得到命中集合。搜索页面和 RAG 往往只需要最相关的若干条,Doris 4.0 起通过 score() 暴露 BM25 相关性。

BM25 综合考虑:

  • TF:查询词在当前文档中的出现频率;
  • IDF:查询词在整个语料中的稀有程度;
  • 文档长度:避免长文本仅凭词多获得过高分数。

官方当前给出的默认参数为:

k1 = 1.2
b  = 0.75
boost = 1.0

BM25 相关性
BM25 相关性

5.1 score() 的激活条件

当前相关性计算需要同时满足:

  1. SELECT 显式调用 score()
  2. WHERE 至少包含一个支持评分的 MATCH_*SEARCH 条件;
  3. 查询形成 ORDER BY score() ... LIMIT K 的 Top-N。
SELECT
    id,
    title,
    score() AS relevance
FROM docs
WHERE body MATCH_ANY 'apache doris vector search'
ORDER BY relevance DESC
LIMIT 20;

非分词倒排索引只承担精确过滤,不计算 BM25。

5.2 BM25 分值怎样解释

BM25 分值为正数,没有固定上界。分值取决于本次 Query、Token 和当前语料分布。可靠用法是在同一次 Query 内比较相对大小。两个不同 Query 都得到 3.0,无法据此判断相关程度一致。

官方 Key Feature 文档还指出,当前 Doris 使用 Lucene 默认参数,k1b 没有开放为按 Query 动态调节的运行参数。课程实验以 Analyzer、候选集合和排序结果为重点,不设计“每条 SQL 临时调 BM25 参数”的方案。

5.3 一个 BM25 排名变化的例子

假设语料中“database”几乎每篇文档都出现,“delete bitmap”只出现在少量 Doris 内核文档中。查询:

database delete bitmap

BM25 会给“delete bitmap”更高区分权重。正文很长、重复出现大量公共词的文档,也不会无限提高得分,因为 k1 控制词频饱和,b 对文档长度做归一化。

这也解释了一个生产现象:同一条 Query 在大量新文档导入后,排名可能变化。新增语料会改变总行数、某个 Token 的文档频率和平均文档长度。质量回归要固定数据快照或记录语料版本,排名异常排查也要检查最近的数据增量。

5.4 BM25 适合承担哪一段排序

BM25 非常适合第一阶段关键词 Top-N,也适合最终结果仍以词法相关性为主的搜索。以下场景需要增加其他信号:

  • 时间新鲜度;
  • 文档权威等级;
  • 产品版本匹配;
  • 用户点击反馈;
  • 向量语义相似度;
  • 业务库存、价格或风险等级。

可以先用 BM25 得到可控候选,再由 SQL 加入业务字段排序。涉及 ANN 双路召回时,RRF 比直接拼接绝对分数更容易解释和治理。


六、向量在 Doris 中怎样存

Doris 当前没有单独的 VECTOR 列类型,Embedding 使用固定维度:

embedding ARRAY<FLOAT> NOT NULL

ANN 索引声明算法、距离和维度:

INDEX idx_embedding (embedding) USING ANN
PROPERTIES (
    "index_type" = "hnsw",
    "metric_type" = "l2_distance",
    "dim" = "768"
)

每行实际向量长度必须等于 dim,向量列不能为 NULL。导入链路还需要检测 NaN、Infinity、零向量和模型版本混写。

向量存储与索引
向量存储与索引

6.1 表模型的官方口径存在差异

截至本文核验日,Doris 4.x 两份官方页面对 ANN 支持的表模型有不一致表述:

  • Vector Index Overview 写明 DUPLICATE KEY 或 MoW UNIQUE KEY
  • Vector Index Practical Guide 的前置条件和 FAQ 仍写 Only DUPLICATE KEY

课程主实验统一使用 DUPLICATE KEY,这样在 4.0.8 和 4.1.3 上拥有更稳妥的可执行基线。项目计划使用 Unique Key 时,需要在目标补丁版本完成真实 DDL、更新、Compaction、索引构建和查询命中测试,并把验证结果写入 ADR。

6.2 Embedding 写入链路也属于数据工程

向量来自外部模型或 Doris EMBED() 等 AI 能力。无论调用位置在哪里,生产链路都要保存模型和输入版本。

建议把一次 Embedding 生成拆成:

读取原文
→ 文本清洗与 Chunk
→ 计算 content_hash
→ 调用 Embedding Model
→ 校验维度、数值和范数
→ 写入 embedding + model/version
→ 抽样对账

需要重点拦截:

  • 维度不等于 dim
  • NULL;
  • NaN 或 Infinity;
  • Cosine 场景中的零向量;
  • 正文已变更、Embedding 未重算;
  • 同一索引混入不同模型空间。

轻量实验可以使用伪向量验证 SQL 和索引流程,Recall 与业务质量评测必须使用真实模型向量。模型升级时推荐新增 embedding_v2idx_embedding_v2,保持 V1 可查询,再做离线与线上灰度。

6.3 Chunk 设计会改变索引规模和召回质量

RAG 文档切得过大,一个 Chunk 可能包含多个主题,向量语义被平均,BM25 还会受到长度归一化影响。切得过小,行数、倒排索引、ANN 索引和结果去重成本都会增加,上下文又可能缺少完整语义。

可以对 256、512、1024 Token 三组 Chunk 做同一 Query Set 评测,比较 NDCG、Recall、索引大小、p99 和最终回答质量。Chunk Size 应作为检索 Schema 的一部分管理。


七、ANN 查询如何进入存储层 Top-K

向量索引只会在特定查询形态下发挥作用:

SELECT
    id,
    l2_distance_approximate(embedding, [ ... ]) AS dist
FROM docs
ORDER BY dist ASC
LIMIT 20;

Nereids 识别 approximate distance、ORDER BYLIMIT,将查询转换为存储侧 AnnTopN。执行过程包含:

结构化 / 文本 Pre-filter
→ Segment ANN Local Top-K
→ Tablet Merge
→ 跨节点 Global Top-K

AnnTopN 生命周期
AnnTopN 生命周期

l2_distance_approximate 按升序排列,距离越小越相似。inner_product_approximate 按降序排列,值越大越相似。去掉 _approximate 后会进行精确 Brute-force 计算,适合构造 Ground Truth 或处理很小的候选集合。

Cosine Similarity 当前不能直接配置 metric_type="cosine"。官方推荐在写入前将向量做 L2 归一化,再使用 inner_productinner_product_approximate。单位向量空间中,Inner Product 与 Cosine 排序一致。

7.1 Pre-filter 是否高效,取决于过滤列自身的索引

向量 Query 带有 tenant_idcategorycreated_at 和文本 MATCH 时,过滤列需要能尽早缩小候选。可以复用前几天学习的分区、Prefix、ZoneMap 和 Inverted Index。

理想路径:

Partition Pruning
→ tenant/category Secondary Index
→ Text Posting List
→ Candidate RowID Bitmap
→ AnnTopN

过滤列没有适当二级索引时,系统仍会维持结果正确性,执行成本可能退化为对过滤候选进行更多精确计算。Hybrid Search POC 要同时记录过滤前行数、过滤后候选数、AnnTopN 输入和最终 K。

7.2 Segment 数量直接进入 ANN 成本

ANN 索引按 Segment 构建。一个 Tablet 有 300 个小 Segment,查询需要从更多 Segment 获取 Local Top-K,再做合并。高频小批写入会放大:

  • 索引构建次数;
  • Segment 文件数;
  • AnnTopN 局部候选数;
  • Compaction 和索引重建成本。

Day 20 学过的批次治理、Group Commit 和 Compaction 会直接影响 Day 28 的 Search 性能。向量索引项目需要把写入路径和查询路径放在同一个压测场景里。


登录后可阅读本文完整内容。