第 4 章 Schema 设计:把知识库建对


文档摘要

第 4 章 Schema 设计:把知识库建对 Schema 是知识库的"结构合同"——一旦建好,所有写入和检索都要围绕它展开。RAG 系统的很多问题,根子在 Schema 设计错了:该建索引的没建、不该存的存了一堆、维度选错导致后期推倒重来。这一章给你一套可直接套用的 RAG Schema 范式。 4.1 Schema 的三个组成部分 回顾第 2 章, 由三部分构成。这一章我们把每一部分在 RAG 场景下"该怎么填"讲透: 组成 | 作用 | RAG 里的典型内容 | Collection 标识符 | 如 、 | 标量字段列表 | 原文 text、出处 source、时间、分类标签…… | 向量字段列表 | 稠密向量(语义)、稀疏向量(关键词) 💡 Zvec 的 Schema

第 4 章 Schema 设计:把知识库建对

Schema 是知识库的"结构合同"——一旦建好,所有写入和检索都要围绕它展开。RAG 系统的很多问题,根子在 Schema 设计错了:该建索引的没建、不该存的存了一堆、维度选错导致后期推倒重来。这一章给你一套可直接套用的 RAG Schema 范式。

4.1 Schema 的三个组成部分

回顾第 2 章,CollectionSchema 由三部分构成。这一章我们把每一部分在 RAG 场景下"该怎么填"讲透:

组成 作用 RAG 里的典型内容
name Collection 标识符 product_manual_kbsupport_faq_kb
fields 标量字段列表 原文 text、出处 source、时间、分类标签……
vectors 向量字段列表 稠密向量(语义)、稀疏向量(关键词)

💡 Zvec 的 Schema 是动态的——你可以随时加列、改索引,不用重建 Collection(详见 4.5 节 Schema 演进)。但这不代表可以乱设计:核心向量字段的维度一旦定下,改起来代价极大(见第 3 章)。所以 Schema 设计的核心,是"先把向量维度和要过滤的字段想清楚"。

4.2 标量字段:哪些该建索引,哪些只存

这是 Schema 设计里性价比最高的决策点。原则很朴素:只给"经常用来过滤/检索"的字段建索引,其余只存

要不要给这个标量字段建倒排索引? │ ├─ 会出现在 query 的 filter 里吗? ──► 会 ──► ✅ 建倒排索引 │ (如 source / publish_year / category) │ ├─ 会被全文检索吗? ──► 会 ──► ✅ 建全文索引 │ (如正文 text) │ └─ 只用来展示/喂给 LLM? ──► 是 ──► ❌ 只存,不建索引 (如纯展示用的 url、摘要)

⚠️ 索引不是越多越好。每个索引都有代价:占额外存储、每次写入都要维护(写放大)。给一个从不参与过滤的字段建索引,纯粹是浪费。第 2 章那张"三类索引分工表"在这里要反复对照。

倒排索引还有两个可选项,专为特定查询加速:

