跳到主要内容
版本:v3.0.x

Membership Match

Overview​

Membership Match 通过测试标量字段值是否属于调用方提供的 Collection 来筛选 Entity。客户端构建紧凑的二进制成员过滤器 blob,并将其作为表达式模板值传入。

它可替代较大的 IN [...] 列表。IN Filter 会将每个成员都序列化到请求中,因此成员 Collection 足够大时,可能会超出 Proxy 的 gRPC 接收限制。使用 membership_match 时,Proxy 会验证 blob,并将其嵌入查询计划,无需重新构建成员列表。

Membership Match 工作流:客户端构建可复用的 blob,Proxy 对其进行验证,QueryNode 对其进行求值。

上图展示了固定的职责划分。客户端构建一次 blob,并在多个请求中复用;Proxy 验证其封装格式和大小限制,并将 blob 原样嵌入查询计划;QueryNode 则针对原始数据或 Index 对查询计划进行求值。

该运算符支持两种编码,其正确性保证各不相同:

  • Bloom (type=bloom):近似成员匹配。可能出现误判为匹配,但有效过滤器不会出现漏判。典型用途:大规模受众或排除 Filter,这些场景可以接受偶发的误判。
  • Roaring (type=roaring):对整数值进行精确成员匹配。典型用途:ACL、租户可见性、合规性 Filter,以及按 ID Collection 执行 Delete 操作。

请勿将 Bloom 成员匹配用于授权决策或破坏性 Delete 操作。误判可能会暴露不属于预期 Collection 的 Entity,或将其删除。

Operational checklist​

  • 确认目标字段的类型,并使用相同值域中的值构建 blob。
  • 仅在可接受误报时选择 Bloom filter。
  • 对于 ACL、合规、租户隔离和 Delete 操作,请选择 Roaring。
  • 大型 blob 应一次构建并重复使用;不要在每个请求中重新构建。
  • 按序列化后的字节数估算 blob 大小,而不是按成员数量估算。
  • 在混合版本集群中,启用 membership_match 前应先完成滚动升级,并优先升级 Proxy。
  • 在为破坏性工作流启用该表达式前,先在非生产 Collection 上进行测试。

Expression syntax​

Text
membership_match(<field>, {<template>}, type=bloom)
membership_match(<field>, {<template>}, type=roaring)
membership_match(<field>, {<template>})
not membership_match(<field>, {<template>}, type=bloom)

第二个参数是模板占位符。SDK 以 protobuf bytes 的形式提供占位符值,其中包含客户端构建的 Filter blob。在生产环境的表达式中,应显式指定 type,以便在代码和日志中清楚体现预期的正确性约定。

type 参数是可选的。每个 blob 都带有可自描述的魔数标头:Bloom 使用 MBF1,Roaring 使用 MRB1。省略 type 时,服务器会检测该标头。如果显式指定的 type 与 blob 标头冲突,请求将被拒绝并返回错误;如果标头未知或不完整,则会进入故障关闭状态。

选择编码​

如果可以接受假阳性,请使用 Bloom。Bloom 支持有符号整数字段(INT8/16/32/64)、VARCHAR 字段和 JSON 路径。布隆过滤器并不是精确的准入列表。

fpr (假阳性率)表示布隆过滤器将从未插入的值报告为成员的概率:fpr=0.005 表示每 200 个非成员值中大约有一个会被错误地报告为存在。fpr 越低,生成的 blob 越大。

如果每次匹配都必须精确,请使用 Roaring。Roaring 仅支持有符号整数字段;成员在序列化前会进行符号扩展并转换为 INT64,因此 INT8 类型的 -1 和 INT64 类型的 -1 属于同一个成员。请勿将其用于 UUID 或其他字符串字段。

维度Bloom (type=bloom)Roaring (type=roaring)
精度近似;可能出现假阳性;不会出现假阴性精确;不会出现假阳性或假阴性
字段INT8/16/32/64、VARCHAR、JSON 路径仅支持有符号 INT8/16/32/64
Blob 大小取决于成员数量和 FPR;FPR 为 0.005 时,每个成员约占 11 位;主体大小为 2 的幂取决于值的分布;稠密 Collection 可压缩到小得多的体积;随机 64 位值可能达到 262,144 个高位容器的上限
Delete解析时拒绝允许
典型用途受众和排除 FilterACL、合规、租户隔离、按 ID Collection Delete

JSON 路径会逐行进行严格类型匹配 :JSON 字符串按 VARCHAR 值探测 ,JSON 整数按 INT64 值探测,而 JSON double 绝不会匹配整数成员。这一点与精确的 IN 不同,后者会将 5.0 视为等于 5。如果写入端将整数编码为浮点数,请在写入时对其进行规范化,或使用具有明确类型的标量字段。

无论使用哪种编码,都应使用与目标字段相同的值域构建成员 Collection。请勿使用字符串 "10086" 构建布隆过滤器,然后将其应用于 INT64 字段。Blob header 会记录已插入值的值域;如果 blob 的值域不匹配,Proxy 会返回参数错误,而不是悄无声息地不匹配任何内容。应跨请求复用已构建的 blob,而不是为每次查询重新构建。

对于成员数量不超过几千的 Collection,直接使用 IN 仍然是最简单的选择。Server 不会在 IN、Bloom 和 Roaring 之间自动选择。调用方需要自行选择编码。

