见微知著:全文、模糊与向量检索
15 见微知著:全文、模糊与向量检索
搜索不是“给某一列加个索引”。它至少包含四个问题:
全文检索、三元组模糊匹配和向量近邻解决的是不同子问题:
| 方法 | 最擅长捕捉 | 主要盲区 |
|---|---|---|
| PostgreSQL FTS | 词形归一、多词布尔/短语、字段权重 | 拼写错误、词表之外的语义表达 |
pg_trgm |
字符串局部相似、错拼、包含与相似候选 | 业务语义、长文档相关性 |
| 向量检索 | 由模型编码的相似性 | 精确词优势、模型偏差、近似召回 |
| 混合检索 | 多路召回互补 | 参数、成本、解释与验证复杂度 |
它们可以组合,却不能因为“混合”两个字就自动更好。本章把检索从演示查询 变成一个可反驳的工程实验:
冻结语料、查询、相关性标注和模型身份;每一路独立产生候选;质量黄金值 使用精确计算;近似索引另测召回;最后才讨论延迟、部署与上线。
本章完成后
你应当能够:
- 区分精确过滤、词法相关、字符串相似与模型相似;
- 把查询解析、过滤、候选生成、排序、融合和重排拆成独立阶段;
- 为检索实验建立带版本的语料、查询集与分级相关性标注;
- 解释 parser、dictionary、configuration、
tsvector与tsquery; - 显式固定文本搜索配置,构造带 A/B 权重的存储生成列;
- 正确选择
websearch_to_tsquery、plainto_tsquery、phraseto_tsquery或to_tsquery,而不把原始用户输入直接交给严格语法; - 用
ts_rank_cd排序,并知道 0–1 归一化不等于跨检索器可比概率; - 解释
similarity、word_similarity、%门槛与 GIN/GiST 的边界; - 选择 L2、余弦或内积时,把模型训练与归一化合同写清楚;
- 区分精确近邻与 HNSW/IVFFlat 近似近邻,不用 ANN 结果定义质量黄金值;
- 解释过滤为何可能降低 ANN 返回数,以及 iterative scan 能补什么、不能 保证什么;
- 用 RRF 合并不同分数尺度的排名,并知道何时需要校准或重排;
- 计算 Precision@K、Recall@K、MRR@K 与 NDCG@K;
- 在 Pigsty 中把扩展装包、数据库启用、版本核对和节点一致性分开验收;
- 把模型费用、数据出境、向量回填、索引维护、WAL 与副本延迟纳入 ADR;
- 交付一个可运行、可审校、可精确复位的检索 PoC。
一条有意不完美的实验
贯穿实验位于 shop_ch15,固定:
四维向量的模型标识为:
它不是 embedding 模型,也没有调用任何外部 API。它只是可人工核算、每次 重建完全相同的主题坐标,用来隔离数据库机制与模型随机性。真实模型质量必须 用真实文本重新测,不能继承本章数字。
固定评估集得到:
| 策略 | Precision@3 | Recall@3 | MRR@3 | mean NDCG@3 | min NDCG@3 |
|---|---|---|---|---|---|
| 全文 | 0.291667 | 0.291667 | 0.750000 | 0.613043 | 0.000000 |
| 模糊 | 0.916667 | 0.916667 | 1.000000 | 0.942881 | 0.842828 |
| 精确向量 | 1.000000 | 1.000000 | 1.000000 | 0.817314 | 0.631039 |
| RRF 混合 | 1.000000 | 1.000000 | 1.000000 | 0.962929 | 0.759192 |
结果故意保留三个反例:
wireles hedphones与postgre databse tuning的全文结果为空,说明 词形归一不是拼写纠正;- 精确向量覆盖全部相关对象,但次序不总正确,Recall 高不等于 NDCG 高;
q02上混合 NDCG 为0.759192,低于纯模糊的0.842828,说明 融合可以把弱信号带进来。
因此本章的结论不是“向量最好”或“混合必胜”,而是:
精确质量与近似服务路径分开
质量视图枚举过滤后的全部候选并计算精确 L2 距离,不经过 HNSW。HNSW 只在 独立 probe 中运行:
这一个点证明“能够查询并比较”,不证明生产召回为 1。pgvector 官方说明, 默认无 ANN 索引时是精确搜索;HNSW/IVFFlat 以部分召回换取速度,生产应持续 拿近似结果与精确结果对照。参见 pgvector 0.8.4 README。
强制执行计划分别验证:
这里关闭部分 planner 路径,只为证明索引可用;不是 17 行数据上的性能比较。
实验资产
规范与决策:
输入与实现:
三份 CSV 不只是仓库附件。自动化会从数据库重新导出并执行逐字节比较,
fixture-manifest.json 还固定各文件 SHA-256、行数、模型身份、生成方法与
许可证边界。
快速运行
本章复用第 14 章已经安装和认证的扩展,不自行接管扩展生命周期:
all 会:
- 验证目标数据库、角色、ch04-v1 模型和第 14 章扩展身份;
- 以 owner/marker/对象白名单保护
shop_ch15; - 创建带权重的存储
tsvector、四张表、七个排名/质量视图; - 创建全文 GIN、标题 trigram GIN、向量 HNSW 与过滤 B-tree;
- 从数据库导出三份 CSV 并与冻结输入逐字节比较;
- 采集文档、索引、体积、权限、查询解析、排名和质量证据;
- 采集精确计划与三类索引计划;
- 以精确集合为真值测一次 HNSW Recall@3;
- 证明
pg36_app可读、更新返回 SQLSTATE42501; - 运行关系、生成列、过滤、质量、checksum 和权限的全量断言;
- 证明错误 token、错误 target、活跃 worker 时 reset 分别被拒绝;
- 不用
CASCADE精确复位,确认第 14 章扩展保留,再完整重建复验。
正式 Homebrew PostgreSQL 18.6 证据两轮均得到:
安全边界
task.sh all会删除并重建带本章精确 marker 的shop_ch15,只适合 本书本地/开发夹具。它不会删除扩展。生产上应以在线建索引、双写/回填、 灰度读流量和可回退发布替代“删后重建”。
学习路径
15.1 先定义检索任务与评估集
先固定“什么叫好”。没有评估集,后面的任何好看查询都只是故事。
15.2 PostgreSQL 全文检索
从 PostgreSQL 原生词法管线开始,理解它为何强,也理解错拼为何仍会漏。
15.3 模糊匹配与拼写容错
用字符三元组补足错拼,但不给零分 Top-K 贴上“相关”标签。
15.4 可复现的向量检索
把向量看作有来源、有版本、有度量合同的数据,而不是神秘的“AI 列”。
15.5 混合检索与排序验证
学会合并名次、计算指标、解释反例,而不是拿一个查询挑选截图。
15.6 扩展部署与运行代价
把 PostgreSQL 对象映射到 Pigsty 交付和长期运行,不把安装成功当作上线。
15.7 实战:pg36_shop 商品混合检索 PoC
- 15.7.1 用冻结向量离线复现实验
- 15.7.2 比较全文、模糊、向量与混合结果
- 15.7.3 输出 ADR、质量证据、生产代价与退出路径
- 15.7.4 验收采用
checklist:evidence,不设脱离场景的性能线
最后运行双周期实验,拿出可以评审、可以拒绝、也可以退出的 proposal。
版本与证据边界
本章原理覆盖 PostgreSQL 14–18;可执行 baseline 固定 pg_trgm 1.6、
vector 0.8.4,并在 PostgreSQL 18.6 上验证。实验使用英语配置,因为冻结
语料是英语。中文、多语言与混合字段不能照抄 english,应重新选择 tokenizer/
dictionary、语料和指标。
Pigsty 映射按 4.4 文档在 2026-07-29 核验。本地证据路径是直接 PostgreSQL,
没有冒充 Pigsty L1。当前 Pigsty 文档把 pgvector 列为默认安装包,把
pg_trgm 列为默认启用扩展;实际环境仍要检查目标版本、镜像/仓库和每个
主备节点。
权威入口:
- PostgreSQL 18:全文检索导论
- PostgreSQL 18:控制文本搜索
- PostgreSQL 18:文本搜索索引
- PostgreSQL 18:
pg_trgm - pgvector 0.8.4 README
- Pigsty 4.5:默认扩展
上一章:博采众长:内核分支与扩展生态 · 返回上卷导读 · 下一章:经天纬地:时序、空间与时空查询 · 查看全书目录 · 查看索引中心
15.1 先定义检索任务与评估集
如果没有“哪些结果算相关”的外部判断,搜索系统只能证明自己能够执行。 返回三行、命中一个熟悉商品、甚至计划使用了索引,都不等于检索质量好。
本节先冻结问题。第 15.2–15.5 节才允许比较实现。
15.1.1 精确筛选、词法相关与语义相似
先问是在判断资格,还是比较相关
下面两个 SQL 看起来都在“找商品”,合同完全不同:
前者是 filtering。正确性来自布尔谓词、约束、权限和事务快照;B-tree、 hash、BRIN 或分区裁剪可能让它更快,但不会改变逻辑答案。
后者是 retrieval/ranking。正确性至少包含:
- 相关对象是否进入候选集;
- 不相关对象是否被抑制;
- 最相关对象是否排得足够靠前;
- 同分时结果是否稳定;
- 过滤是否在正确阶段生效;
- 结果是否符合当前用户的权限与业务状态。
不要把两个问题压成一个“不透明相关性 SQL”。先用可验证谓词确定资格,再在 合格集合中比较相关性,最容易推理。
四种相似不是一回事
以商品检索为例:
| 输入与目标 | 更接近的能力 | 例子 |
|---|---|---|
| SKU、状态、类别完全相等 | 精确过滤 | sku = 'AUD-001' |
| 词形、布尔词、短语与字段权重 | 全文词法检索 | headphones 匹配词位 |
| 字符局部重合与错拼 | trigram 模糊匹配 | wireles hedphones |
| 模型编码的意图相近 | 向量相似 | music on the go |
词法相关不等于字符串包含。PostgreSQL FTS 会把文本解析为 token,再经
词典归一为 lexeme;英语配置能把 rats 归一成 rat,也可能去掉 stop
word。它支持 AND、OR、NOT、短语和位置,适合“文档含有哪些有意义的词”。
字符串相似不等于语言含义。pg_trgm 按三个连续字符的集合衡量重合,
所以对局部拼写错误很有效;database 与 databse 很近,但字符很近并不
保证商品意图相同。
向量相似也不是数据库自动理解语义。数据库只看一串数和一个距离函数; “这串数表达什么”来自模型、输入模板、截断、版本和归一化。手工坐标也能 做向量近邻,但不能称为训练模型的语义质量证据。
因此“语义检索”至少应展开为:
缺任意一项,未来都很难解释同一文本为何得到不同结果。
把任务写成合同
一份可执行任务定义至少回答:
本章没有把价格、库存、租户或行级权限塞进夹具,但生产合同不能省略。尤其是 租户与 ACL:一个相关性极高却无权读取的对象不是“排名较低”,而是根本不能 进入候选集合。
先写反例
本章八个查询不是八种同义表达,而是有意覆盖失败模式:
| 查询 | 想观察什么 |
|---|---|
wireless headphones |
精确商品词 |
wireles hedphones |
两处错拼 |
music on the go |
描述性意图 |
coffee bean grinder |
标题与描述共同提供词 |
make espresso at home |
任务表达 |
trail hydration |
类别过滤与多候选 |
postgre databse tuning |
技术词错拼 |
semantic nearest neighbor |
长尾精确术语 |
如果评估集只有实现者已经看过的成功查询,它只会确认实现者的直觉。先把必然 失败的查询写进去,后续比较才有信息量。
15.1.2 查询、候选、排序和过滤的分层
一条检索链有六个阶段
每一层有不同的失败:
| 阶段 | 典型失败 |
|---|---|
| 查询解析 | 非法语法、空 query、极长输入、语言选错 |
| 硬过滤 | 跨租户泄漏、停用对象复活、过滤放到 ANN 之后导致不足 K |
| 候选生成 | FTS 零召回、模糊门槛太低、ANN 漏召回 |
| 单路排序 | 字段权重错、距离函数错、同分漂移 |
| 融合/重排 | 分数尺度乱加、弱源挤掉强源、重排器超时 |
| 响应 | 无稳定游标、证据不可解释、缓存越权 |
分层不是为了制造更多组件,而是为了让每个结论可以单独测。
查询解析必须显式
生产接口通常接收 raw text。不要让客户端偷偷决定它是:
- 所有词必须出现;
- 任一词出现;
- 完整短语;
- 支持引号与排除词;
- 前缀查询;
- 还是严格
tsquery表达式。
本章显式使用:
它接受普通 web 风格文本、引号、OR 和减号排除,并且官方文档说明不会因
输入语法抛错。这降低了语法层事故,但不代表可以无限接受输入:长度、token
数、超时、并发和滥用仍要在接口层设限。
先过滤还是先 ANN,要写出物理含义
逻辑目标是:
精确执行可以枚举所有合格行再排序。近似索引通常先沿图或倒排结构取候选, 然后应用 PostgreSQL 过滤;当过滤选择性高时,扫描出的近邻大多被过滤掉, 最终可能不足三行。
所以“SQL 把 WHERE 写在 ORDER BY 前面”不证明物理上先过滤。要看:
- 执行计划;
- 过滤选择性;
- 返回行数;
- ANN 与 exact 的集合差异;
ef_search、iterative scan、partial index 或 partition 策略。
本章的 HNSW filtered plan 明确显示:
它证明过滤发生在 HNSW 扫描结果上。hnsw.iterative_scan = 'strict_order' 能在过滤后不足时继续扫描,仍受最大扫描限制和数据分布影响,
不是“保证召回”的开关。
候选深度与返回深度不是一个 K
若最终返回 3 个结果,每路只取 3 个候选,融合器没有纠错空间。常见设计是:
本章每路取前 4,最终取 3,只为保持 SQL 可读。生产候选深度应通过质量/延迟
曲线决定,不能照抄 4。
候选源还必须带上:
否则融合以后只剩一个总分,无法追问“它为何出现”。
排名必须确定
浮点分数可以相同,近似索引也可能在近似等距对象间选择不同结果。所有实验 查询都追加稳定 tie-breaker:
这不会让近似算法变成精确算法,却能消除同分的随机输出,让回归测试和分页 至少有明确顺序。线上游标还要把排序键全部编码进去,不能只按一个不唯一分数 翻页。
过滤正确性优先于相关性
本章商品 17:
它对两个 audio 查询非常接近,是专门布置的 canary。verify.sql 断言它
不能出现在全文、模糊、精确向量或混合的任何排名中。若出现,实验立即失败,
即使平均 NDCG 更高也不能发布。
这条原则可推广为:
15.1.3 建立带人工相关性标签的查询集合
三份输入,三个不同身份
本章把输入拆成:
frozen-corpus.csv:可被检索的对象;frozen-queries.csv:查询、过滤与查询 向量;frozen-judgments.csv:查询—商品的 分级相关性。
数据库中对应:
把查询与标注分开很重要:查询是实际输入分布,标注是人对业务意图的判断。 模型和检索器都不能改写标注来让自己得分更高。
先定义标注等级
本章使用 1–3 级正相关:
| grade | 含义 |
|---|---|
| 3 | 直接满足意图,是理想结果 |
| 2 | 明确相关,但不是最佳 |
| 1 | 可接受的邻近结果 |
| 0/无行 | 未标注为相关 |
每条标注都有 rationale,例如:
理由不是装饰。两位标注者发生分歧时,没有理由就无法判断是规范模糊、文档 信息不足,还是标注错误。
真实项目应进一步规定:
- 标注者是否知道检索器输出;
- 一个查询由几人独立标注;
- 分歧怎样仲裁;
- 未展示对象是“不相关”还是“未判断”;
- 如何抽样候选池,避免只标注现有系统召回到的对象;
- 是否按用户群、语言、地区、设备与时间分层;
- PII、敏感类别与数据保留规则。
如果只标注旧检索器的候选,新方法召回的新对象会被误当成零相关,这叫 pooling bias。
四个指标回答四个问题
令前 个结果为 ,相关集合为 。
Precision@K:
它问“展示位有多少是相关的”。本章即使某策略只返回一行,也仍除以 3; 空位会损失质量,避免系统靠少返回来虚增 precision。
Recall@K:
它问“已知相关对象覆盖了多少”。本章每个查询恰好有 3 个相关对象,因此 Precision@3 与 Recall@3 数值相同;这是夹具结构的巧合,不是两个指标等价。
MRR@K:
它只关心第一个相关结果多靠前,适合“用户通常点第一个可用答案”的任务。 多个高质量结果的次序差异不会充分反映在 MRR 中。
NDCG@K 使用分级相关性:
它奖励高等级对象靠前,并以理想次序归一。本章用 NDCG 暴露了“向量找全, 次序却不理想”以及“混合在某个错拼查询上变差”。
指标不是越多越科学。先从用户行为选择指标:
| 任务 | 更重要的信号 |
|---|---|
| 唯一答案/导航 | MRR、Success@K |
| 商品列表前三屏 | NDCG、Precision@K、业务转化 |
| 法务/安全材料发现 | Recall@K、漏召回审计 |
| ANN 服务路径 | exact-relative recall + latency |
冻结不等于永久
自动化执行:
数据库多一个空格、少一条标注、换一个向量都会被发现。
但冻结集只是一个版本。遇到以下变化应生成新版本,而不是覆盖旧 golden:
- 真实查询分布改变;
- 商品/文档类型扩展;
- 模型、模板或预处理改变;
- 语言配置与词典改变;
- 人工标注规则改变;
- 融合目标或业务约束改变。
发布报告要同时写:
只在一个反复调参的集合上提高,会逐渐把参数拟合到测试集。
本章验收
运行:
审校器要求:
到这里还没有决定用哪种检索器。我们只是让后续每个决定都有同一把尺。
15.2 PostgreSQL 全文检索
PostgreSQL 全文检索不是 LIKE 的加速版。它把原文处理成带词位、权重的
lexeme 向量,把查询处理成逻辑表达式,再用 @@ 匹配、用 ranking function
排序。
本节先只讨论词法证据。拼写容错与向量语义分别留给后两节。
15.2.1 文档、词典、配置与 tsvector
文档是检索单位,不一定是一列
全文检索中的 document 是“一次返回和排序的单位”。它可以是:
- 一篇文章;
- 一个商品;
- 一封邮件;
- 多列拼接后的一个业务对象;
- 甚至跨表构造的投影。
本章把一个商品行视为 document,并只索引标题与描述。不要先把所有字符串 列都拼进去:SKU、类目、品牌、权限标签往往需要精确过滤或独立权重,盲目 拼接会同时损害相关性和索引体积。
从原文到 tsvector 的管线是:
四类 PostgreSQL 对象各有职责:
| 对象 | 职责 |
|---|---|
| parser | 切 token 并判定 token type |
| dictionary | 把 token 归一为 lexeme,或认定 stop word |
| template | 提供 dictionary 的底层行为 |
| configuration | 把 parser token type 映射到 dictionary 链 |
官方文档把 token 解释为原始片段,把 lexeme 解释为用于索引的归一化词。 例如英语配置会去掉常见 stop word,并把一些复数/词形归到词干。参见 Full Text Search Introduction。
配置必须是数据合同的一部分
下面写法依赖会话/数据库/集群默认值:
同一数据迁到另一个集群,default_text_search_config 不同,就可能生成不同
lexeme。更危险的是建索引和查询端使用了不同配置:SQL 都能执行,却永远
错过部分匹配。
本章始终显式写:
并用全限定名称避免受 search_path 影响。生产 ADR 至少记录:
english 适合本章英语夹具,不适合中文照抄。语言不是一个装饰参数:中文
如何分词、多语言文档如何路由、专有名词是否要保留原形,都会改变 document
和 query 的共同词表。
检查当前配置:
调试某段文本:
ts_debug 能显示 token type、dictionary、lexeme 与是否被丢弃,是处理
“为什么没命中”的第一证据,而不是先调相关性权重。
多字段用权重表达结构
本章定义:
三个细节都不能省:
coalesce防止一列NULL让整个 document 变成NULL;- title 用 A、description 用 B,保留字段结构给 ranking;
- configuration 显式固定,生成列和查询端一致。
默认 ts_rank/ts_rank_cd 权重按 D、C、B、A 分别为
0.1, 0.2, 0.4, 1.0。这只是默认,不是商品搜索的普遍最优值。
为什么用存储生成列
存储生成列在行写入/更新时计算,占用真实存储;读取时不必重新解析全文。
PostgreSQL 还保证它随基列变化自动更新,应用不能绕过表达式写入另一份
不一致的 tsvector。
官方限制生成表达式只能使用 immutable function,不能含子查询,也不能 引用当前行之外的数据。参见 Generated Columns。
因此它适合:
- 稳定、行内、确定性的文本预处理;
- 明确的 parser/dictionary 配置;
- 由数据库维护的一致索引列。
它不适合直接调用:
- 外部 embedding API;
- 会变化的跨表同义词;
- 当前时间;
- 随机或非确定函数。
外部模型输出应是普通列加模型身份与作业状态,由受控异步流程写入。
用目录证明生成关系
看值:
看生成属性与表达式:
attgenerated = 's' 表示 stored。verify.sql 还会为 17 行重新计算表达式,
逐行与存储值比较,避免只有目录声明正确、数据却来自旧表达式。
15.2.2 查询语法、权重与相关性排序
tsquery 不是原始字符串
@@ 比较的是 document 与 query:
常用构造函数:
| 函数 | 输入合同 | 典型用途 |
|---|---|---|
plainto_tsquery |
普通文本,存活词之间加 AND | 简单所有词查询 |
phraseto_tsquery |
普通文本,保留词位顺序/stop word 距离 | 短语 |
websearch_to_tsquery |
web 风格文本、引号、OR、减号 | 面向原始用户输入 |
to_tsquery |
严格运算符表达式 | 受控高级查询语言 |
to_tsquery 能表达 &、|、!、<->、权重和前缀,但它要求输入已经
符合语法。把 raw user text 直接传入,标点或漏写运算符就可能报错。
本章选择:
官方文档说明它不会抛出 syntax error,并支持:
“不会语法报错”并不等于“没有风险”。要处理:
空 query、全部是 stop word、节点过多、负向条件过多或输入过长,都应有接口
级策略与 statement_timeout。
先用 @@ 缩小,再计算 rank
本章词法排名:
@@ 是候选条件;ts_rank_cd 只对命中 document 排序。若对所有行先算 rank
再筛选,会放大 CPU 与 I/O。
ts_rank 主要基于匹配 lexeme 频率;ts_rank_cd 还考虑 cover density,
也就是匹配词在文档中的接近程度,并依赖词位信息。对被 strip 掉词位的
tsvector,cover density 信息会丢失。
normalization 不是概率校准
本章给 ts_rank_cd 的第三个参数为 32:
它把正分数压进 0–1 范围,但官方文档特别说明,这只是 cosmetic scaling, 不会改变排序,也不能产生全局百分比。
所以这些写法没有理论依据:
全文 rank 的 0.7、余弦相似的 0.7、trigram similarity 的 0.7 来自不同 生成机制,含义不相同。要么用训练/标注数据做分数校准,要么像本章一样用 只依赖名次的 RRF。
权重是可验证假设
setweight(..., 'A') 只给 lexeme 标标签;最终权重由 ranking function 的
weight array 决定:
数组顺序是 D、C、B、A,不是 A、B、C、D。调权重时应记录:
不要因为一个标题命中的样例“看起来更好”就把 A 权重调到很大;它可能把描述 中的强意图全部压下去。
高亮不是匹配证据本身
ts_headline 可以生成带命中词的摘要,但它会重新处理原始 document,且
输出是展示材料,不应替代 @@ 与 rank 证据。生产还要:
- 对输出做 HTML escaping;
- 控制片段长度和调用成本;
- 不把未经授权的原文片段泄露给用户;
- 缓存时把语言、query 与权限身份纳入 key。
先返回稳定 document id 和 rank,再在安全边界内生成展示片段。
15.2.3 GIN/GiST 索引、更新与语言边界
GIN 是通常的全文首选
PostgreSQL 支持用 GIN 或 GiST 加速全文检索。官方文档把 GIN 称为 preferred text search index type:
GIN 为每个 lexeme 保存匹配位置列表,适合多词交集。它不在索引里保存
tsvector 的权重标签,所以带权查询需要回表 recheck。
GiST 用固定长度 signature 表示 document,是 lossy 的,可能产生 false
match,由 PostgreSQL 自动回表排除。GiST 可以 INCLUDE 其他列,而 GIN/
GiST 在写入、体积、构建和查询上的权衡应由目标数据测量。
参见 Preferred Index Types for Text Search。
本章不是 GIN/GiST benchmark。它选择 GIN 是因为普通 document FTS 的候选 生成合同明确,并用计划证明 opclass:
由于只有 17 行,正常 planner 很可能觉得顺序扫描更便宜。实验临时:
只证明路径存在。真正上线要恢复默认 planner 设置,在代表性规模下看
EXPLAIN (ANALYZE, BUFFERS, WAL) 和延迟分布。
存储列、表达式索引与触发器
三种常见维护方式:
| 方式 | 优点 | 代价 |
|---|---|---|
tsvector 存储生成列 + GIN |
自动一致、值可见、查询清晰 | 行与 WAL 增大,表达式受 immutable 限制 |
GIN(to_tsvector(config, text)) 表达式索引 |
不存单独列 | 查询表达式必须匹配,rank 仍需计算 document |
普通 tsvector + trigger/job |
可跨更复杂来源 | 有漂移/失败状态,必须治理回填 |
如果 document 只来自当前行且表达式稳定,本章偏好存储生成列。若 document 跨表、依赖外部词典版本或异步 enrichment,就把刷新状态建成显式数据模型, 不要伪装成同步生成列。
每次内容更新都可能更新多个索引
一次 title/description UPDATE 可能同时:
- 生成新
tsvector; - 更新 FTS GIN;
- 更新标题 trigram GIN;
- 若重新生成 embedding,更新 HNSW;
- 产生 heap/index WAL;
- 在副本重放;
- 留下旧 tuple/index entry 等待 vacuum。
“搜索读得快”可能把成本转给写入、WAL、autovacuum 和副本延迟。生产实验要 分别测:
不能用 pg_relation_size 的一个时间点代替整个生命周期。
GIN pending list 与维护预算
GIN 默认可用 fast update,把新条目先放入 pending list,之后批量合并。 这通常降低前台写入成本,却可能让后续查询或 cleanup 承担尖峰。检查:
这需要 pageinspect 和相应权限,只应由运维角色执行。更通用的观测包括:
参数选择应结合更新率、gin_pending_list_limit、autovacuum 与延迟尖峰,而
不是全局照抄一组值。
语言边界是功能边界
本章的英语结果:
这证明:
- stemming/stop word 能处理词法变体与普通短语;
- 它不会自动修正
wireles、hedphones、databse; - AND 语义可能把只命中部分词的 document 排除。
对中文或混合语言,先回答:
如果需要外部分词扩展,回到第 14 章的扩展 ADR:软件包、权限、升级、备份、 主备节点和退出都要重新评审。
何时停止扩展 FTS
PostgreSQL FTS 很适合与事务数据同库、查询边界明确、规模和 ranking 需求 可控的场景。以下信号值得重新评估架构,而不是继续堆 SQL:
- 多语言/自定义分析链远超当前 parser/dictionary 能力;
- 需要复杂学习排序、拼写纠正、聚合/facet 与实时个性化;
- 搜索写入和主交易表索引维护互相伤害;
- 索引规模、构建窗口或副本恢复不满足 SLO;
- 需要独立扩缩容、故障域或跨多源索引。
重新评估不等于立刻外置。先用本章评估集和成本证据比较 PostgreSQL、扩展与 外部搜索系统,保持双写、回放、校验和退出路径。
上一节:先定义检索任务与评估集 · 返回本章目录 · 下一节:模糊匹配与拼写容错 · 查看全书目录 · 查看索引中心
15.3 模糊匹配与拼写容错
全文检索把不同词形归一到 lexeme,却不会自动把错拼词改成正确词。
pg_trgm 从另一个角度补召回:不先理解语言,而是比较字符三元组。
这使它很适合人名、标题、标识符和拼写容错,也意味着它可能找到“长得像” 但业务上完全不相关的字符串。
15.3.1 pg_trgm 相似度与距离
三元组如何形成
trigram 是三个连续字符。pg_trgm 忽略非单词字符;对每个词,左侧补两个
空格,右侧补一个空格。官方例子中 cat 产生:
两个字符串共享的 trigram 越多,similarity(a, b) 越高。它的取值在
0–1,距离运算符则是:
这不是 Levenshtein edit distance。字符插入、删除或换位会同时影响周围 多个 trigram;短字符串可用 trigram 更少,一个字符的影响会更大。
查看:
本章扩展装在 shop_ch14,所以函数和 operator class 都全限定。普通安装
可能在 public;不要假设 search_path。
similarity、word_similarity 与 strict 版本
三个函数回答不同问题:
| 函数 | 比较范围 |
|---|---|
similarity(a,b) |
两个完整字符串的 trigram 集合 |
word_similarity(a,b) |
a 与 b 中任一连续 trigram extent |
strict_word_similarity(a,b) |
extent 还必须落在词边界 |
例如 query 是一个短词,title 是多个词时,完整字符串 similarity 会被 title
额外字符稀释;word_similarity(query, title) 更像是在 title 中寻找最相似
局部。strict 版本适合不希望跨词边界拼接的场景。
参数顺序很重要。官方把 word_similarity(first, second) 描述为 first 的
trigram 集与 second 某一连续 extent 的最大相似。对称函数 similarity
可以交换,word_similarity 的语义不要想当然地交换。
本章用:
这样同时保留整串与局部词候选。lower 使表达式与索引表达式一致;官方文档
还说明默认构建中的 trigram similarity 本身不区分大小写,但显式规范化能
让应用合同与表达式索引更清楚。
参见
pg_trgm Functions and Operators。
函数分数、布尔门槛与距离排序
pg_trgm 提供三组布尔操作符:
默认门槛在本章 PostgreSQL 18 文档中分别为 0.3、0.6、0.5。它们是 session
GUC,可以 SET LOCAL,不应靠某个连接之前遗留的值:
门槛控制 candidate set,函数分数控制 set 内次序。若只写:
就会永远返回十行,即使后几行分数是 0;也不一定能利用 GIN 做候选过滤。 这是“Top-K 必有结果”最常见的误判。
本章 fuzzy_ranking 故意对过滤后的全部小集合排名,不设阈值。它让评估
框架看到低分尾部,也展示为何生产必须选择门槛、最小结果可信度或 no-result
行为。
索引操作类决定可用路径
本章:
强制 probe:
得到:
目录同时验证:
PostgreSQL 官方 pg_trgm 提供 GiST 与 GIN opclass。两者都支持相似操作符,
也支持 trigram 可提取的 LIKE、ILIKE、正则与等号查询;普通等值查询
通常还是 B-tree 更合适。
若需求是:
GiST 能提供 KNN distance 路径;官方文档明确指出某些 distance Top-N 形式 能高效使用 GiST,而不能用 GIN 直接完成同样的有序扫描。选择前先写清楚是 “按门槛过滤大量候选”还是“按距离取最近 K 个”。
15.3.2 前缀、包含、拼写错误与候选召回
先按意图选算子
| 用户意图 | 首选起点 |
|---|---|
| SKU 完全相等 | B-tree = |
| 规范化前缀补全 | B-tree/pattern opclass 或专门前缀设计 |
| 任意位置包含 | trigram LIKE '%term%' |
| 错拼容忍 | % / word similarity + threshold |
| 词形、布尔词、短语 | FTS |
| 模型表达的语义 | vector |
能被 trigram 索引支持,不等于它是所有文本谓词的首选。用 trigram 做每一次 等值查询,通常比普通唯一/B-tree 索引更大、更贵、语义也更弱。
短输入是天然边界
trigram 需要可提取的三字符片段。官方文档提醒:LIKE 或正则模式若没有
可提取 trigram,会退化为 full-index scan。对一两个字符的自动补全:
- trigram 区分力低;
- 候选可能巨大;
- 门槛对短词异常敏感;
- 用户每敲一个字符就查询会放大负载。
常见策略:
长度规则要按语言与规范化后的 token 定义,不要只按 UTF-8 byte 数。
规范化必须与索引表达式相同
本章索引的是:
所以 predicate 也写 lower(title)。生产可能还要处理:
- Unicode normalization;
- accent folding;
- 标点与空白;
- 全角/半角;
- SKU 中应保留的连字符;
- locale/collation;
- 同义缩写。
若规范化函数不 immutable,或查询表达式和索引表达式不同,表达式索引可能 无法使用。更重要的是:过度规范化会把本来不同的业务标识合并。先把规则做成 测试向量,再决定存储列或表达式索引。
门槛不是一次拍脑袋
对每个 query,按 score 取候选并与标注比较:
再画出或列出不同 threshold 下:
门槛可能按字段、查询长度或语言分层,但每多一个规则就多一个需要版本化和
回归的参数。不要在应用各处散落 0.2、0.3、0.45。
召回池与展示结果要分开
模糊匹配很适合 召回候选,不一定适合最终排序:
如果 title 很短、候选同质,trigram 分数可以直接做主要排序;如果 document 很长、字段复杂或业务相关性与字面差别大,它更适合作为一项 feature。
本章 q04 coffee bean grinder 的模糊前三名包含商品 14(coffee scale),
却漏掉标注相关的 espresso machine,说明“字符串看起来像”会压过工作流上
相关但字面不同的对象。
15.3.3 与全文检索的互补和重复
用失败矩阵判断互补
本章观察:
| query | FTS matches | fuzzy top-3 relevant | 解释 |
|---|---|---|---|
| q01 exact words | 1 | 3 | FTS 精确,fuzzy 扩展同类 |
| q02 two typos | 0 | 3 | trigram 补错拼 |
| q03 descriptive phrase | 1 | 3 | 描述命中与标题相似都可工作 |
| q04 grinder | 1 | 2 | fuzzy 被字面相似干扰 |
| q05 espresso task | 1 | 3 | 两路互补 |
| q06 trail hydration | 2 | 2 | 两路都漏一个字面较远对象 |
| q07 technical typos | 0 | 3 | trigram 补技术词错拼 |
| q08 exact long-tail | 1 | 3 | FTS 精确,fuzzy 给邻近书籍 |
这比“FTS + trigram 效果更好”更有用。它指出:
- 哪些查询需要 fallback;
- 哪些查询适合 union;
- 哪些弱候选可能污染最终结果;
- 下一批标注应该补什么失败类型。
不要做脆弱的二选一 fallback
一种常见实现:
它简单,但“FTS 有一条结果”不代表召回充分。q06 的 FTS 有两条,却漏掉 第三条相关对象;fallback 不会启动。
更稳健的做法是并行产生有限候选:
然后保留 source/rank,去重并融合。这样能看到一条对象由几路支持,也能在 延迟预算不足时按证据关闭某一路。
也不要无条件扩大候选
多一路候选会增加:
- index/heap 读取;
- SQL 排序与去重;
- 重排器输入;
- 解释复杂度;
- 弱信号把强结果挤下去的机会。
本章 RRF 的 q02 就是反例:纯 fuzzy 把最理想商品排第一;vector 与 lexical 信息加入后,混合把商品 3 排到第一,NDCG 下降。
候选源的采用条件应写成:
可解释证据
给每个最终结果至少保留:
不必把内部数值全部暴露给最终用户,但调试与审核必须能重建。
一条可操作的路由原则
先不要设计“智能路由器”。以简单证据开始:
每条规则都需要 query segment 指标,最终也可能被统一并行候选替代。路由器 本身是一个模型;如果没有独立评估,它只是在隐藏失败。
本节验收
本章同时固定两类证据:
行为证据回答“结果如何”;机制证据回答“数据库能否走这条路径”。二者不能 相互替代。
上一节:PostgreSQL 全文检索 · 返回本章目录 · 下一节:可复现的向量检索 · 查看全书目录 · 查看索引中心
15.4 可复现的向量检索
向量列并不自带“语义”。它只是一种有维度的数值,配合一个距离/相似度函数 产生次序。语义来自向量生成过程;查询性能来自精确或近似执行路径;质量来自 外部标注。
把三者分开,才能复现,也才能退出。
15.4.1 维度、距离度量与归一化
一列必须有模型身份
最小表不是:
而是至少:
真实系统还可能需要:
同一维度不代表同一空间。两个 1536 维模型生成的向量不能因为类型相容就放进 同一个近邻索引比较。一次模型升级应视为数据迁移,而不是悄悄覆盖列。
本章用:
四维分别是 audio、kitchen、outdoor、books 的人工主题坐标。它的价值是每个 距离可手算、没有 API 漂移;它不证明真实 embedding 的语义能力。
四个常见距离/相似合同
pgvector 0.8.4 对 vector 提供:
| 运算符 | 含义 | HNSW opclass |
|---|---|---|
<-> |
L2 / Euclidean distance | vector_l2_ops |
<#> |
negative inner product | vector_ip_ops |
<=> |
cosine distance | vector_cosine_ops |
<+> |
L1 / taxicab distance | vector_l1_ops |
还有 binary vector 的 Hamming/Jaccard,以及 halfvec、sparsevec 的相应
能力;本章不展开。
pgvector 让“越小越近”符合 PostgreSQL ascending index scan:
不要把 <#> 的负数直接叫“相似度”。用于展示内积时要乘 -1,用于索引
排序则保留升序负内积。
选距离要回到模型合同
L2:
它同时受方向与向量长度影响。
cosine similarity:
更强调方向,cosine distance 通常为 。
inner product:
同时受方向与模长影响。某些模型明确训练为用 dot product 排序。
若所有向量都被归一化为单位长度:
此时 L2、cosine 与 inner product 的次序存在紧密关系;但仍应按模型文档 和性能目标选 opclass,不能因为数学关系就混用未归一化数据。
模型 ADR 要回答:
一个常见错误:
索引是 cosine,查询却用 L2;二者 operator family 不匹配,不能期待该索引 支持查询。目录与执行计划必须同时复核。
维度是存储和索引合同
本章使用 vector(4),数据库拒绝不同维度的值。pgvector 0.8.4 的 HNSW
vector 索引支持最多 2000 维;halfvec、bit、sparsevec 有各自上限。
这些是当前版本事实,升级前重查官方文档。
“维度越高语义越好”不是规律。维度会影响:
- 每行存储;
- index tuple 与图内存;
- 构建和查询计算;
- WAL、备份和网络;
- 模型能力与压缩损失。
先以模型要求为输入,再用真实规模测成本。不要为了迎合数据库索引上限随意 截断向量;降维、量化或子向量都需要新的质量基线。
15.4.2 精确近邻与近似索引
默认是精确搜索
没有近似索引时:
PostgreSQL 对所有合格行算距离、排序并取前三。这是 exact nearest neighbor: 在当前快照和距离合同下,召回是完备的。它的成本随参与比较的行数、维度与 并行度增长。
本章质量视图不是直接执行一个可能被 HNSW 接管的 ORDER BY ... LIMIT。
它先产生所有 pair 的 distance,再用 window function 排名:
这使质量 golden 明确来自 exact 全集,与线上 planner 是否选择 ANN 无关。
强制 exact plan:
得到:
这是本章的 reference path。
ANN 用召回换速度
pgvector 支持 HNSW 与 IVFFlat:
官方 README 明确指出,增加 approximate index 后,同一查询可能得到不同 结果;必须把这种差异视为算法合同,而不是数据库 bug。
本章建:
默认值在 pgvector 0.8.4 是 m=16、ef_construction=64;本章用较小值
只是让夹具声明显式,并非生产建议。
HNSW 建多层图:
- 通常有较好的 speed/recall trade-off;
- 构建更慢、内存更多;
- 不需要像 IVFFlat 那样先训练 lists,所以空表也能先建;
- 持续写入仍要维护图和 WAL。
IVFFlat 把向量分到 lists,查询探测部分 lists:
- 构建更快、内存较少;
- 通常查询 speed/recall trade-off 低于 HNSW;
- 建索引前应有代表性数据;
- lists/probes 选择直接影响 recall。
这不是永久排名。数据规模、更新率、过滤、内存和 SLO 会改变选择。
索引查询形状必须匹配
让 ANN index 生效,典型查询要:
只写距离范围:
不一定形成同样的 ordered index path。pgvector 官方建议把范围条件与
ORDER BY、LIMIT 结合。
还要避免把 indexed expression 包进不等价表达式:
展示 similarity 可以在外层计算;候选扫描保持与 opclass/operator 一致。
ANN recall 必须相对 exact 定义
对于同一 query 与 filters:
本章 ann-compare.sql 在同一事务中:
- 从 exact 质量视图取 q06 前三;
- 强制 HNSW path 并取同样过滤后的前三;
- 求集合交集。
结果:
一次查询、17 行、四维向量上的 1.0 不是生产结论。正式测量至少按:
报告分布,并把 exact 抽样任务长期保留。pgvector 官方 monitoring 建议同样 是关闭 index scan 取得 exact 结果,再与近似结果比较。
15.4.3 索引参数、过滤条件与召回代价
HNSW 有构建参数和查询参数
构建参数:
| 参数 | 作用 | 增大通常带来的影响 |
|---|---|---|
m |
每层最大连接数 | 图更密、潜在召回更好、空间/构建更贵 |
ef_construction |
构图候选列表大小 | 潜在召回更好、构建/写入更慢 |
查询参数:
| 参数 | 作用 | 增大通常带来的影响 |
|---|---|---|
hnsw.ef_search |
查询动态候选列表 | recall 上升机会、延迟与工作量上升 |
pgvector 0.8.4 默认 ef_search=40。单次实验应使用事务局部设置:
不要把 session pool 中遗留的 GUC 当作服务配置;也不要只测一个值。需要 绘制:
参数发布要与 index build identity、模型、数据快照和查询集一起版本化。
过滤为什么会“吃掉”结果
pgvector 0.8.4 官方说明,approximate index scan 的普通过滤是在扫描后应用。 假设只有 10% 行满足过滤,初次探索 40 个候选,平均可能只留下约 4 个;需要 K=10 时就会短缺。
这不是 SQL 三值逻辑错,而是 ANN 没继续探索足够候选。
可选策略:
- 过滤列 B-tree + exact search 当过滤后集合很小,先用普通索引缩小到少量行,再 exact 排序,常常更快且 recall 完整。
- iterative index scan 过滤后不足时继续探索 ANN。
- partial HNSW index
少数稳定类别各有索引,例如
WHERE category_id=123。 - partitioning 过滤维度离散且能形成真实数据生命周期/裁剪边界时分区。
- 扩大 candidate/ef_search 简单但增加每次查询成本,仍要测。
本章同时创建:
它提醒读者:向量检索仍然是 PostgreSQL 查询,普通关系索引和选择性统计并未 失效。
iterative scan 的 strict 与 relaxed
pgvector 从 0.8.0 起支持 iterative index scan:
strict 保持距离严格次序;relaxed 允许轻微乱序,可能获得更好的 recall。 relaxed 结果若要重新严格排序,官方示例使用 materialized CTE 后在外层排序。
迭代不会无限进行,还受:
等边界影响。返回 K 行、返回顺序与 recall 都要分别断言。
本章用 strict:
只为让证据顺序稳定。它不能让 approximate graph 等价于 exact scan。
partial index 不是“每个租户建一个”
官方建议在过滤值很少时考虑 partial HNSW:
若有成千上万租户或高基数 ACL,给每个值建索引会造成:
- index 数量爆炸;
- DDL/catalog/autovacuum 管理困难;
- 写放大;
- planner 规划开销;
- 备份恢复和升级时间增长。
高基数过滤更可能需要:
- exact on a selective conventional index;
- 合理分区;
- 分片/路由层;
- 两阶段候选与业务过滤;
- 或重新选择搜索架构。
任何方案都必须验证权限过滤不会因“为了 recall”被放宽。
构建与维护的安全线
HNSW 构建图若能放入 maintenance_work_mem 会更快;官方也警告不要把该参数
设到耗尽 server 内存。生产建索引:
可以减少阻塞写入,但会更慢、产生更长资源占用,失败还可能留下 invalid index。要监控:
并检查:
本章 setup 是离线教学重建,所以用普通 CREATE INDEX;不得把这个选择直接
搬到在线主表。
何时退回 exact
以下场景 exact 可能更好:
- 过滤后只剩少量行;
- K 较大,ANN 需要探索大部分集合;
- 质量要求不允许近似漏召回;
- 数据规模尚小;
- 向量更新频繁,ANN 维护成本超过收益;
- exact 可以在可接受延迟内并行完成。
先测 crossover,再引入 ANN。拥有 HNSW 扩展能力不构成使用理由。
15.4.4 冻结文本、模型标识、许可证、向量文件与校验和
“同一个模型”仍可能生成不同向量
可复现输入至少包括:
只记录营销名,例如 embedding-v3,不够。云服务可能滚动更新后端;本地模型
可能因权重、tokenizer、运行库或量化不同而变化。
本章如何消除外部变量
商品与 query 的向量都保存在冻结 CSV 中。setup 载入后,数据库导出:
再与源文件逐字节 cmp。这比“查询结果差不多”严格得多:向量文本一个数字
变化就失败。
文件 hash 与数据库 checksum 各负责什么
fixture manifest 固定:
最终数据库 business_checksum 则覆盖:
二者互补:
- file hash 证明输入资产没变;
- database checksum 证明装载、计算和最终行为没变。
运行时间、OID、绝对路径、索引物理页大小不进入 business checksum,因为它们 不是跨重建稳定的业务事实。
许可证和数据边界不能等上线后再补
向量可能泄露源数据特征,也可能受模型服务条款约束。ADR 至少记录:
“只传向量,不传原文”也不是自动匿名。是否能从向量推断敏感信息要按威胁 模型评估。
本章文本和数字都是项目自有合成 fixture,不调用外部服务:
这使仓库可重复,不替真实项目完成法务评审。
模型升级要双版本迁移
不要:
在同一列原地混写。更稳健的流程:
若维度改变,类型和索引自然需要新对象;即使维度相同,也应保持逻辑隔离。
退出路径
pgvector 列可转文本导出:
但“能导出字符串”只是技术出口第一步。还要证明目标系统:
- 能按同一精度解析;
- 保留 document/model identity;
- 使用同一距离与归一化;
- 重建索引后质量不变;
- 在切换窗口双读校验;
- 能回放迁移期间更新。
本章精确 reset 删除 shop_ch15,却保留第 14 章持有的扩展;它演示了对象
边界,不是生产数据迁移演练。
上一节:模糊匹配与拼写容错 · 返回本章目录 · 下一节:混合检索与排序验证 · 查看全书目录 · 查看索引中心
15.5 混合检索与排序验证
混合检索的价值来自失败互补,不来自方法数量。每加一路候选,都同时增加 召回机会、查询成本和排序污染。
本节先保留每路独立排名,再用只依赖名次的 RRF 合并;最后用同一标注集找出 收益与退化。
15.5.1 词法、模糊和语义候选合并
每一路先独立成立
本章先建三个排名视图:
共同输出:
向量视图还保留 distance。每路都使用相同资格条件:
如果三路过滤不同,融合后的差异既可能来自检索能力,也可能来自候选资格, 指标无法解释。授权、租户和生命周期过滤应在每路都保持同一语义。
source rank 是融合接口
本章把每路前四名规范成:
为什么用 UNION ALL:
- 同一商品被多路召回是有价值的支持证据;
- 普通
UNION会按整行去重,但 source/rank 不同,本来也不会合并; - 后续应显式按
(query_id, product_id)聚合,而不是让 set operator 隐式决定。
每路只提供 candidate,不应在视图内部偷偷调用另一路。这样才能:
- 单独关闭某一路;
- 比较增量质量;
- 观察每路延迟;
- 定位失败;
- 给最终结果解释 source。
候选深度是资源预算
本章:
它只适合 17 行夹具。生产候选深度影响:
应按 query segment 做曲线:
当一条 source 加深只增加重复/低质量对象而不提高最终 recall,就没有继续 扩大的理由。
空结果与低分结果不同
全文视图只输出 @@ 命中对象,所以 q02/q07 没有行。
模糊视图对全部合格对象排名,即使低分也有行。这是评估 fixture 的有意设计, 生产则应加候选门槛。否则:
仍可能被融合成一个看似精确的总排名。
候选接口应区分:
超时不能被当作“该方法认为没有相关结果”,否则线上质量诊断会混淆能力与 故障。
去重后的 evidence 仍要保留
一个商品多路出现时,最终记录应能表达:
本章 hybrid_rrf_ranking.sources 用 text array 保存 source 名称,完整排名
仍可回查原视图。生产可把细节写进响应 trace、审计表或离线日志,不必塞进
业务表永久保存。
SQL 内混合的边界
在 PostgreSQL 内完成候选和融合有明显优势:
- 与事务快照、状态和权限过滤同处一处;
- 少一次跨系统网络与数据同步;
- SQL 可以直接被
EXPLAIN、统计和审计; - 小中规模问题结构简单。
但如果模型推理、复杂学习排序、跨多源索引或独立扩缩容占主导,融合可能更 适合应用/检索服务。决策标准不是“SQL 能不能写”,而是:
15.5.2 分数归一、倒数排名融合与重排
为什么不能直接加原始分数
本章三路原始值:
即使都变换到 0–1,也没有共同概率含义。下面只是拍脑袋:
它会隐含:
- 三种尺度的形状可比;
- 一单位变化意义相同;
- 权重对所有 query segment 相同;
- 极值与分布稳定;
- 模型升级后仍适用。
若要线性加分,应使用标注数据做 calibration/learning-to-rank,并在 fresh holdout 上验证。
RRF 只比较名次
Reciprocal Rank Fusion 对 document :
其中:
- 是 document 在 source 中的名次;
- 未出现在 source candidate depth 内就没有该项;
- 是可选 source 权重;
- 平滑头部名次差。
本章:
SQL:
再:
pgvector 官方 Hybrid Search 小节也把 RRF 或 cross-encoder 列为融合路径; 参见 pgvector 0.8.4 Hybrid Search。
k 不等于返回 K
RRF 中的小写 是平滑常数,不是最终结果数。增大它会让头部第 1 与 第 4 的单路差距变小,多路共同支持相对更重要;减小它会放大头部名次差。
不要因为经典示例常用 60 就把它当数学常数。本章用 60 是显式 proposal 参数。调参时要同时版本化:
并报告每个 query segment 的变化。
RRF 的优点与盲区
优点:
- 不要求原始 score 同尺度;
- 一个 document 被多路高排会自然加分;
- 实现与解释简单;
- source 新旧版本可独立。
盲区:
- 丢失 source 内分数差距:第 1 比第 2 好很多或只好一点都看不出来;
- candidate depth 外信息完全消失;
- 弱 source 也会投票;
- 不直接使用业务新鲜度、库存、质量、安全等 feature;
- 不会自动学会 query-dependent weight。
所以 RRF 是可靠 baseline,不是排序终点。
source weight 要谨慎
加权 RRF:
很容易实现,但“vector 权重 1.5”必须由指标支持。若某一路只在错拼 query 有帮助,更合理的方向可能是:
- query segment 路由;
- 以置信门槛启用;
- 学习排序;
- 或扩大/收窄该路 candidate depth。
全局权重会把局部收益和局部伤害平均掉。
两阶段重排
当候选生成需要快、最终质量需要更多 feature:
关键合同:
- 第一阶段 recall 必须足够;被漏掉的对象无法被重排救回;
- 第二阶段有独立超时与 fallback;
- 重排模型有版本、输入与许可证;
- fallback 次序仍经过评估;
- 在线日志保留 candidate pool 与最终选择。
外部 cross-encoder 可能最贵、最慢、最敏感,不要让数据库事务长时间等待 网络推理。常见做法是在数据库取候选后结束事务,再用版本化快照数据重排; 若新鲜度要求更高,需要明确一致性策略。
精确重排近似候选
ANN 常用模式:
第二层用原向量精确重排 candidate,可以修正量化/子向量/relaxed ordering 内部次序,却不能召回第一层没选中的对象。最终仍要用 exact-relative recall 评估 candidate generator。
15.5.3 用标注集比较质量,不用单个“好看案例”
结果表只是起点
运行:
得到 quality-summary.csv:
平均值显示 hybrid 在本集合上 mean NDCG 最高,但不能到此结束。
看每个 query 的退化
quality-detail.csv 暴露:
q02 的前三名:
商品 1 是 grade 3,纯 fuzzy 放第一;混合受到另外两路名次影响,把 grade 2 商品 3 放第一。因此:
生产 release gate 应同时有:
- aggregate threshold;
- 关键 query segment threshold;
- worst-case/低分位保护;
- 不允许退化的 canary query;
- filter/security hard assertions。
四种策略各告诉了什么
全文
它不是“差”,而是 candidate role 较窄。单独 NDCG 低不等于应删除;它可能 提供最可解释、最精确的头部证据。
模糊
它证明字符相似的价值,也暴露业务邻近但字面不近的盲区。
精确向量
这是“召回好、排序不够好”的典型 candidate generator。
RRF
它是当前 proposal baseline,而不是普遍胜者。
8 个查询没有统计外推力
本章数字可以证明:
- SQL 指标计算可复现;
- fixture 改动会被发现;
- 策略之间确有可解释差异;
- 反例和 filter guard 可持续回归。
不能证明:
- 真实流量的总体质量;
- 各语言/用户/类别公平;
- 指标提升具有统计显著性;
- 点击、转化或任务完成提高;
- 线上 P95/P99;
- embedding 模型优劣。
真实评估集应从代表性查询日志和业务任务构建,保留 fresh holdout。对线上 A/B:
离线相关性是上线前门槛,不是用户价值的最终替代。
指标 SQL 也要审校
本章 quality_per_query 明确建立所有策略 × 所有查询的 grid,再 LEFT JOIN
返回结果。这样没有返回行的 query 仍有 0 分;若从 ranking 表直接 GROUP BY,
空 query 会消失,平均值被悄悄抬高。
它还:
- 固定前三;
- 分母使用 3,不使用实际 result count;
- 对未标注 result 记 grade 0;
- 用 grade 计算 DCG 与 ideal DCG;
- 稳定按 grade/product id 生成理想排序。
审查 retrieval 指标实现时,重点找:
这些 bug 往往比检索算法差异更大。
发布结论的正确句式
可以写:
在
ch15-search-v1的 8 个冻结查询、24 条分级标注和精确向量 golden 下,等权 RRF 的 mean NDCG@3 为 0.962929,并覆盖全部标注相关对象; q02 相对纯 fuzzy 退化,必须保留分段监控。结果不外推到真实语料。
不要写:
混合检索准确率 96%,优于其他方案。
后一句把 NDCG 当 accuracy,把小合成集当总体,也隐去了反例。
上一节:可复现的向量检索 · 返回本章目录 · 下一节:扩展部署与运行代价 · 查看全书目录 · 查看索引中心
15.6 扩展部署与运行代价
到目前为止,我们只证明了检索行为。生产采用还要回答:
本节把 PostgreSQL 机制映射到 Pigsty 4.5,但仍以 live 节点、系统目录和实际 证据为准。
15.6.1 安装检索扩展并核对版本
复用第 14 章的三层状态
检索扩展仍然有三层:
pg_trgm 随 PostgreSQL contrib 交付;vector 的项目/包常叫 pgvector,而
SQL 扩展名是 vector。不要混写:
本章不会安装或升级它们,而是要求第 14 章最终状态:
如果版本、owner、schema 或 marker 不符,context.sql 拒绝。下游实验不能
悄悄接管上游扩展。
Pigsty 中分开“装包”和“启用”
Pigsty 4.5 当前文档指出:
pgvector随pgsql-main默认安装;pg_trgm位于默认启用扩展列表;- 可通过
pg_packages/pg_extensions影响包; - 可通过
pg_default_extensions影响数据库默认启用对象。
本章给出的
pigsty-declaration.example.yml
只是合并片段:
实际版本中 pgvector 可能已被 pgsql-main 包别名覆盖,重复声明是否允许、
包名如何展开、目标 OS/PG major 是否有构建,都要用目标 inventory 与
Pigsty 文档确认。
教学夹具把扩展放在 shop_ch14,是为了凸显 namespace/owner。生产片段使用
public 只是示例,不是推荐所有扩展都堆进 public。schema 选择必须同时
满足:
- extension 是否 relocatable/是否强制 schema;
- 应用是否用全限定名称;
search_path安全;- dump/restore;
- 运维与升级脚本;
- 权限最小化。
L1 每个节点都要核对
物理复制会把数据库对象复制到备库,但不会把 OS 软件包和动态库通过 WAL
复制过去。主库 CREATE EXTENSION vector 成功,某备库仍可能因缺
vector.so 在查询、恢复或升主后失败。
对每个 L1 host 收集:
再核对目标 major 的 package manager 版本与文件 hash。不能从当前 shell
PATH 里的 pg_config 猜正在运行的 server;先从实例确认 server major,
再找对应安装树。
数据库内:
控制文件可见性:
两份目录回答不同问题:
pg_available_extension_versions:server 支持文件允许哪些版本;pg_extension:当前 database 创建了哪个对象版本。
安装权限不要交给应用
本章角色边界:
pg36_app 对 product_search UPDATE 必须返回:
真实商品服务当然需要写入,但写角色不等于在线读角色必须拥有表级任意 UPDATE。 可使用:
- 受控迁移角色;
- 列级权限;
- 参数化函数;
- 独立异步 embedding worker;
- 队列/作业表状态机。
尤其不要让业务请求角色创建、升级或删除扩展。
证据边界
本章正式运行:
所以 manifest 明确:
移植到 Pigsty 后要补:
15.6.2 观察索引体积、构建、查询和维护
先列出四类物理对象
本章商品表有:
基础盘点:
17 行夹具看到的 8/16/32 KiB 级数字主要由最小页分配决定,不能计算
“每百万商品多少 GiB”。size-catalog.csv 只是证明对象存在且非零。
生产 size baseline 应记录:
否则两个 size 数字不可比较。
构建要观察过程与阻塞
初始离线载入通常:
比逐行维护索引高效。在线已有表则通常考虑:
它减少对写入的阻塞,却有更长构建时间、额外扫描、资源占用和失败状态。运行时 观察:
并结合:
发布后必须确认 pg_index.indisvalid/indisready/indislive,不能只看 DDL
client exit。
查询观测分机械与用户两层
机械层:
看:
- index/seq/bitmap path;
- actual rows 与估算;
- filter removed rows;
- buffers 与 temp I/O;
- planning/execution time;
- 生效的非默认 settings。
用户层:
执行快但 recall 差,不是成功;quality 高但 P99 超时,也不是成功。
Pigsty 默认启用/预加载的 pg_stat_statements 可帮助聚合 SQL 调用与耗时,
但要为检索 query 保持可归一的 SQL 形状,并将业务 query id/segment 放在
受控日志或 trace,而不是拼进 SQL 注释造成 statement 指纹爆炸。
写入成本要单独压测
文本变化会更新两个 GIN 和 generated tsvector;向量变化会更新 HNSW。
测:
可以在受控事务前后比较:
再用 pg_wal_lsn_diff 估算该受控批次产生的 WAL。并发生产环境还有其他
事务,不能把全局 LSN 差未经隔离就归给一个作业。
HNSW 维护不是普通 B-tree 的复制粘贴
pgvector 官方说明 HNSW vacuum 可能耗时,并给出先 REINDEX INDEX CONCURRENTLY 再 VACUUM 的一种加速建议。这是需要谨慎评估的维护动作,
不是每晚固定模板:
- reindex 需要额外磁盘与构建资源;
- concurrently 有更长窗口和失败恢复;
- vacuum 仍要处理 heap;
- 副本会重放相关 WAL;
- 索引重建期间 recall/latency 要观测;
- 新 index 的参数与 opclass 必须一致。
先在代表性副本/演练环境测周期,再决定维护阈值。
监控矩阵
| 维度 | PostgreSQL 证据 | Pigsty/平台视图 |
|---|---|---|
| SQL 调用/耗时 | pg_stat_statements、logs |
dashboard/alerts |
| 执行计划 | EXPLAIN、auto_explain |
集中日志 |
| index 使用 | pg_stat_user_indexes |
表/索引面板 |
| 表生命周期 | pg_stat_user_tables |
vacuum/bloat 面板 |
| 构建进度 | pg_stat_progress_create_index |
变更任务证据 |
| WAL/副本 | LSN、replication views | HA/replication 面板 |
| 主机资源 | PostgreSQL/OS stats | node/PG exporter |
| 质量/ANN recall | 自建 eval job | release/SLO dashboard |
最后一行不能由通用数据库 exporter 自动推导。检索质量是业务测量,必须把 evaluation job 当成一等生产组件。
15.6.3 外部嵌入生成的权限、费用与数据边界
不要从数据库触发器同步调用外部 API
一个危险设计:
它把:
- 外部网络延迟;
- rate limit;
- provider outage;
- 费用;
- 密钥;
- 不确定重试;
放进数据库锁与事务寿命。远端已收费成功、本地事务却回滚时,还会出现不可 原子化的副作用。
更稳健的异步结构:
可用 outbox、作业表或消息系统实现;第 13 章已经讨论过触发器与异步边界。
幂等身份
一次 embedding 任务的自然 key 可以是:
写回时做 compare-and-set:
如果源文本在推理期间变化,旧结果不能覆盖新版本。重试同一 key 应复用已完成 结果或安全 upsert,避免重复收费。
worker 最小权限
embedding worker 通常需要:
- 读取允许外发的字段;
- 读取/更新自己的 job;
- 写特定 vector/model/status 列;
- 不需要 DDL、扩展 owner、超级用户;
- 不需要任意读其他敏感 schema。
API secret 放在 secret manager/受控运行环境,不存进 SQL、YAML 仓库、表 comment 或 evidence manifest。Pigsty 负责 PostgreSQL 平台,不意味着模型 密钥应注入数据库 server 进程。
若文本不能离开边界,可以:
- 在受控网络自托管模型;
- 只对批准字段生成;
- 做脱敏/分区处理;
- 或拒绝向量方案。
“先接 API,之后再补合规”不是试点策略。
费用模型
至少估算:
[ Cost = InitialBackfillTokens \times Price
- DailyChangedTokens \times Price
- QueryTokens \times Price
- RetryWaste
- Storage/Index/Compute ]
还包括:
- backfill 期间 API 并发与限流;
- 模型升级全量重算;
- 双版本存储与索引;
- 失败重试和重复调用;
- query embedding cache;
- 数据出口与网络;
- 本地推理 GPU/CPU 和运维。
费用控制要有:
缓存 query vector 时,key 必须包含 exact normalized text、model、template 与 版本;只按原始字符串缓存会在模型切换时串用旧空间。
数据生命周期要双向传播
删除或更正 source document 时,检查:
物理删除在 PostgreSQL 中还受 MVCC、vacuum、WAL 与备份保留影响。法律上的
删除承诺必须和备份/恢复政策一致,不能只执行一条 DELETE。
故障与降级
模型服务不可用时,搜索不一定要整体不可用:
每种 fallback 都要在离线标注集上测质量、在线压测容量,且不能放宽权限过滤。
上线前问题
- 哪些字段能外发,依据是什么?
- provider 是否保留输入/输出、是否用于训练?
- 模型版本是否可固定,变更如何通知?
- 单次、每日、回填、升级的费用上限是什么?
- 谁能读原文、调用服务、写 vector?
- 如何证明 vector 对应当前 source hash?
- 失败如何重试,何时进入 dead letter?
- 模型切换如何双写、评估、回退?
- 删除、备份与 provider 侧数据如何协调?
- 无模型服务时,业务还能提供什么质量的结果?
回答不了这些问题时,向量 PoC 可以继续离线,不能进入生产写链路。
上一节:混合检索与排序验证 · 返回本章目录 · 下一节:实战:pg36_shop 商品混合检索 PoC ·
查看全书目录 · 查看索引中心
15.7 实战:`pg36_shop` 商品混合检索 PoC
本节把前六节压成一个可审计的 1.3-proposal:
正式证据来自 Homebrew PostgreSQL 18.6 的受控开发数据库。Pigsty 4.5 的 声明与运维职责已经映射,但本地没有运行 L1,因此不把直接 PostgreSQL 结果 伪装成 Pigsty 集群验收。
破坏边界
task.sh all会删除并重建带精确 marker 的shop_ch15。它不删除pg_trgm、vector或第 14 章 schema,只适合本书本地/开发夹具。 生产不得执行这条“删后重建”路径。
15.7.1 用冻结向量离线复现实验
前置状态
本章沿用前章 libpq service:
不要把密码放进命令行、脚本或 evidence。
context.sql 要求:
任何一项不符就停止。本章不会“顺便安装一个更接近的版本”,因为那会同时 改变扩展与检索两个变量。
输入清单
ch15 关键实验输入
fixture-manifest.json 固定:
这些 hash 识别的是仓库输入。proposal checksum 识别的是版本、方法、质量和 验收合同,二者不要混淆。
单步建立
setup 先检查已有 shop_ch15:
-
schema owner 必须是
pg36_owner; -
schema comment 必须是:
-
relation/index/view 必须在 21 个对象白名单中;
-
每个对象必须有同一 marker;
-
schema 不能有未知 routine/operator/opclass。
碰撞保护通过后,它按依赖顺序删除旧视图/表/schema,不用 CASCADE,再以
pg36_owner 创建。
核心表:
索引:
排名/质量视图:
完成摘要:
逐字节回读
它用 COPY ... TO STDOUT CSV HEADER 从数据库导出 corpus/query/judgment,
再执行:
这验证:
- SQL loader 没有手工录错;
vector::text可稳定导出;- 布尔、字符串、顺序和 rationale 都一致;
- 重建后数据身份没有漂移。
如果 CSV 行顺序没有显式 ORDER BY,逐字节比较没有意义。本章三条 export
分别按 product id、query id、query/product id 排序。
为什么不调用真实模型
若测试运行时调用外部 embedding API:
- provider 可能改变输出;
- 网络/限流会让数据库实验不稳定;
- 凭据和费用进入教学流程;
- 无法区分模型漂移与 SQL 漂移;
- 离线读者无法复现。
所以本章把真实模型评估留作迁移任务。读者要替换为真实向量,应复制
ch15-search-v1 为新 fixture/version,保留旧 golden,不要覆盖四维输入后
继续沿用本章 checksum。
15.7.2 比较全文、模糊、向量与混合结果
查看词法解析
固定结果:
| query | parsed tsquery |
matches |
|---|---|---|
| q01 | 'wireless' & 'headphon' |
1 |
| q02 | 'wirel' & 'hedphon' |
0 |
| q03 | 'music' & 'go' |
1 |
| q04 | 'coffe' & 'bean' & 'grinder' |
1 |
| q05 | 'make' & 'espresso' & 'home' |
1 |
| q06 | 'trail' & 'hydrat' |
2 |
| q07 | 'postgr' & 'databs' & 'tune' |
0 |
| q08 | 'semant' & 'nearest' & 'neighbor' |
1 |
q02/q07 的 lexeme 并不会因为“看起来像错拼”自动改正。这个证据把 FTS 零召回定位在语言处理层,不是 GIN index 故障。
前三名全景
固定 product id:
| query | lexical | fuzzy | exact vector | hybrid RRF |
|---|---|---|---|---|
| q01 | 1 | 1,3,2 | 2,3,1 | 1,2,3 |
| q02 | — | 3,1,2 | 2,3,1 | 3,2,1 |
| q03 | 2 | 1,2,3 | 2,3,1 | 2,1,3 |
| q04 | 4 | 4,6,14 | 5,6,4 | 4,6,5 |
| q05 | 5 | 5,6,4 | 5,6,4 | 5,6,4 |
| q06 | 7,8 | 7,8,15 | 8,9,7 | 7,8,9 |
| q07 | — | 10,11,12 | 11,10,12 | 10,11,12 |
| q08 | 12 | 12,10,11 | 12,11,10 | 12,10,11 |
— 是无行,不是三条零分结果。
注意 q04:
这是互补成功例。
注意 q02:
这是融合退化例。两种都必须写进报告。
质量结果
得到:
| strategy | queries | P@3 | R@3 | MRR@3 | mean NDCG@3 | min NDCG@3 |
|---|---|---|---|---|---|---|
| fuzzy | 8 | .916667 | .916667 | 1 | .942881 | .842828 |
| hybrid_rrf | 8 | 1 | 1 | 1 | .962929 | .759192 |
| lexical | 8 | .291667 | .291667 | .75 | .613043 | 0 |
| vector_exact | 8 | 1 | 1 | 1 | .817314 | .631039 |
质量视图把未返回 query 保留为 0,避免 survivorship bias。
计划证据
断言:
这些文件通过禁用 planner 备选路径建立“能力证明”。正常 17 行查询走顺序 扫描很合理;不要把强制 index plan 贴成性能结果。
目录证据:
最终四个 index 都必须 valid/ready/live,opclass 与查询 operator 一致。
ANN 对照
结果:
这个 probe:
- 固定 q06 与 outdoor/active filter;
- exact 结果来自全量 window ranking;
- ANN 强制 HNSW;
- 用集合交集测 recall。
它没有报告毫秒,因为 tiny fixture 的时间不稳定且无业务意义。
应用权限
应用可读 q02/q08 混合结果和质量摘要。
未授权更新:
必须:
自动化不是只检查非零 exit;它同时检查精确 SQLSTATE,避免连接失败或语法 错误被误当成权限测试通过。
15.7.3 输出 ADR、质量证据、生产代价与退出路径
ADR 结论
search-adr.md 决定:
它还明确否决:
ILIKE替代完整检索;- 只用 FTS;
- 只用向量;
- 未校准原始分数直接相加;
- 用 HNSW 结果生成质量 golden。
proposal 是版本化合同
baseline-v1.3-proposal.json
固定:
- PostgreSQL/extension/Pigsty 参考版本;
- fixture/model/距离;
- index 与权限;
- 四种质量指标;
- ANN probe;
- business checksum;
- evidence 文件清单;
- reset 边界;
- 明确 limitation。
canonical JSON checksum:
更改 key 顺序或空白不会改变 canonical checksum;更改合同值会改变。
最终数据库状态:
business checksum 不包含运行时间、OID、index bytes 或绝对路径。
evidence 目录
一轮完整采集包含:
manifest.txt 记录 server、database、in-recovery、扩展版本、模型、Pigsty
证据边界、proposal checksum、fixture manifest checksum 与全部实验源文件
SHA-256。
review.py 不信任脚本“跑完了”,它重新解析证据并断言:
- 三份 export 与 source byte-identical;
- manifest hash 与真实文件一致;
- 17/8/24 行数;
- 唯一 inactive 商品为 17;
- 四种索引 AM/opclass/状态/marker;
- 应用 ACL;
- FTS match counts;
- 32 个策略×查询质量格;
- 79 条实际 top-3 ranking 记录;
- hybrid 每个 query 的 id 次序;
- ANN exact/approx intersection;
- 五份计划的关键 node;
42501权限失败;- final checksum 与 verify summary。
生产代价清单
当前 proposal 未给出性能线,因为本地 17 行不能回答:
迁移到业务数据后,ADR 需要附:
| 领域 | 最小证据 |
|---|---|
| 质量 | frozen + fresh queries,分段 P/R/MRR/NDCG |
| ANN | exact-relative recall curve |
| 查询 | P50/P95/P99、timeout、buffers、CPU |
| 写入 | rows/s、WAL/row、索引增长、vacuum |
| HA | replica query、lag、switchover/failback |
| 恢复 | clean environment restore |
| 模型 | version/input/license/cost/failure |
| 安全 | tenant/ACL canaries、secret/data boundary |
精确退出
手工 reset 需要两个一致的显式确认:
还会检查:
- context/database/writable instance;
- schema owner 与 marker;
- 21 个 relation 的精确白名单与 marker;
- 未知 routine/operator/opclass;
application_name LIKE 'pg36-ch15-%'的其他活跃 worker。
错误分别用自定义 SQLSTATE:
删除顺序:
没有 CASCADE。最终必须:
生产退出不是 DROP schema。生产要先:
15.7.4 验收采用 checklist:evidence,不设脱离场景的性能线
为什么不给“必须 10 ms”
延迟取决于:
在 17 行、热缓存、本地 socket 上测到的微秒/毫秒,既不能预测一亿行,也 不能作为读者机器失败线。硬写一个数字只会诱导为过测试而牺牲质量或关闭 安全过滤。
因此本章把 15.7.4 的规则定义为:
即每一类能力都必须有可复核证据;业务上线再给每项填入自己的 SLO。
本地机制验收
| 检查 | 证据 | 固定结论 |
|---|---|---|
| 输入身份 | manifest + byte cmp | 17/8/24,三份一致 |
| FTS 行为 | parsed query/match table | q02/q07 零命中 |
| fuzzy 行为 | ranks/quality | mean NDCG .942881 |
| vector quality | exact ranking | Recall@3 1 |
| fusion | RRF ranks/quality | mean NDCG .962929,有 q02 退化 |
| ANN 机制 | exact vs HNSW | q06 Recall@3 1,仅单点 |
| index 机制 | catalog + forced plans | GIN/GIN/HNSW 路径存在 |
| filtering | canary 17 | 所有排名均不出现 |
| ACL | catalog + expected failure | read-only,UPDATE 42501 |
| reset | token/target/active tests | P3660/P3661/P3663 |
| rebuild | second full cycle | checksum 相同 |
| Pigsty | manifest | L1 not run |
这一表中没有任何一项可被“SQL 返回了三行”替代。
业务场景性能卡
移植时创建一份场景卡:
数字必须来自业务 owner/SLO 和代表性测试,而不是本书替读者决定。
L1 验收增量
在 Pigsty L1 上补:
若某项未运行,报告应写 not-run,而不是 pass。
双周期正式运行
只把新建的专用 evidence 路径传给实验;不要把仓库根或广泛目录当目标。
all 的内部顺序:
正式结果:
第二轮完成后数据库保留可查询的 shop_ch15 最终状态,便于继续第 16 章;
evidence 保留两个独立 cycle,证明 reset 后不是依赖第一次残留才通过。
最终评审句
本章可以得出的最强结论是:
1.3-proposal在固定 PostgreSQL 18.6、本章扩展版本与合成 fixture 上, 两轮可重复;全文、模糊、精确向量、RRF、HNSW 测量路径、权限与精确复位 均有证据。它可以进入真实语料与 Pigsty L1 的下一阶段试点,尚未获得生产 性能与真实模型质量批准。
这比一句“PostgreSQL 可以做混合搜索”更窄,也更有用。
上一节:扩展部署与运行代价 · 返回本章目录 · 下一章:经天纬地:时序、空间与时空查询 · 查看全书目录 · 查看索引中心