过滤与多租户

本节目标

你将把 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

请求流程:

  1. API 验证用户/服务身份;
  2. 服务端解析 tenant、用户、groups、roles 和策略版本;
  3. 授权模块生成不可放宽的 mandatory predicate;
  4. 用户业务 filter 只能与 mandatory predicate 做 AND;
  5. 查询数据库;
  6. 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 跨租户去重。

删除与权限撤销

权限撤销比正文更新更紧急:

  1. Canonical ACL revision 更新;
  2. 向量库 payload/filter 更新或 point 撤下;
  3. 读取路径 fail-closed;
  4. 查询/parent/rerank cache 失效;
  5. 旧 embedding spaces 同步;
  6. 备份保留和恢复后的撤销重放;
  7. 审计对账。

数据库返回更新成功不代表所有 replica/cache/旧空间立刻不可见;第 4~6 课会处理生命周期、一致性和恢复。

越权测试矩阵

场景Fixture期望
其他 tenant 更相似unauthorized vector = query永不返回、永不进入可见分数
同 tenant 错 groupACL=platform,用户=employees不返回
空 groups身份解析失败/空集合返回空或拒绝请求
缺 ACL历史坏记录不发布/不返回
Draftstatus=draft不返回
Cache相同 query、不同 groups不复用授权更宽结果
Parentchild 可见、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,不只扩大全局资源。

练习

  1. 写出 tenant AND published AND (ACL intersects groups) AND valid-time 的布尔 predicate。
  2. 设计 other-tenant vector 完全等于 query 的越权测试,并列出所有需要检查的输出层。
  3. 为 1%、10%、80% selectivity 分别建立 exact/ANN 对照。
  4. 比较 10 个大租户 + 100,000 个小租户的三种布局,给出 hybrid 方案。
  5. 设计 group revoke 的 cache key/失效策略。
  6. 判断 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-目录|向量数据库目录]]。