English translation is not available yet. Showing Russian content.

Что такое Filtered ANN Search и как оно реализовано в Qdrant vs Weaviate?

Краткий тезис

Filtered ANN Search — это поиск приблизительных ближайших соседей с учётом дополнительных фильтров (по метаданным, числовым полям, тегам). Реализация в Qdrant и Weaviate принципиально различается: Qdrant использует pre-filtering (сначала применяет фильтр, затем ANN на отфильтрованном подмножестве), а Weaviatepost-filtering (сначала ANN, затем фильтрует результаты). Выбор стратегии критически влияет на производительность и точность в зависимости от селективности фильтра.


1. Термин: Filtered ANN Search

ANN (Approximate Nearest Neighbor) — метод поиска векторов, который находит приблизительно ближайшие соседи к запросу, жертвуя точностью ради скорости. Filtered ANN Search добавляет к этому поиску ограничения по невекторным полям (например, «только документы за 2024 год» или «категория = 'наука'»). Без фильтрации ANN ищет по всему пространству векторов; с фильтром нужно учитывать только те векторы, которые удовлетворяют условию.

Проблема: ANN-индексы (например, HNSW) не хранят метаданные напрямую, поэтому фильтрация либо применяется до, либо после поиска. Два основных подхода:


2. Pre-filtering vs Post-filtering: общая концепция

ХарактеристикаPre-filteringPost-filtering
Порядок операцийСначала фильтр по метаданным → затем ANN на отфильтрованном набореСначала ANN на всём индексе → затем фильтр по метаданным
Размер поискового пространства для ANNМаленькое (только точки, прошедшие фильтр)Всё (весь индекс)
Скорость при селективном фильтреВысокая (ANN работает на малом подмножестве)Низкая (ANN сканирует много лишних точек)
Скорость при неселективном фильтреМожет быть низкой (фильтр отсекает мало, но overhead фильтрации есть)Высокая (ANN работает на всём индексе, фильтр только в конце)
ТочностьВысокая (ANN видит только релевантные точки)Может страдать, если ANN не нашёл нужные точки из-за приближения
OverheadНужен быстрый доступ к метаданным для фильтрацииНужно хранить метаданные для каждого вектора

Селективность фильтра — доля данных, проходящих через фильтр. Если фильтр пропускает <10% данных — он селективный; если >50% — неселективный.


3. Реализация в Qdrant: Pre-filtering

Qdrant по умолчанию использует pre-filtering. Процесс:

  1. Пользователь отправляет запрос с вектором и фильтром (например, must/filter conditions|should/filter conditions|must_not по скалярным полям).
  2. Qdrant сначала применяет фильтр к метаданным, получая список ID точек, удовлетворяющих условию.
  3. Затем запускает ANN-поиск (на основе HNSW) только по этим ID. Для этого Qdrant использует специальный механизм — oversampling (поиск большего числа кандидатов, чем нужно, чтобы компенсировать возможные потери из-за фильтрации) или прямой поиск по отфильтрованному подмножеству.

Ключевые особенности Qdrant:

  • Фильтры могут быть сложными (вложенные логические условия, диапазоны, теги).
  • Для ускорения фильтрации Qdrant хранит метаданные в payload index (индекс по полям, например, B-tree для чисел, inverted index для строк).
  • При очень селективных фильтрах (например, 1% данных) pre-filtering даёт огромный выигрыш в скорости, так как ANN работает на малом наборе.

Пример кода (Python):

from qdrant_client import QdrantClient
from qdrant_client.http.models import Filter, FieldCondition, Range

client = QdrantClient(host="localhost", port=6333)

# Фильтр: год >= 2023
filter_condition = Filter(
    must=[
        FieldCondition(
            key="year",
            range=Range(gte=2023)
        )
    ]
)

# Поиск с pre-filtering
results = client.search(
    collection_name="my_collection",
    query_vector=[0.1, 0.2, 0.3],
    query_filter=filter_condition,
    limit=10
)

4. Реализация в Weaviate: Post-filtering

Weaviate по умолчанию использует post-filtering. Процесс:

  1. Пользователь отправляет запрос с вектором и фильтром (через where clause).
  2. Weaviate сначала выполняет ANN-поиск (на основе HNSW) по всему индексу, получая, например, 100 ближайших соседей (параметр certainty или distance).
  3. Затем применяет фильтр к этим 100 результатам, оставляя только те, которые удовлетворяют условию.
  4. Если после фильтрации осталось меньше, чем limit, Weaviate может повторить поиск с большим числом кандидатов (механизм autocut).

