登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

向量数据库按租户过滤时怎样避免召回范围串租户

来源:17golang原创

时间:2026-09-07 08:22:00 108浏览 收藏

向量相似度只回答“内容像不像”,不会回答“这个结果是否属于当前租户”。多租户 RAG 或知识库如果把所有数据放进同一个 collection,却只依赖 query vector 排序,召回范围就可能跨过租户边界。

可靠做法是把 tenant_id 当成服务端鉴权后的必选查询条件:写入每条向量记录,按精确字符串建立过滤索引,并把它与向量查询用 AND 关系合并。租户数量、隔离要求或跨租户分析再决定是否使用 namespace、partition key 或独立 collection。
要点速览
  • tenant_id 必须来自已验证的请求上下文,不能信任客户端参数。
  • 共享集合方案要同时保证“入库有字段”和“查询必过滤”,缺一都会留下串租户窗口。
  • 过滤后的候选范围、topK、审计日志和跨租户回归用例要一起纳入上线检查。

先把租户身份变成不可绕过的查询条件

最容易被忽略的地方不是向量库,而是查询服务的边界。请求体里带来的 tenant_id 只能作为业务参数,不能直接决定权限。服务应先从已验证的 API key、JWT 或会话映射出当前租户,再把这个值传给检索函数;没有租户身份时直接拒绝查询。

入库也要遵循同一个原则。每条记录至少保留 tenant_iddoc_id 和可用于展示的标题。tenant_id 使用稳定的内部标识,避免把可变的公司名称或用户昵称当作隔离键。

向量数据库多租户检索中鉴权上下文、租户查询条件、keyword 索引与向量集合的边界关系
图1:请求身份经过鉴权上下文进入租户查询条件,再与 keyword 索引和向量集合形成静态隔离边界。

共享集合的过滤写法要和向量查询绑定

下面用 Qdrant Python 客户端表达一条共享集合查询。Qdrant 把附加字段称为 payload,精确字符串适合使用 keyword 类型;过滤条件放在查询对象中,表示只在符合租户条件的记录里做相似度检索。

from qdrant_client import QdrantClient, models

def search_for_tenant(client: QdrantClient, collection: str,
                      verified_tenant_id: str, query_vector: list[float]):
    # tenant_id 必须来自已验证的鉴权上下文,不能取请求体原值
    if not verified_tenant_id:
        raise ValueError("missing tenant identity")

    return client.query_points(
        collection_name=collection,
        query=query_vector,
        query_filter=models.Filter(
            must=[
                models.FieldCondition(
                    key="tenant_id",
                    match=models.MatchValue(value=verified_tenant_id),
                )
            ]
        ),
        limit=5,
        with_payload=["tenant_id", "doc_id", "title"],
    ).points

这个函数的重点不是 SDK 方法名,而是调用契约:检索函数只接收已经验证的租户身份,过滤条件不能由上层可选地拼接。若业务确实需要跨租户分析,应单独暴露受权限控制的接口,明确传入允许的租户集合,而不是复用单租户搜索接口。

生产数据还应为 tenant_id 建立精确过滤索引,并在写入流程中拒绝缺字段记录。索引解决的是过滤效率,不能替代权限判断;权限服务解决的是“谁能查”,过滤条件解决的是“库里只返回哪一组”。

为什么只在召回后过滤仍然不够

有些实现先从全库取 topK,再在应用层筛掉其他租户。这样既扩大了敏感数据进入应用进程的范围,也会让过滤后的结果数量不可控:全库 topK 中如果恰好没有当前租户文档,应用层只能得到空结果,不能把它当成完整的租户内 topK。

正确边界是让向量库收到带 tenant 条件的查询,随后再在返回对象中复查 tenant_id 与请求上下文是否一致。这个复查是纵深防御,不应成为唯一隔离措施。测试时至少准备两个租户的相同问题文档,并验证 A 的向量查询不会返回 B 的 doc_id

方案适合场景主要代价
共享集合 + payload filter大量小租户、结构相同每条记录和每次查询都必须守住字段约束
namespace / partition key租户边界稳定,想缩小搜索范围依赖具体向量库语义,跨租户查询要单独设计
独立 collection少量大租户或更强物理隔离集合数量、索引、迁移和运维成本上升

按隔离强度和租户规模选择存储边界

Qdrant 的多租户文档把共享 collection 的 payload 分区、用户自定义分片和组合方式列为不同取舍;Milvus 也提供 database、collection、partition 和 partition key 等层级。它们的名字不能直接互换,落地前要确认目标引擎的过滤、分区、RBAC 和跨租户查询语义。

如果租户多而数据量相近,共享集合配合精确索引通常更容易运营;如果单个大租户需要独立资源或要降低 noisy neighbor 风险,可以提升到专用 shard、partition 或独立 collection。合规要求、删除范围、备份恢复和租户级限流往往比一次查询的延迟更能决定方案。

无论选择哪一层,应用接口都应保留统一的“当前租户”概念。namespace 或 partition 只是存储路由,不应让前端自由填写并直接切换;路由值仍需由服务端根据授权结果生成。

共享向量集合、metadata filter、partition key 与独立 collection 的多租户隔离策略关系
图2:共享集合过滤、分区键和独立集合分别对应不同的逻辑与物理边界,选择时同时看隔离和运营成本。

上线前用四组用例抓住串租户窗口

第一组是正常隔离:A、B 各写入相同语义但不同 doc_id 的文档,A 查询时断言所有结果的 tenant_id 都是 A。第二组是越权输入:把请求体中的 tenant_id 改成 B,结果仍应由鉴权上下文决定,而不是切到 B。

第三组检查脏数据:缺少 tenant_id、空字符串、大小写不一致或错误类型的记录不能静默进入正式集合。第四组检查接口契约:没有租户身份、过滤条件被移除、跨租户列表超出授权范围时,都应得到明确错误并写入审计日志。

监控上不要只看总召回量,还要记录租户过滤后的候选量、最终命中文档的租户一致性、空结果比例和按租户的延迟。过滤后候选长期过少时,优先检查数据写入与 embedding 分片,而不是先把 topK 无限调大。

常见问题

只给向量加 tenant_id,不建立索引可以吗?

逻辑上可以表达过滤,但大规模查询可能承担更高过滤成本;具体是否强制索引取决于引擎的 strict mode 和部署配置。把索引当性能基础设施,同时保留鉴权和回归测试。

namespace 能否替代权限校验?

不能。namespace、partition 或 collection 是存储和查询范围,不能证明调用者有权访问该范围。范围标识必须由服务端根据身份生成。

为什么过滤后结果数量经常不足?

可能是当前租户有效数据少、字段缺失、过滤值类型不一致,或全库 topK 后置过滤造成的。先检查入库字段和查询边界,再考虑调整租户内 topK。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>