选项 作用 何时开启
enable_range_optimization=True 加速范围查询(price > 100year >= 2024 数值字段会被范围过滤时
enable_extended_wildcard=True 支持中缀/后缀通配(name LIKE '%abc' 有复杂字符串模式匹配需求时

4.3 向量字段:RAG 的主力军

向量字段的 Schema 由四个属性决定,每一个都值得深思:

属性 决策要点 RAG 建议
name 见名知意 dense(稠密语义)、sparse(稀疏关键词)、image_vec(多模态)
data_type 精度 vs 存储 起步用 VECTOR_FP32;要省内存可 VECTOR_FP16(见第 5 章量化)
dimension 必须等于模型输出维度,定下难改 768/1024/1536,由 Embedding 模型决定
index_param 索引类型 + 度量 文本 RAG 默认 HnswIndexParam(metric_type=COSINE)(第 5 章展开)

⚠️ 稠密向量维度必填,稀疏向量不填维度。稀疏向量(SPARSE_VECTOR_FP32)天然没有固定长度,只需存非零项,所以 VectorSchema不要给稀疏向量写 dimension。这是个容易写错的细节。

一个 RAG 知识库的"黄金 Schema"

下面这套 Schema 覆盖了生产级 RAG 的大多数需求:语义检索(稠密)+ 关键词检索(稀疏+全文)+ 元数据过滤。可以直接作为你项目的起点:

import zvec schema = zvec.CollectionSchema( name="rag_kb", fields=[ # —— 原文 & 出处 —— # 正文:要喂给 LLM,并存一份供全文检索(BM25 关键词召回) zvec.FieldSchema( name="text", data_type=zvec.DataType.STRING, index_param=zvec.FtsIndexParam(tokenizer_name="jieba"), # 中文用 Jieba ), # 出处:常用于"只在某文档里找",建倒排 zvec.FieldSchema( name="source", data_type=zvec.DataType.STRING, index_param=zvec.InvertIndexParam(), ), # —— 过滤字段 —— # 时间:范围过滤("只查近一年"),建倒排 + 范围优化 zvec.FieldSchema( name="publish_year", data_type=zvec.DataType.INT32, index_param=zvec.InvertIndexParam(enable_range_optimization=True), ), # 多标签:数组成员查询(CONTAIN_ANY / CONTAIN_ALL),建倒排 zvec.FieldSchema( name="category", data_type=zvec.DataType.ARRAY_STRING, index_param=zvec.InvertIndexParam(), ), # —— 只存不索引 —— # 摘要:只用于展示,从不参与过滤,省索引开销 zvec.FieldSchema(name="summary", data_type=zvec.DataType.STRING), ], vectors=[ # 语义向量:768 维稠密,HNSW + COSINE(文本 RAG 默认配置) zvec.VectorSchema( name="dense", data_type=zvec.DataType.VECTOR_FP32, dimension=768, index_param=zvec.HnswIndexParam(metric_type=zvec.MetricType.COSINE), ), # 关键词向量:稀疏向量,IP 度量(加权词项匹配) # 若用 bge-m3 等模型,可同时拿到 dense 和 sparse,一次 Embedding 双倍召回 zvec.VectorSchema( name="sparse", data_type=zvec.DataType.SPARSE_VECTOR_FP32, index_param=zvec.HnswIndexParam(metric_type=zvec.MetricType.IP), ), ], ) kb = zvec.create_and_open(path="./rag_kb", schema=schema)

💡 为什么同时要 FTS 和稀疏向量? 两者都做"关键词召回",但有分工:FTS 直接对原文建索引,零额外 Embedding 成本,适合简单关键词;稀疏向量由模型(如 bge-m3 稀疏头、SPLADE)生成,能学到词项权重甚至语义扩展,召回更智能。轻量场景用 FTS 就够;追求召回上限时用稀疏向量(或两者配合)。第 6、7 章会演示它们的检索写法。

4.4 Collection 选项:运行时行为

创建 Collection 时还能传 option,控制运行时行为。两个关键开关:

选项 作用 何时用
read_only=True 只读模式,禁止任何写入 多进程共享同一个 Collection 时必用(第 8 章)
enable_mmap=True(默认) 内存映射 I/O,用少量内存换更快访问 数据量大于内存时尤其有用
collection = zvec.create_and_open( path="./rag_kb", schema=schema, option=zvec.CollectionOption(read_only=False, enable_mmap=True), )

4.5 Schema 演进:在线改结构,不重建

这是 Zvec 区别于很多向量库的杀手锏——Collection 建好后,还能在线改 Schema,不用停机、不用重新导入数据、不用重新建索引。它通过"数据定义语言 DDL"实现,分两类:

DDL 类别 管什么 支持的操作
Column DDL 存什么数据(字段) 加列 add_column、删列 drop_column、改列 alter_column
Index DDL 怎么搜索(索引) 建索引 create_index、删索引 drop_index

💡 RAG 的实际价值:知识库上线后,产品同学突然说"加个字段标记是否内部文档,好做权限过滤"。在不能改 Schema 的库里,你得重建整个库;在 Zvec 里,一行 add_column 就搞定了。

# Column DDL:给已有 Collection 加一个数值字段,并用表达式给历史数据填默认值 collection.add_column( field_schema=zvec.FieldSchema(name="importance", data_type=zvec.DataType.INT32), expression="3", # 历史数据默认重要性 = 3 ) # Index DDL:后来发现某标量字段常用来过滤,补建倒排索引(不用重新导入数据!) collection.create_index( field_name="source", index_param=zvec.InvertIndexParam(), ) # Index DDL:把某个向量字段的 HNSW 换成 Flat(调试/小数据场景) collection.create_index( field_name="dense", index_param=zvec.FlatIndexParam(metric_type=zvec.MetricType.COSINE), )

⚠️ 演进有边界

  • 向量字段目前不支持在线添加/删除(这是已规划但尚未支持的特性)——所以向量字段要在建库时一次想清楚。
  • 向量字段的索引必须始终存在(不允许 drop_index 删向量索引),但可以用 create_index 替换索引类型。
  • add_column 目前主要支持数值型标量字段,历史数据通过 expression 填默认值。

4.6 检视与统计:随时看清库的状态

设计完、写入后,随时用这几个属性查看 Collection 的真实状态,这是排查问题的第一步:

属性 看什么 排查场景
collection.schema 当前结构(字段/向量/索引) 确认 Schema 是否按预期演进
collection.stats 运行时指标(Doc 数量、索引构建进度) 判断是否该 optimize(第 8 章)
collection.option 运行时配置(只读/mmap) 确认多进程共享设置
collection.path 磁盘位置 确认数据落在哪、可否迁移
print(collection.schema) # 看结构 print(collection.stats) # 看 Doc 数和索引进度 —— optimize 节奏就靠它

4.7 动手实验

  1. 索引代价感知:建两个 Collection,一个给所有标量字段建索引、一个只给必要字段建索引,写入 1000 条相同数据,对比磁盘占用差异。
  2. 在线演进:按 4.3 建库并写入数据,然后用 add_column 加一个 importance 字段,检索时加 filter="importance >= 3" 验证新字段立刻可用。
  3. 补建索引:先建一个 source 字段不建索引的库,写入数据后用 filter="source = 'x.md'" 感受慢;再 create_index 补建,对比过滤速度。

本章小结

  • Schema 三部分:name / fields(标量)/ vectors(向量);核心是决定每个字段建什么索引
  • 标量字段索引原则:只给经常过滤/检索的字段建,倒排管过滤、全文管关键词;范围/通配有专用优化开关。
  • 向量字段:维度必须等于模型输出且定下难改,文本 RAG 默认稠密 HNSW+COSINE、稀疏 IP
  • 给了一套可直接套用的 RAG 黄金 Schema(语义+关键词+过滤三合一)。
  • Zvec 支持在线 Schema 演进(Column/Index DDL),但向量字段不能在线增删,要一次想清楚。

索引类型怎么选、参数怎么调,是下一章的主题。详见第 5 章


发布者: 作者: 转发
评论区 (0)
U