Membership Match
Overview
Membership Match 通过测试标量字段值是否属于调用方提供的 Collection 来筛选 Entity。客户端构建紧凑的二进制成员过滤器 blob,并将其作为表达式模板值传入。
它可替代较大的 IN [...] 列表。IN Filter 会将每个成员都序列化到请求中,因此成员 Collection 足够大时,可能会超出 Proxy 的 gRPC 接收限制。使用 membership_match 时,Proxy 会验证 blob,并将其嵌入查询计划,无需重新构建成员列表。

上图展示了固定的职责划分。客户端构建一次 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
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 | 解析时拒绝 | 允许 |
| 典型用途 | 受众和排除 Filter | ACL、合规、租户隔离、按 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 之间自动选择。调用方需要自行选择编码。
选择编码时:
- 确定工作负载能否容忍假阳性。如果不能,绝不要选择 Bloom。
- 如需精确的成员匹配,请对有符号整数字段使用 Roaring。值越稠密或越集中,压缩后的 blob 越小。
- 检查大小限制。均匀随机的 64 位值可能超过 262,144 个高位容器的上限或其他大小限制。如果超出限制,请使用符合请求大小限制的传统 IN 列表。
- 对非整数字段进行精确的成员匹配时,请使用 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
- Java
- Node.js
- Go
- cURL
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"])
byte[] bloomBlob = BloomFilterUtils.buildBloomFilter(userIds, BloomFilterUtils.DEFAULT_FPR);
QueryReq request = QueryReq.builder().collectionName("docs")
.filter("membership_match(user_id, {members}, type=bloom)")
.filterTemplateValues(Collections.singletonMap("members", bloomBlob)).build();
byte[] roaringBlob = RoaringBitmapUtils.buildRoaringBitmap(userIds);
const bloomBlob = buildBloomFilter(userIds);
await client.query({ collection_name: 'docs',
filter: 'membership_match(user_id, {members}, type=bloom)',
exprValues: { members: bloomBlob } });
const roaringBlob = buildRoaringBitmap(integerIds);
bloomBlob, err := milvusclient.NewBloomFilterBlob([]int64{1001, 1002}, fpr)
if err != nil { return err }
search := milvusclient.NewSearchOption("posts", topK, vectors).
WithFilter("membership_match(creator_id, {members}, type=bloom)").
WithTemplateParam("members", bloomBlob)
roaringBlob, err := milvusclient.NewRoaringBitmapBlob([]int64{1001, 1002})
if err != nil { return err }
query := milvusclient.NewQueryOption("docs").
WithFilter("membership_match(user_id, {members}, type=roaring)").
WithTemplateParam("members", roaringBlob)
Delete with Roaring
使用精确的 Roaring blob 删除由外部整数 ID Collection 选中的 Entity。执行 Delete 前,请检查成员 Collection 及 Collection 字段类型。不要使用 Bloom:近似成员判断不能作为安全的 Delete 谓词,并且解析 Delete 表达式时会拒绝 Bloom 格式。
- Python
- Java
- Node.js
- Go
- cURL
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})
byte[] roaringBlob = RoaringBitmapUtils.buildRoaringBitmap(userIds);
DeleteReq request = DeleteReq.builder().collectionName("docs")
.filter("membership_match(id, {members}, type=roaring)")
.filterTemplateValues(Collections.singletonMap("members", roaringBlob)).build();
const roaringBlob = buildRoaringBitmap(integerIds);
await client.delete({ collection_name: 'docs',
filter: 'membership_match(id, {members}, type=roaring)',
exprValues: { members: roaringBlob } });
roaringBlob, err := milvusclient.NewRoaringBitmapBlob([]int64{1001, 1002, 1003})
if err != nil { return err }
_, err = client.Delete(ctx, milvusclient.NewDeleteOption("docs").
WithExpr("membership_match(id, {members}, type=roaring)").
WithTemplateParam("members", roaringBlob))
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.maxMembershipFilterSize | 64 MiB | milvus.yaml 中的 proxy 配置 | 单个 blob 主体 |
proxy.maxMembershipFilterPlanSize | 128 MiB | milvus.yaml 中的 proxy 配置 | 单次请求的总量 |
proxy.grpc.serverMaxRecvSize | 128 MiB | milvus.yaml 中的 proxy 配置 | 整个请求 |
| Roaring 高基数容器上限 | 262,144 | 在代码中固定 | 每个 Filter |
| Roaring 解码内存估算上限 | 64 MiB | 在代码中固定 | 每个 Filter |
| MBF1 格式上限 | 128 MiB | 在代码中固定 | blob 格式容量上限 |
Bloom fpr | 0.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 通过模板值带外传输。