过滤与多租户
本节目标
你将把 tenant、ACL、发布状态和业务条件变成检索计划的一部分;理解 filter selectivity 与 ANN 的交互;比较共享 collection、分区/自定义 shard、collection-per-tenant,并写出 fail-closed 越权测试。
为什么“取 top-k 后再删”是错的
假设全局最相似 10 条都属于其他租户。先全局 ANN top-10,再在应用层删除,授权用户可能只剩 0 条;把 k调到 100 也不能证明找到其租户真正 top-10。
更严重的是,越权记录可能已经:
- 参与向量评分;
- 出现在数据库响应、trace 或 debug log;
- 影响缓存和 rerank;
- 被送入模型后再从 UI 隐藏。
授权 filter 应在候选评分/返回前 fail-closed 生效。最终应用层复核是纵深防御,不是补救数据库层泄露。
从可信身份构造 filter
请求流程:
- API 验证用户/服务身份;
- 服务端解析 tenant、用户、groups、roles 和策略版本;
- 授权模块生成不可放宽的 mandatory predicate;
- 用户业务 filter 只能与 mandatory predicate 做 AND;
- 查询数据库;
- parent 展开、缓存与引用再次复核。
不要直接接受客户端:
{"tenant_id": "someone-else", "acl": ["admin"]}该 JSON 是不可信客户端输入的反例,所以保持它可被解析:tenant_id 和 acl 都不能由客户端声明后直接写入 mandatory filter;服务端必须从已经认证的主体与授权策略生成这两个约束。
客户端可以选择“只看某语言”,不能选择自己属于哪个 tenant 或提升 ACL。
明确定义 ACL 语义
最简单 group OR:
生产策略还可能有:
- user-specific grant;
- all-of groups;
- explicit deny 优先;
- document/parent 继承;
- time-bound access;
- classification clearance;
- ABAC 属性条件。
必须写出布尔语义和优先级,不要让不同服务分别猜。空 acl、缺 tenant 或身份解析失败应拒绝发布/查询,而不是公开可见。
若用户 group 变化,检索读取当前授权上下文。Cache key 至少包含 tenant、授权策略版本或可失效的 membership fingerprint;仅用 query text 作 key 会跨权限复用结果。
业务 filter
常见字段:
status = published;- language/document type;
- valid_from / valid_to;
- source/project;
- region/data residency;
- embedding/source revision;
- tags/department;
- numeric/date range。
Mandatory security filter 与用户业务 filter 分开记录。日志可以输出字段名、选择率和策略 ID,不默认输出敏感值。
Selectivity 与 cardinality
- Selectivity:filter 后保留候选比例;1% 比 80% 更严格。
- Cardinality:字段有多少不同值;tenant ID 通常高基数,status 通常低基数。
这些会影响:
- scalar/payload index 类型和内存;
- planner 是否先 filter 或边搜索;
- ANN traversal 能否找到足够候选;
- shard routing 和 fan-out;
- tail latency。
不能用“高/低基数固定阈值”跨产品决策。采集实际分布,按当前产品索引机制压测。
Filter index
为高频 predicate 建标量/payload index,可能显著减少扫描;代价是:
- 写入与更新更多索引;
- 磁盘/RAM;
- schema migration;
- 低频字段浪费;
- 多字段组合不一定命中同一计划。
记录每个 indexed field 的类型、基数、查询频率、选择率和删除/更新成本。String、keyword、full-text、numeric、datetime、geo 的语义不同;大小写和分词也要用真实数据测试。
ANN 与 filter 的执行策略
产品可能:
- pre-filter 后在子集 exact/ANN;
- traversal 中检查 filter 并继续扩展;
- 先 ANN 再 post-filter;
- 过滤不足时 iterative scan/oversampling;
- 按 partition/shard 路由后局部搜索。
每种对低选择率、延迟和返回数量的影响不同。pgvector 当前 approximate index + WHERE 的执行受 PostgreSQL planner/index scan 影响,并提供 iterative scans 相关能力;Qdrant 有自己的 filter/index 策略。只依据“支持 filter”四个字无法判断质量和性能。
测试应同时检查:
- 返回结果全部满足 predicate;
- 返回数量;
- 相对 filter-exact top-k 的 ANN recall;
- P95/P99;
- 扫描/候选统计(产品可见时);
- filter 组合与缺字段行为。
多租户的三类布局
共享 collection + tenant payload
优点:
- collection 数少;
- 统一索引和备份;
- 小租户共享资源;
- 跨授权范围聚合较直接。
风险:
- 每个查询都必须带正确 tenant filter;
- 大租户与小租户资源争用;
- filter-aware ANN 与高基数字段压力;
- 误配置影响面大。
Collection-per-tenant
优点:
- 硬逻辑隔离;
- 生命周期、备份、SLO 可独立;
- 查询天然不跨 collection。
风险:
- 大量小 collection 的控制面、内存、快照和升级成本;
- 跨 tenant 管理复杂;
- 产品可能有 collection 配额/开销;
- 容量碎片。
Partition / custom shard / routing
按 tenant 或 tenant group 路由到特定 partition/shard,可减少 fan-out 并提供资源隔离。风险是热点/大租户倾斜、重分片复杂和产品绑定。
现实常用 hybrid:
- 大型/高风险租户独立;
- 长尾小租户共享;
- tenant group 作为 shard key;
- hard residency domain 分集群。
Qdrant 当前提供 multitenancy/custom sharding 指南;能力、限制和推荐会随版本变化,不应抄成其他产品方案。
相同内容不同权限
同一段公开模板可能出现在两个 tenant。如果只保留一条“公共 vector”,需要非常明确的授权和 source mapping;否则:
- 一个 tenant 删除可能影响另一 tenant;
- payload 只能表达一套 ACL;
- source revision 不同却误去重;
- cache/result 泄露。
安全基线是 point identity 包含 tenant/security domain,或使用经过治理的共享内容域并显式授权。不要只按 content hash 跨租户去重。
删除与权限撤销
权限撤销比正文更新更紧急:
- Canonical ACL revision 更新;
- 向量库 payload/filter 更新或 point 撤下;
- 读取路径 fail-closed;
- 查询/parent/rerank cache 失效;
- 旧 embedding spaces 同步;
- 备份保留和恢复后的撤销重放;
- 审计对账。
数据库返回更新成功不代表所有 replica/cache/旧空间立刻不可见;第 4~6 课会处理生命周期、一致性和恢复。
越权测试矩阵
| 场景 | Fixture | 期望 |
|---|---|---|
| 其他 tenant 更相似 | unauthorized vector = query | 永不返回、永不进入可见分数 |
| 同 tenant 错 group | ACL=platform,用户=employees | 不返回 |
| 空 groups | 身份解析失败/空集合 | 返回空或拒绝请求 |
| 缺 ACL | 历史坏记录 | 不发布/不返回 |
| Draft | status=draft | 不返回 |
| Cache | 相同 query、不同 groups | 不复用授权更宽结果 |
| Parent | child 可见、parent 含不可见段 | 拒绝/裁剪并保留来源 |
| Group revoke | 查询前后 membership 变化 | 撤销后不可见 |
测试最终 UI、数据库响应、trace 和下游 context,不只检查一层。
常见错误与排查
- 过滤后结果总不足:查执行策略、选择率、oversampling/iterative scan 和 filter exact baseline。
- 偶发跨租户:mandatory filter 来自客户端、缓存缺授权上下文或 parent 展开未复核。
- 新增 filter 后 P99 激增:缺 scalar index、组合计划改变或 fan-out。
- 给所有字段建索引:写入/内存增加;回到查询频率和选择率。
- 按 content hash 跨租户去重:权限/删除语义丢失。
- 空 ACL 当 public:改为 ingestion 和 query 双重 fail-closed。
- 共享 collection 热点:分析 tenant 规模与 shard routing,不只扩大全局资源。
练习
- 写出
tenant AND published AND (ACL intersects groups) AND valid-time的布尔 predicate。 - 设计 other-tenant vector 完全等于 query 的越权测试,并列出所有需要检查的输出层。
- 为 1%、10%、80% selectivity 分别建立 exact/ANN 对照。
- 比较 10 个大租户 + 100,000 个小租户的三种布局,给出 hybrid 方案。
- 设计 group revoke 的 cache key/失效策略。
- 判断 title、tenant、ACL、status、content hash、timestamp 哪些应建 filter index,并说明证据。
掌握检查
- Tenant/ACL 从可信身份派生,客户端不能放宽。
- Mandatory security filter 在评分前执行,应用层再复核。
- ACL 缺失、空 groups 和身份失败都 fail-closed。
- 我能解释 selectivity/cardinality 与 filter index/ANN 的关系。
- Filter benchmark 用 filter-exact ground truth。
- 多租户布局按租户分布、隔离、备份和运维选择。
- Cache、parent、旧空间与恢复流程不会绕过撤销。
小结与下一步
过滤让候选“可看且符合条件”;数据仍会持续新增、更新和删除。下一步建立可重放生命周期:[[Knowledge/AI Agent Engineer/docs-CN/向量数据库/04-写入更新删除与版本|写入、更新、删除与版本]]。
参考资料
来源获取日期:2026-07-22。返回 [[Knowledge/AI Agent Engineer/docs-CN/向量数据库/00-目录|向量数据库目录]]。