Ключевые особенности Weaviate:

  • Фильтр применяется только к результатам ANN, а не ко всему индексу.
  • При селективных фильтрах (например, 1% данных) может потребоваться много кандидатов, чтобы после фильтрации осталось нужное количество. Это увеличивает latency.
  • Weaviate поддерживает hybrid search (векторный + keyword), где фильтр может применяться на разных этапах.

Пример кода (Python):

import weaviate

client = weaviate.Client("http://localhost:8080")

# Фильтр: year >= 2023
where_filter = {
    "path": ["year"],
    "operator": "GreaterThanEqual",
    "valueNumber": 2023
}

# Поиск с post-filtering
results = client.query.get(
    "Document",
    ["title", "year"]
).with_near_vector({
    "vector": [0.1, 0.2, 0.3]
}).with_where(where_filter).with_limit(10).do()

5. Сравнительная таблица Qdrant vs Weaviate для Filtered ANN

КритерийQdrant (pre-filtering)Weaviate (post-filtering)
Стратегия фильтрацииPre-filterPost-filter
Механизм ANNHNSWHNSW
Производительность при селективном фильтре (<10%)Высокая (ANN на малом подмножестве)Низкая (нужно много кандидатов)
Производительность при неселективном фильтре (>50%)Средняя (overhead фильтрации)Высокая (ANN на всём индексе, фильтр лёгкий)
Точность при селективном фильтреВысокая (ANN не теряет релевантные точки)Средняя (ANN может пропустить точки, которые не попали в top-k кандидатов)
Сложность настройкиНужно следить за размером payload indexПроще, но может потребоваться увеличение ef (параметр HNSW)
Поддержка сложных фильтровДа (вложенные логические, гео, текст)Да (where clause)
Типичный use caseRAG с жёсткими фильтрами по дате/категорииПолнотекстовый поиск с мягкими фильтрами

6. Когда что выбирать

  • Qdrant (pre-filtering) лучше, когда:

    • Фильтры селективные (<10% данных проходят).
    • Фильтры сложные (много условий).
    • Важна высокая точность при фильтрации (например, поиск только по документам определённого автора).
    • Пример: RAG-система для юридических документов, где нужно искать только по делам за последний год.
  • Weaviate (post-filtering) лучше, когда:

    • Фильтры неселективные (большая часть данных проходит).
    • Фильтры простые (одно условие).
    • Важна скорость при большом объёме данных без фильтрации.
    • Пример: поиск изображений по сходству, где фильтр по категории применяется редко.

Компромисс: Некоторые системы (например, Milvus) поддерживают оба подхода и позволяют выбирать стратегию. Qdrant также имеет режим post-filtering (через параметр exact), но по умолчанию pre-filtering.


7. Пет-проект для закрепления

Задача: Сравнить производительность Filtered ANN Search в Qdrant и Weaviate на синтетическом датасете.

Инструменты: Python, Qdrant Client, Weaviate Client, numpy, time.

Шаги:

  1. Сгенерировать 100 000 векторов размерностью 128 и случайные метаданные (год от 2000 до 2024, категория из 10 вариантов).
  2. Загрузить данные в Qdrant и Weaviate (создать коллекции/классы, добавить векторы и метаданные).
  3. Для каждого из 100 тестовых запросов:
    • Выполнить поиск с фильтром (селективным: год >= 2023, ~5% данных; неселективным: год >= 2000, ~100%).
    • Замерить latency и количество найденных релевантных (по золотому стандарту) документов.
  4. Построить таблицу средних значений.

Ожидаемый результат:

  • Для селективного фильтра Qdrant будет в 2-5 раз быстрее Weaviate.
  • Для неселективного фильтра Weaviate может быть немного быстрее или наравне.
  • Точность (recall) у Qdrant будет выше при селективном фильтре.

Код (фрагмент для Qdrant):

import time
from qdrant_client import QdrantClient
from qdrant_client.http.models import Filter, FieldCondition, Range

client = QdrantClient("localhost", 6333)
filter_cond = Filter(must=[FieldCondition(key="year", range=Range(gte=2023))])
start = time.time()
results = client.search("test_collection", query_vector=vec, query_filter=filter_cond, limit=10)
latency = time.time() - start

8. Связь с другими вопросами

ВопросТема
225Что такое ANN и HNSW?
227Сравнение Qdrant и Weaviate как векторных БД
224Как работает HNSW-индекс?
228Фильтрация в векторных БД: pre-filtering vs post-filtering
230Оптимизация latency в RAG при фильтрации
210Выбор векторной БД для RAG-системы

9. Навигация


Навигация