元数据过滤与索引管理
光靠向量相似度检索有时不够精准。比如一个文档库里只想搜某个分类的文档,一个商品库里只想召回在售商品。这种”先按条件筛再按相似度排”的需求,要靠元数据过滤实现。这篇讲清楚怎么给向量挂元数据、建元数据索引、写过滤条件,以及怎么查看和管理索引配置。
为什么需要元数据过滤
向量相似度解决的是”语义近不近”,但很多业务检索还要叠加结构化条件。举几个常见场景。
| 场景 | 不加过滤 | 加过滤 |
|---|---|---|
| 多租户知识库 | 全库混搜,可能返回别家数据 | 只在本租户内搜 |
| 商品搜索 | 下架商品也命中 | 只召回在售商品 |
| 时间序列 | 历史旧内容挤掉新内容 | 限定近 30 天 |
| 多分类文档 | 跨分类混杂 | 限定某个分类 |
过滤逻辑是先按 filter 条件圈出一批向量,再从中取相似度最高的 topK 条。所以过滤不会改变 topK 的数量,只是缩小了候选范围。
给向量加元数据
元数据是插入或 upsert 时附在每条向量上的键值对。结构很简单
const vectors = [
{
id: "doc-1",
values: [0.12, 0.45, 0.78],
metadata: {
category: "tech",
status: "published",
views: 1024,
featured: true,
},
},
];
await env.MY_INDEX.upsert(vectors);
元数据的值支持三种类型
| 类型 | 示例 | 典型用途 |
|---|---|---|
| string | “tech”、”published” | 分类、状态、租户 id |
| number | 1024、1734242400 | 浏览量、时间戳、价格 |
| boolean | true、false | 是否置顶、是否上架 |
元数据的规则和限制
| 限制项 | 值 |
|---|---|
| 单条向量上限 | 10 KB |
| 键名禁止字符 | 点号、双引号 |
| 键名禁止前缀 | 美元符号开头 |
| 键名长度 | 最长 512 字符 |
设计元数据要克制。元数据是给过滤用的,不要把文档原文塞进去,原文应存在 D1(Cloudflare 的 SQLite 数据库)或 R2(Cloudflare 的对象存储)里,metadata 里只放用于筛选的字段和一个指向原文的 id 或路径。
创建元数据索引
这里有个最容易踩的坑。元数据默认不能直接过滤,要先给某个字段建索引,过滤才能生效。
用 wrangler 命令建元数据索引
npx wrangler vectorize create-metadata-index my-index --property-name='category' --type='string'
执行后输出一个 mutation 标识符,异步构建,稍等片刻生效。
参数说明
| 参数 | 作用 |
|---|---|
| property-name | 要建索引的元数据字段名 |
| type | 字段类型,string、number、boolean |
每个 Vectorize 索引最多建 10 个元数据索引。类型一旦确定改不了,要换只能先删再建。
最关键的时序问题。在元数据索引建好之前插入的向量,那个字段的值不会被索引到。要让旧向量也支持过滤,必须建完索引后重新 upsert 一遍。所以正确顺序是,先建索引、建元数据索引,再批量导入向量,避免返工。
过滤操作符
filter 参数用一种类似 MongoDB 的语法,支持八个操作符
| 操作符 | 含义 | 值类型 |
|---|---|---|
| $eq | 等于 | 单值 |
| $ne | 不等于 | 单值 |
| $in | 属于集合 | 数组 |
| $nin | 不属于集合 | 数组 |
| $lt | 小于 | string 或 number |
| $lte | 小于等于 | string 或 number |
| $gt | 大于 | string 或 number |
| $gte | 大于等于 | string 或 number |
几个要点。$eq 可以省略,直接写字段值就是隐式等于。范围查询里 $lt 和 $lte 只能和 $gt、$gte 组合,不能两个上界或两个下界混用。string 的范围查询按字典序,能实现前缀匹配。filter 的 JSON 表示不能超过 2048 字节。
过滤查询示例
把过滤条件加到 query 的 filter 参数里
export default {
async fetch(request, env) {
const { vector } = await request.json();
const matches = await env.MY_INDEX.query(vector, {
topK: 5,
returnMetadata: "all",
filter: { category: "tech", status: "published" },
});
return Response.json(matches);
},
};
这表示只在 category 等于 tech 且 status 等于 published 的向量里找相似度最高的 5 条。
更复杂的条件示例
// 等于加不等于
filter: { category: "tech", status: { $ne: "draft" } }
// 集合匹配
filter: { category: { $in: ["tech", "science"] } }
// 数值范围,近 30 天
filter: { timestamp: { $gte: 1734242400, $lt: 1736834400 } }
// 字符串前缀,匹配以 net 开头的值
filter: { tag: { $gte: "net", $lt: "neu" } }
设计过滤字段时要考虑基数。基数指一个字段有多少种不同的值。高基数字段(比如用户 id)用 $eq 过滤很高效,能快速定位一小撮向量。但高基数字段做范围查询会扫大量索引项,性能下降。比如毫秒级时间戳做跨长时间的范围查询会退化。一个常见优化是把时间戳按 5 分钟取整存一份专门用于过滤,原始时间戳另存一个字段。
命名空间
除了元数据过滤,Vectorize 还有个 namespace 概念,也能切分检索范围。插入时给向量加 namespace
{
id: "doc-1",
values: [0.12, 0.45, 0.78],
namespace: "tenant-001",
metadata: { category: "tech" },
}
namespace 和 metadata 过滤的对比
| 对比项 | namespace | metadata 过滤 |
|---|---|---|
| 切分方式 | 一条向量只属一个 namespace | 多个字段组合条件 |
| 灵活度 | 单一维度 | 多维度组合 |
| 值类型 | 字符串 | string、number、boolean |
| 是否需建索引 | 不需要 | 需要建元数据索引 |
| 执行顺序 | 先按 namespace 切分 | 再按 metadata 过滤 |
经验法则。如果只有一个维度要硬隔离(比如多租户),用 namespace 更简单。如果要按多个字段组合筛选(分类加状态加时间),用 metadata 过滤更灵活。两者可以叠加,先 namespace 再 metadata。
索引配置管理
Vectorize 提供几个手段查看和管理索引配置。
在 Worker 代码里用 describe 方法拿到索引的基本配置
export default {
async fetch(request, env) {
const info = await env.MY_INDEX.describe();
return Response.json(info);
},
};
返回包含索引名、配置的维度和度量。describe 用来在运行时校验配置,比如代码里硬编码了维度数,可以读出来对一下。
命令行管理索引
| 命令 | 作用 |
|---|---|
| wrangler vectorize info my-index | 查看索引详情和用量 |
| wrangler vectorize list-vectors my-index | 分页列出向量 id |
| wrangler vectorize list-metadata-index my-index | 查看建了哪些元数据索引 |
| wrangler vectorize delete-metadata-index my-index –property-name=’category’ | 删除某个元数据索引 |
list-vectors 支持分页,每页最多 1000 条
npx wrangler vectorize list-vectors my-index --count=100
元数据索引不能随便删。删掉某个字段的元数据索引后,该字段就不能再用于过滤,要重新建并重新 upsert 向量才能恢复。
指标监控
生产环境要盯几个关键指标,确保检索质量和成本可控。
主要监控项
| 指标 | 关注点 | 异常信号 |
|---|---|---|
| 向量总数 | 索引规模增长 | 突增可能是重复插入 |
| 查询延迟 | 用户体验 | p99 超过百毫秒要排查 |
| 查询成功率 | 服务健康 | 错误率上升看维度是否错配 |
| 元数据索引命中 | 过滤效率 | 命中低说明字段基数不合理 |
| 维度配置 | 一致性 | 和嵌入模型输出不一致会报错 |
Cloudflare 仪表盘的 Vectorize 页面能看到索引的向量数和用量。命令行用 wrangler vectorize info 也能查。延迟和错误率建议接到 Cloudflare 的分析或外部监控里持续观察。
成本方面,Vectorize 按存储的向量维度数和查询次数计费。控制好 topK 和 returnValues 的使用能省不少,returnValues 开着会传回完整向量,维度高时流量和耗时都上去了,非必要不开。
小结
元数据过滤让向量检索从”纯语义相似”升级到”带业务条件的精准检索”。关键是先建元数据索引再插数据,按字段类型选对操作符,高基数字段慎做范围查询。配合 namespace 能实现多租户硬隔离,配合 describe 和 wrangler info 能持续盯住索引健康。下一篇把这些组合起来,加上 Workers AI 的嵌入和生成能力,搭一个能问答的 RAG 系统。
上一篇 向量索引与相似度搜索
下一篇 实现 RAG 检索