Поиск партнеров среди членов клубаCompatible with Milvus v3.0.2+
Обзор
Фильтр членства фильтрует сущности, проверяя, принадлежит ли значение скалярного поля набору, предоставленному вызывающим. Клиент создает компактный двоичный блок фильтра членства и передает его в качестве значения шаблона выражения.
Он заменяет большие списки « IN [...] ». Фильтр « IN » сериализует каждого элемента в запрос, поэтому достаточно большие наборы элементов могут превысить лимит приема прокси gRPC. С помощью « membership_match » прокси проверяет блоб и встраивает его в план запроса без построения списка элементов заново.
Рабочий процесс сопоставления членства: клиент создаёт многократно используемый BLOB, прокси проверяет его, а QueryNodes оценивают его.
На диаграмме показано фиксированное распределение обязанностей. Клиент создает BLOB один раз и повторно использует его в разных запросах; прокси проверяет соответствие ограничениям по конверту и размеру и встраивает BLOB в план запроса без изменений; а QueryNodes оценивают план на основе необработанных данных или индекса.
Оператор имеет два вида кодирования с разными гарантиями корректности:
- Bloom (
type=bloom): приблизительная принадлежность. Возможны ложные срабатывания; ложные пропуски не ожидаются при использовании корректного фильтра. Типичное применение: фильтры с большим объёмом аудитории или фильтры исключения, где допустимы единичные ложные срабатывания. - Roaring (
type=roaring): точное членство для целочисленных значений. Типичное применение: списки контроля доступа (ACL), видимость арендаторов, фильтры соответствия требованиям и операции удаления по набору идентификаторов.
Не используйте принадлежность по алгоритму Блума для принятия решений об авторизации или разрушительного удаления. Ложное срабатывание может привести к раскрытию объекта или удалению объекта, который не входит в предполагаемый набор.
Контрольный список для эксплуатации
- Убедитесь в типе целевого поля и постройте BLOB на основе того же домена значений.
- Выбирайте алгоритм Блума только в тех случаях, когда допустимы ложные срабатывания.
- Выбирайте алгоритм Roaring для операций с ACL, обеспечения соответствия требованиям, изоляции арендаторов и удаления.
- Создавайте большие BLOB-объекты один раз и повторно используйте их; не создавайте их заново в каждом запросе.
- Рассчитывайте размер BLOB в байтах сериализованных данных, а не по количеству элементов.
- В кластере со смешанными версиями завершите постепенное обновление, сначала прокси, прежде чем включать функцию «
membership_match». - Перед включением разрушающего рабочего процесса протестируйте выражение на непроизводственной коллекции.
Синтаксис выражения
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, содержащего BLOB-объект фильтра, сгенерированный клиентом. В производственных выражениях явно указывайте type, чтобы договор о корректности был виден в коде и журналах.
Аргумент type является необязательным. Каждый блоб содержит самоописывающий магический заголовок: MBF1 для алгоритма Блума и MRB1 для алгоритма Roaring; сервер анализирует этот заголовок, если type опущен. Явно указанный type, конфликтующий с заголовком блоба, отклоняется как ошибка запроса, а неизвестные или усеченные заголовки приводят к состоянию «fail-closed».
Выбор кодировки
Используйте фильтр Блума, когда допустимы ложные срабатывания. Фильтр Блума поддерживает поля со знаковыми целыми числами (INT8/16/32/64), поля VARCHAR и пути JSON. Фильтр Блума не является точным списком разрешённых элементов.
fpr (коэффициент ложных срабатываний, false-positive rate) — это вероятность того, что фильтр Блума сообщит о значении как о члене, хотя это значение никогда не вставлялось: fpr=0.005 означает, что примерно одно значение из 200, не являющееся членом, ошибочно сообщается как присутствующее. Более низкий fpr приводит к увеличению размера блоба.
Используйте алгоритм 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; около 11 бит на элемент при FPR 0,005; тело в виде степени двойки | Распределение значений; плотные множества сжимаются до гораздо меньших размеров; случайные 64-битные значения достигают верхнего предела контейнера в 262 144 |
| Удаление | Отклоняется на этапе синтаксического анализа | Разрешено |
| Типичное использование | Фильтры аудитории и исключений | ACL, соответствие требованиям, изоляция арендаторов, удаление по набору идентификаторов |
ПутиJSON строго типизированы построчно: строка JSON проверяется как значение VARCHAR, целое число JSON — как значение INT64, а число типа double в JSON никогда не сопоставляется с целочисленным элементом, в отличие от точного IN, где 5.0 равно 5. Если ваши модули записи кодируют целые числа с плавающей запятой, нормализуйте их при записи или используйте типизированное скалярное поле.
Для любого из этих видов кодирования создавайте набор элементов из той же области значений, что и целевое поле. Не создавайте фильтр Блума на основе строкового "10086" и не применяйте его к полю INT64. Заголовок BLOB фиксирует домены вставленных значений, и BLOB с неверным доменом отклоняется прокси с ошибкой параметра, а не проходит сопоставление без ошибок, не найдя ни одного совпадения. Используйте уже созданный BLOB для нескольких запросов, а не создавайте его заново для каждого запроса.
Для наборов, состоящих из нескольких тысяч элементов или меньше, простый IN остаётся самым простым вариантом. Сервер не выбирает между IN, Bloom и Roaring. Выбор кодировки лежит на вызывающей стороне.
Чтобы выбрать кодировку:
- Определите, допускает ли рабочая нагрузка ложные срабатывания. Если нет, Bloom никогда не является вариантом.
- Для точной принадлежности используйте Roaring с полем знаковых целых чисел. Плотные или сгруппированные значения сжимаются до самых маленьких блобов.
- Проверьте ограничения по размеру. Равномерно случайные 64-битные значения могут превысить ограничение на количество контейнеров (262 144) или ограничения по размеру. Если это происходит, используйте традиционный список IN, который укладывается в ограничения запроса.
- Для точного определения принадлежности по нецелочисленным полям используйте IN. Для этих полей не существует кодировки точного определения принадлежности.
Сопоставление принадлежности недоступно через RESTful HTTP API. Параметры шаблонов выражений HTTP принимают только значения типа bool, number, string и array; для BLOB отсутствует двоичный тип bytes. Для передачи BLOB-объектов принадлежности используйте SDK на основе gRPC.
Шаблоны запросов SDK
В приведенных ниже фрагментах кода основное внимание уделяется созданию BLOB-объектов членства и их передаче в качестве параметров шаблонов фильтров. В них предполагается, что коллекции и поля уже существуют и загружены, клиенты SDK подключены, а векторы запросов, списки членов и другие переменные, на которые есть ссылки, определены. Настройка коллекций и полный каркас приложения опущены.
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"])
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)
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);
Удаление с помощью Roaring
Используйте точный BLOB типа Roaring при удалении сущностей, выбранных по внешнему набору целочисленных идентификаторов. Перед выполнением удаления проверьте набор членов и тип поля коллекции. Не используйте Bloom: приблизительное членство не является безопасным предикатом удаления, и формы Bloom отклоняются в выражениях Delete на этапе синтаксического анализа.
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})
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))
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 } });
Семантика удаления следует точному трёхзначному сопоставлению:
| Значение поля | membership_match(…) | не membership_match(…) |
|---|---|---|
| NULL | соответствия нет (NULL) | не совпадает (NULL) |
| в множестве элементов | совпадение | не совпадает |
| вне множества | не найдено | соответствие |
Таблица читается построчно: операция «Delete» с параметром « membership_match » удаляет сущности, входящие в набор, операция «Delete» с параметром « not membership_match » удаляет сущности, не входящие в набор, а сущности со значением NULL сохраняются при обеих полярностях.
- Элементы со значением поля NULL никогда не удаляются ни при одной из полярностей.
- Пустая растровая карта ничего не удаляет. «
not membership_match(<field>, {<empty>}, type=roaring)» удаляет все сущности со значением поля, отличным от NULL — точное дополнение, которое по замыслу может представлять собой большое множество. not membership_matchявляется точным дополнением к множеству элементов, состоящему из сущностей с не-NULL-значениями полей, а не приближением.
Для запечатанных сегментов, загруженных только по индексу, без исходных скалярных данных, при вычислении используется ограниченный обратный поиск, если индекс это поддерживает. Если недоступны ни исходные данные полей, ни соответствующий индекс, запрос завершается с ошибкой « FieldNotLoaded » вместо возврата нефильтрованных сущностей.
Операционные ограничения
| Параметр | По умолчанию | Настраиваемо | Что ограничивается |
|---|---|---|---|
proxy.maxMembershipFilterSize | 64 MiB | milvus.yaml прокси | одно тело BLOB |
proxy.maxMembershipFilterPlanSize | 128 MiB | milvus.yaml прокси | агрегат на запрос |
proxy.grpc.serverMaxRecvSize | 128 MiB | milvus.yaml прокси | весь запрос |
| Максимальный размер контейнера | 262 144 | зафиксировано в коде | на фильтр |
| Оценка объёма декодированной памяти Roaring | 64 MiB | фиксировано в коде | на фильтр |
| Ограничение формата MBF1 | 128 МБ | зафиксировано в коде | максимальный размер блока |
Блум fpr | 0,005 | построитель клиента | цель ложноположительных результатов |
Настройки « proxy.* » относятся к конфигурации сервера Milvus: они задаются оператором кластера в файле milvus.yaml в разделе « proxy » и не могут быть изменены для отдельного запроса или выражения во время фильтрации. Параметры Roaring admissions и ограничение формата MBF1 зафиксированы в реализации и не поддаются настройке нигде. Строка « fpr » является единственным элементом управления на стороне клиента: она выбирается при построении BLOB, при этом допустимый диапазон значений составляет от 0,0001 до 0,05.
Максимальный размер тела BLOB допускает около 48,6 миллиона элементов типа INT64 при значении fpr по умолчанию, с фиксированным заголовком оболочки размером 32 байта, добавляемым сверху. Общий бюджет запроса покрывает совокупный сериализованный размер схем, содержащих элементы, плюс совокупный расчетный размер фильтров Roaring после декодирования; этот бюджет совместно используют подзапросы HybridSearch и фильтры оценщиков. Тела BLOOM представляют собой степени числа два: количество элементов, чуть превышающее границу уровня, удваивает размер BLOB, а немного более высокий показатель fpr часто позволяет уместиться в следующий уровень ниже. Когда прокси отклоняет блоб Блума, превышающий допустимый размер, в сообщении об ошибке предлагается альтернативный вариант ( fpr ), который подходит.
- Результаты выражения принадлежности не кэшируются между запросами.
- Кластеры со смешанными версиями работают в режиме «закрыто»: QueryNodes, работающие на старом коде, отклоняют планы, содержащие узлы плана принадлежности. Сначала обновите прокси, затем завершите постепенное обновление, прежде чем включить оператор.
- В журналах сервера байты блоба замазываются. Текст выражения содержит только имя шаблона; двоичные данные передаются вне полосы в значениях шаблона.