Qdrant — открытая векторная база данных на Rust, которая хранит эмбеддинги и ищет ближайшие соседние точки по косинусной или другой метрике. В отличие от полнотекстового поиска, она сравнивает смысл: запрос про резервное копирование находит статью про бэкапы, даже если точных слов в ней нет. Qdrant распространяется под Apache 2.0, ставится одним контейнером, поддерживает фильтрацию по payload, квантование и распределённый режим. Разберём запуск, схему коллекции, загрузку данных и эксплуатацию.
Как устроена векторная коллекция
Коллекция — набор точек. У каждой точки есть идентификатор, один или несколько векторов и payload — произвольный JSON с метаданными. Поиск идёт по HNSW-индексу, который строится в памяти; payload-индексы ускоряют фильтры. Размерность вектора определяется моделью эмбеддингов и не может быть изменена после создания коллекции.
- Cosine — угол между векторами, обычный выбор для текстовых эмбеддингов.
- Dot — скалярное произведение, если векторы уже нормализованы.
- Euclid — евклидово расстояние для координатных признаков.
- Manhattan — сумма модулей разностей, устойчивее к выбросам.
Запуск в Docker
Для разработки достаточно одного контейнера. Порт 6333 отдаёт REST и веб-панель, 6334 — gRPC.
docker run -d --name qdrant \
-p 6333:6333 -p 6334:6334 \
-v qdrant_storage:/qdrant/storage \
qdrant/qdrant
Панель доступна по адресу http://localhost:6333/dashboard. Для продакшена контейнер запускают с ограничением памяти: индекс HNSW живёт в RAM, и экономия на этом месте превращается в задержки.
Создание коллекции
Коллекция создаётся запросом PUT. В теле указывают размерность и метрику. Ниже — демонстрационная коллекция на четыре измерения: так все команды можно скопировать целиком, не генерируя настоящий вектор. В боевой коллекции размерность равна размерности модели — обычно 384, 768 или 1024.
curl -X PUT http://localhost:6333/collections/demo \
-H 'Content-Type: application/json' \
-d '{"vectors": {"size": 4, "distance": "Cosine"}}'
Ошибка «wrong vector size» означает, что модель не совпадает с объявленной размерностью.
Загрузка точек
Точки загружаются пакетами. Параметр wait=true заставляет сервер подтвердить запись до ответа; для массовых загрузок его отключают и следят за очередью.
curl -X PUT 'http://localhost:6333/collections/demo/points?wait=true' \
-H 'Content-Type: application/json' \
-d '{"points": [
{"id": 1, "vector": [0.10, 0.20, 0.30, 0.40], "payload": {"lang": "ru", "source": "manual"}},
{"id": 2, "vector": [0.90, 0.10, 0.05, 0.00], "payload": {"lang": "en", "source": "faq"}}
]}'
В реальном проекте векторы приходят из модели. С Python-клиентом удобно использовать FastEmbed: он скачивает модель и считает эмбеддинги локально, без внешнего API.
from fastembed import TextEmbedding
from qdrant_client import QdrantClient, models
model = TextEmbedding(model_name="sentence-transformers/all-MiniLM-L6-v2")
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
"docs",
vectors_config=models.VectorParams(size=384, distance=models.Distance.COSINE),
)
texts = ["настройка резервного копирования", "обновление ядра Linux"]
vectors = list(model.embed(texts))
client.upsert("docs", points=[
models.PointStruct(id=i, vector=v.tolist(), payload={"lang": "ru"})
for i, v in enumerate(vectors, start=1)
])
Модель all-MiniLM-L6-v2 даёт 384 измерения; для русских текстов берите многоязычную модель и меняйте размерность коллекции.
Поиск и фильтры
Универсальный эндпоинт /points/query принимает вектор, лимит и фильтр. Фильтры применяются до или во время обхода индекса, поэтому важно, чтобы поля были проиндексированы.
curl -X POST http://localhost:6333/collections/demo/points/query \
-H 'Content-Type: application/json' \
-d '{"query": [0.12, 0.18, 0.31, 0.39], "limit": 5, "with_payload": true,
"filter": {"must": [{"key": "lang", "match": {"value": "ru"}}]}}'
В ответе приходят точки с score: для Cosine чем выше, тем ближе по смыслу. Не сравнивайте score разных моделей, а порог score_threshold подбирайте на размеченной выборке.
Payload-индексы и гибридный поиск
Без индекса фильтр по payload всё равно работает, но перебирает точки. Индекс создаётся для конкретного поля и типа: keyword, integer, float, datetime, bool.
curl -X PUT http://localhost:6333/collections/demo/index \
-H 'Content-Type: application/json' \
-d '{"field_name": "lang", "field_schema": "keyword"}'
Для гибридного поиска добавляют разреженный вектор и объединяют результаты dense- и sparse-запросов через RRF: это даёт и смысловую близость, и точное совпадение терминов.
Память и квантование
Основной ресурс — оперативная память. Векторы можно держать на диске через on_disk, а скалярное квантование int8 уменьшает объём примерно вчетверо при небольшой потере качества.
curl -X PATCH http://localhost:6333/collections/demo \
-H 'Content-Type: application/json' \
-d '{"quantization_config": {"scalar": {"type": "int8", "always_ram": true}},
"optimizers_config": {"indexing_threshold": 20000}}'
Параметр indexing_threshold задаёт, сколько точек накопить до построения индекса: низкое значение замедляет загрузку, высокое ухудшает поиск на старте.
Резервные копии
Снапшот коллекции — согласованный снимок на диске сервера; его можно скачать и восстановить на другом сервере.
curl -X POST http://localhost:6333/collections/demo/snapshots
curl http://localhost:6333/collections/demo/snapshots
curl -X PUT http://localhost:6333/collections/demo/snapshots/recover \
-H 'Content-Type: application/json' \
-d '{"location": "file:///qdrant/snapshots/demo/demo-latest.snapshot"}'
Для регулярного бэкапа снапшоты складывают в S3-совместимое хранилище и проверяют восстановление на тестовом стенде.
Диагностика
Метрики отдаются в формате Prometheus, логи — в stdout контейнера. Для медленных запросов смотрите hnsw_ef: увеличение повышает полноту, но замедляет поиск.
curl -s localhost:6333/metrics | grep qdrant_collections
curl -s localhost:6333/collections/demo | python -m json.tool
docker logs --tail 100 qdrant
docker stats qdrant --no-stream
- Высокая задержка — проверьте payload-индексы и долю точек на диске.
- Рост памяти — уменьшите число реплик на узле или включите квантование.
- Ошибки размерности — сверьте модель и схему коллекции.
- Долгая загрузка — отправляйте пакеты по 100–500 точек и не ставьте wait=true на каждый запрос.
Чек-лист
- Размерность и метрика зафиксированы до загрузки данных.
- Поля фильтрации проиндексированы.
- Порог score подобран на реальных запросах.
- Снапшоты вынесены за пределы узла и проверены восстановлением.
- Память и диск покрывают рост коллекции на полгода.
- Метрики и алерты на задержку и ошибки настроены.
Итог
Qdrant закрывает задачу поиска по смыслу без сборки собственного индекса: коллекция, HNSW, фильтры и снапшоты доступны через понятный REST API. Ключевые решения принимаются до загрузки — модель эмбеддингов, размерность и метрика, а эксплуатация сводится к памяти, индексам payload и резервным копиям. Начните с одной коллекции и тысячи документов, измерьте качество на реальных запросах, и только потом включайте квантование и распределённый режим.