Qdrant - Vector Database
Фильтрация: Filter API
Vector search находит семантически близкое. Но «покажи мне только опубликованные статьи на русском языке, созданные после 2024 года, с ценой до 1000» - это filter. Вместе они дают точный и умный поиск.
- **E-commerce:** 'красные кроссовки' (vector) + size=42 AND brand='Nike' AND in_stock=true (filter)
- **Knowledge base:** 'как настроить nginx' (vector) + category='devops' AND language='ru' (filter)
- **Геолокация:** 'итальянский ресторан' (vector) + geo_radius=2km AND rating>4 AND open_now=true (filter)
Предварительные знания
Структура Filter: must / should / must_not
**Filter API** в Qdrant - способ ограничить поиск по payload. Работает как SQL WHERE, но для vector search. Фильтрация выполняется ДО или ПОСЛЕ векторного поиска - Qdrant выбирает стратегию автоматически.
| Оператор | SQL аналог | Логика |
|---|---|---|
| must | AND | Все условия должны выполняться |
| should | OR | Хотя бы одно условие должно выполняться |
| must_not | NOT | Ни одно условие не должно выполняться |
| min_should | OR с минимумом | Минимум N из M условий должны выполняться |
**Производительность:** фильтрация работает быстро ТОЛЬКО при наличии payload индекса. Без индекса Qdrant проверяет каждую точку - O(N). С индексом - O(log N). Всегда создавайте индекс для полей, по которым фильтруете (урок qd-08).
Нужно найти документы: (category='tech' AND language='en') OR (category='science' AND language='ru'). Как это записать в Filter API?
Field conditions: Match, Range, Geo и другие
**Field conditions** - конкретные проверки значений полей. Qdrant поддерживает: точное совпадение, совпадение из списка, диапазоны, геофильтры, проверку на null.
| Condition | Синтаксис | Описание |
|---|---|---|
| MatchValue | match: { value: 'en' } | Точное совпадение одного значения |
| MatchAny | match: { any: ['en', 'ru'] } | Совпадение с одним из списка (IN) |
| MatchExcept | match: { except: ['deleted'] } | Не совпадает ни с одним из списка (NOT IN) |
| Range | range: { gte: 0, lte: 100 } | Числовой диапазон: gt, gte, lt, lte |
| DatetimeRange | range: { gte: '2024-01-01T00:00:00Z' } | Диапазон дат (ISO 8601) |
| IsNull | is_null: { key: 'field' } | Поле равно null или отсутствует |
| IsEmpty | is_empty: { key: 'tags' } | Массив пуст или отсутствует |
| GeoBoundingBox | geo_bounding_box: { top_left, bottom_right } | Прямоугольная географическая область |
| GeoRadius | geo_radius: { center, radius } | Круговая область по радиусу (метры) |
Нужно найти продукты с тегами ['sale', 'featured'] (оба тега обязательны) и ценой от 500 до 2000. Как правильно написать фильтр?
Nested filters и вложенные объекты
**Nested filters** позволяют фильтровать по полям внутри вложенных объектов и массивов. Особенно полезно для документов со сложной структурой payload.
**Когда нужен nested:** если в payload есть массив объектов, и вы хотите проверить что ОДИН элемент массива удовлетворяет сразу нескольким условиям. Без nested, условия могут совпасть с разными элементами массива.
Payload: `orders: [{status: 'shipped', amount: 50}, {status: 'pending', amount: 200}]`. Нужно найти записи с хотя бы одним заказом status='shipped' AND amount>100. Что вернёт nested filter?
Производительность фильтрации
**Производительность filter** критична в production. Qdrant выбирает стратегию фильтрации автоматически на основе selectivity (избирательности) фильтра и размера коллекции.
| Стратегия | Когда применяется | Как работает |
|---|---|---|
| Pre-filtering | Фильтр высоко selective (< 5-10% точек) | Сначала фильтр → затем ANN в подмножестве |
| Post-filtering | Фильтр низко selective (> 50% точек) | Сначала ANN → затем фильтр по результатам |
| Exact (full scan) | Очень маленькая коллекция или нет индекса | Перебор всех точек |
**Selectivity и стратегия:** если фильтр очень избирательный (например, category='rare-topic' - только 0.1% точек), Qdrant делает pre-filtering и ANN только по этому подмножеству. Это быстрее, чем full ANN + post-filter. Хорошие индексы + правильная selectivity = быстрые filtered queries.
Добавить фильтр по каждому полю payload для 'гибкого' поиска без предварительной индексации
Создавать payload индекс только для полей, реально используемых в фильтрах в production
Каждый payload индекс занимает память (примерно 50-200MB на 1M точек для keyword поля). Индексируйте только то, что нужно. Неиндексированные поля для фильтрации = full scan = медленно
У вас 1 миллион документов. Фильтр `category='python'` отбирает ~500 тысяч (50%). Какую стратегию выберет Qdrant и почему это правильно?
Ключевые идеи
- **must/should/must_not** - AND/OR/NOT для payload условий
- **Field conditions:** MatchValue, MatchAny, Range, IsNull, IsEmpty, GeoBoundingBox, GeoRadius
- **Nested filter** - когда нужно что один элемент массива удовлетворяет нескольким условиям
- **Производительность:** payload индекс обязателен для полей в фильтрах
- **Стратегия:** Qdrant выбирает pre/post-filtering по selectivity автоматически
- Помните hook? Вектор находит смысл, фильтр уточняет структурные требования - вместе они непобедимы
Что дальше
Фильтрация работает. Следующий шаг - группировка результатов для дедупликации.
- Группировка результатов — searchGroups для дедупликации чанков по документу
- Hybrid Search — Добавить filter к hybrid search запросу
- Payload Index — Подробнее о настройке индексов для производительности
Вопросы для размышления
- Какие поля payload в вашем проекте нужно индексировать? Как определить это из требований к поиску?
- Когда имеет смысл использовать nested filter вместо обычного?
- Как измерить влияние фильтра на производительность поиска в production?
Связанные уроки
- qd-08-payload-index — Payload index - основа для фильтрации по полям
- qd-11-hybrid-search — Фильтры используются внутри hybrid search для pre-filtering
- pg-06-select — SQL WHERE clause - то же что qdrant filter, другой синтаксис
- db-09-indexes-btree — B-tree index для фильтрации - аналог payload index в Qdrant