Qdrant - Vector Database
Payload Индексы
1 млн документов. Семантический поиск работает за 5ms. Но добавить фильтр 'только published articles за 2024 год' - и запрос занимает 300ms. Причина: full scan по payload. Один createPayloadIndex - и снова 5ms.
- **E-commerce:** фильтры по категории + ценовому диапазону + наличию - все три поля должны быть проиндексированы
- **Новостной агрегатор:** фильтрация по дате публикации (datetime) + языку (keyword) + теме (keyword) - стандартный набор
- **Геосервис:** поиск похожих мест в радиусе 5 км - geo-индекс обязателен
Предварительные знания
Зачем нужны payload-индексы
**Payload-индекс** - структура данных, позволяющая Qdrant быстро фильтровать точки по значениям payload-полей. Без индекса каждый фильтрующий запрос требует **full scan** - перебора всех точек коллекции.
**Когда создавать индексы:** для любого поля, задействованного в регулярных фильтрах. Qdrant создаёт индексы явно через API - это позволяет не индексировать поля, которые используются редко.
**Индексы не блокируют запросы.** При создании индекса на заполненной коллекции Qdrant строит его в фоне. До окончания построения фильтры работают через full scan (медленно), после - через индекс (быстро). Прогресс можно отслеживать через `/collections/{name}` API.
Коллекция 500k точек. Фильтр по полю 'status' (значения: 'active', 'archived') используется в 80% запросов. Индекса нет. Какой эффект?
Типы индексов: keyword, integer, float, geo, text, datetime
**Qdrant поддерживает 6 типов payload-индексов**, каждый оптимизирован под свой тип данных и паттерн запросов.
| Тип индекса | Тип данных | Поддерживаемые фильтры | Примеры полей |
|---|---|---|---|
| keyword | string | match, is_null, is_empty | category, status, language, tag |
| integer | int64 | match, range (gte/lte/gt/lt) | year, user_id, view_count, priority |
| float | float64 | range (gte/lte/gt/lt) | price, score, latitude, longitude |
| geo | { lon, lat } | geo_bounding_box, geo_radius, geo_polygon | location, coordinates |
| text | string (full-text) | match.text (full-text search) | description, content, title |
| datetime | RFC3339 string | range (gte/lte/gt/lt) | created_at, published_at, expires_at |
Нужно искать рестораны: по кухне (семантически, через вектор), в радиусе 3 км, с рейтингом >= 4.5, открытые в данный момент (поле is_open = true). Какие индексы создать?
Создание индексов: практический пример
**Полный workflow:** создание коллекции, добавление точек, создание индексов, фильтрующий поиск. Индексы создаются после добавления данных - Qdrant строит их в фоне.
**Индексируйте только нужные поля.** Каждый индекс занимает дополнительную память (~50-200MB на 1M точек) и замедляет запись новых точек. Хорошее правило: индексируйте поля, используемые в фильтрах в >20% запросов. Редкие фильтры - пусть делают full scan.
Создавать индексы на все payload-поля «на всякий случай»
Индексировать только поля из фильтров. Избыточные индексы: +память, -скорость записи, нулевая польза от поиска
Каждый индекс нужно поддерживать при каждой записи новой точки (upsert). На 20 индексах вместо 5 - upsert в 4 раза медленнее. Плюс каждый индекс занимает RAM. Правило: индекс только если поле в фильтрах
Добавлен 1 млн точек, затем создан индекс на 'category'. В этот момент поступает запрос с фильтром по category. Что произойдёт?
Ключевые идеи
- **Без индекса = full scan:** каждый фильтр перебирает все точки O(N). С индексом - O(log N)
- **6 типов:** keyword (категории), integer/float (числа/диапазоны), geo (геолокация), text (full-text), datetime (время)
- **createPayloadIndex** создаётся после данных, работает онлайн - не блокирует запросы
- **Индексируйте умеренно:** только поля в фильтрах. Каждый лишний индекс = память + медленнее запись
- **Проверить индексы:** `getCollection` → `payload_schema`
Что дальше
Payload-индексы ускоряют фильтрацию. Следующий шаг - sparse vectors для лексического поиска, который работает совместно с семантическим.
- Sparse Vectors: BM42 и SPLADE — Лексический поиск дополняет семантический - вместе это Hybrid Search
- Квантизация векторов — Сжатие памяти - важно когда много индексов занимают RAM
- HNSW: Как работает индекс — HNSW + payload-индексы работают вместе для filtered vector search
Вопросы для размышления
- Как Qdrant решает: использовать payload-индекс или HNSW-первым при filtered vector search? Что происходит когда фильтр очень селективный?
- В коллекции хранится JSON с вложенными объектами (article.author.country). Как создать индекс на вложенное поле?
- Если 95% ваших запросов используют фильтр по 'is_active: true', но только 10% точек активны - стоит ли создавать keyword-индекс?