选择编码时:

  1. 确定工作负载能否容忍假阳性。如果不能,绝不要选择 Bloom。
  2. 如需精确的成员匹配,请对有符号整数字段使用 Roaring。值越稠密或越集中,压缩后的 blob 越小。
  3. 检查大小限制。均匀随机的 64 位值可能超过 262,144 个高位容器的上限或其他大小限制。如果超出限制,请使用符合请求大小限制的传统 IN 列表。
  4. 对非整数字段进行精确的成员匹配时,请使用 IN。这些字段没有可用的精确成员编码。

RESTful HTTP API 不支持 membership-match。HTTP 表达式模板参数仅支持 bool、number、string 和 array 值,无法使用二进制 bytes 类型传递 blob。请使用基于 gRPC 的 SDK 传递 membership blob。

SDK request patterns​

以下代码片段重点展示如何构建成员列表,并将其作为 Filter 模板参数传入。这些示例假定 Collection 和字段已创建并完成 Load,SDK 客户端已连接,并且查询向量、成员列表及引用的其他变量均已定义。示例省略了 Collection 设置和完整的应用程序框架代码。

Python
from pymilvus import MilvusClient, build_bloom_filter, build_roaring_bitmap

client = MilvusClient(uri="http://localhost:19530")
bloom_blob = build_bloom_filter(["creator-a", "creator-b"], fpr=0.005)
client.search(collection_name="posts", data=[query_vector], anns_field="embedding", limit=10,
filter="membership_match(creator_id, {members}, type=bloom)",
filter_params={"members": bloom_blob})

roaring_blob = build_roaring_bitmap([1001, 1002, 1003])
client.query(collection_name="docs", filter="membership_match(user_id, {members}, type=roaring)",
filter_params={"members": roaring_blob}, output_fields=["user_id", "title"])

Delete with Roaring​

使用精确的 Roaring blob 删除由外部整数 ID Collection 选中的 Entity。执行 Delete 前,请检查成员 Collection 及 Collection 字段类型。不要使用 Bloom:近似成员判断不能作为安全的 Delete 谓词,并且解析 Delete 表达式时会拒绝 Bloom 格式。

Python
roaring_blob = build_roaring_bitmap([1001, 1002, 1003])
client.delete(collection_name="docs", filter="membership_match(id, {members}, type=roaring)",
filter_params={"members": roaring_blob})

Delete 遵循精确的三值匹配语义:

字段值membership_match(...)not membership_match(...)
NULL不匹配(NULL)不匹配(NULL)
在成员 Collection 中匹配不匹配
在成员 Collection 外不匹配匹配

逐行解读上表:使用 membership_match 的 Delete 会删除 Collection 内的 Entity,使用 not membership_match 的 Delete 会删除 Collection 外的 Entity,而字段值为 NULL 的 Entity 在这两种条件下都不会被删除。

  • 无论使用哪种条件,字段值为 NULL 的 Entity 都不会被删除。
  • 空位图不会删除任何 Entity。not membership_match(<field>, {<empty>}, type=roaring) 会删除字段值非 NULL 的所有 Entity,即精确的补集;按设计,这可能是一个很大的 Collection。
  • 对于字段值非 NULL 的 Entity,not membership_match 表示成员 Collection 的精确补集,而不是近似补集。

对于仅加载 Index、没有原始标量数据的 sealed Segment,如果 Index 支持,系统会使用有界反向查找进行求值。如果原始字段数据和满足条件的 Index 均不可用,请求会失败并返回 FieldNotLoaded,而不会返回未经 Filter 的 Entity。

运行限制​

设置默认值是否可配置限制范围
proxy.maxMembershipFilterSize64 MiBmilvus.yaml 中的 proxy 配置单个 blob 主体
proxy.maxMembershipFilterPlanSize128 MiBmilvus.yaml 中的 proxy 配置单次请求的总量
proxy.grpc.serverMaxRecvSize128 MiBmilvus.yaml 中的 proxy 配置整个请求
Roaring 高基数容器上限262,144在代码中固定每个 Filter
Roaring 解码内存估算上限64 MiB在代码中固定每个 Filter
MBF1 格式上限128 MiB在代码中固定blob 格式容量上限
Bloom fpr0.005客户端构建器误判率目标值

proxy.* 设置属于 Milvus 服务端配置,由集群运维人员在 milvus.yaml 的 proxy 部分设置,不能在执行过滤时针对单次请求或单个表达式进行调整。Roaring 准入限制和 MBF1 格式上限在实现中固定,无法在任何位置配置。fpr 是唯一可由客户端控制的设置:构建 blob 时指定,允许的取值范围为 0.0001 到 0.05。

使用默认 fpr 时,blob 主体大小上限可容纳约 4860 万个 INT64 成员,此外还允许包含固定的 32 字节封装头。请求级预算涵盖包含 membership 的 Plan 的序列化总大小,以及 Roaring Filter 解码后大小的估算总量;HybridSearch 子请求和 scorer Filter 共享该预算。Bloom 主体的大小为 2 的幂:成员数量刚超过某个分级边界时,blob 大小会翻倍;稍微提高 fpr 通常可以使其降至下一级大小。当 proxy 因 Bloom blob 过大而拒绝请求时,错误信息会建议一个可满足大小限制的 fpr。

  • Membership 表达式的结果不会跨请求缓存。
  • 混合版本集群采用失败关闭策略:运行旧版代码的 QueryNode 会拒绝包含 membership Plan 节点的 Plan。请先升级 proxy,然后完成滚动升级,再启用 membership 运算符。
  • 服务端日志会隐去 blob 字节。表达式文本只包含模板名称;二进制 payload 通过模板值带外传输。