A B C D E F G H I J K L M N O P Q R S T V W Y Z А Б В Г Е И К М О П С Т Ц
Qd Qw

Qdrant

Qdrant

 

Qdrant (читается «квадрант») это векторная база данных с открытым исходным кодом, написанная на Rust. Она хранит эмбеддинги вместе с произвольными метаданными и отдаёт по запросу список ближайших по смыслу объектов. Проект начинался как движок семантического поиска, а сегодня чаще всего стоит под RAG-системами, рекомендательными сервисами и поиском по картинкам. Ниже разбираем, чем Qdrant отличается от соседей по классу и какие решения в нём приняты не так, как у всех.

 

Что такое Qdrant и какую задачу он закрывает

Обычная СУБД отвечает на вопрос про точное совпадение. Векторная отвечает на вопрос про похожесть. Текст, картинка или аудио прогоняются через модель эмбеддингов и превращаются в массив чисел фиксированной длины. Дальше близость двух объектов считается как расстояние между их векторами, и задача сводится к поиску ближайших соседей. Подробный разбор самого конвейера векторизации есть в статье ИИ и векторные базы данных.

Точный перебор всех векторов работает на тысячах записей и разваливается на миллионах. Поэтому векторные СУБД считают не точных, а приближённых ближайших соседей (approximate nearest neighbor, ANN). Немного точности меняется на радикальный выигрыш в скорости. Qdrant относится именно к этому классу решений. Он закрывает три задачи сразу. Хранит векторы, хранит рядом с ними структурированные метаданные и умеет искать по обоим сразу.

 

Архитектура и ключевые особенности

Qdrant разворачивается одним контейнером, поднимает REST на порту 6333 и gRPC на порту 6334. Отдельного координатора, ZooKeeper или внешнего хранилища метаданных он не требует. Кластер собирается из тех же узлов через встроенный Raft.

 

Коллекции, точки и payload

Единица хранения в Qdrant называется точкой (point). Точка состоит из идентификатора, одного или нескольких векторов и полезной нагрузки. Полезная нагрузка (payload) это произвольный JSON: категория, дата, идентификатор арендатора, ссылка на исходный документ.

Точки лежат в коллекциях. Коллекция задаёт размерность вектора и метрику расстояния, и эти два параметра потом не меняются. Метрик три: косинус, скалярное произведение и евклидово расстояние. Выбор диктует модель эмбеддингов, а не вкус разработчика.

Payload это не приложение к вектору, а полноправный участник поиска. По его полям строятся отдельные индексы: keyword, integer, float, bool, geo, datetime, text и uuid. Именно связка вектора и payload отличает векторную СУБД от библиотеки ANN вроде FAISS. В библиотеке метаданные приходится тащить в отдельном хранилище и склеивать руками.

 

Сегменты, оптимизаторы и журнал упреждающей записи

Физически коллекция разложена на сегменты. Каждый сегмент это самостоятельный кусочек данных со своим индексом. Часть сегментов доступна на запись, часть переведена в неизменяемое состояние ради скорости чтения.

Любое обновление сначала попадает в журнал упреждающей записи (write-ahead log, WAL) и только потом применяется по порядку. За перестройкой структур следят три фоновых оптимизатора. Вакуумный убирает накопившиеся удаления, когда их доля в сегменте переваливает за порог. Сливающий склеивает мелкие сегменты, чтобы поиск не бегал по десяткам кусков. Индексирующий включает индекс и отображение файлов в память, когда сегмент дорос до нужного объёма. Оптимизируемый сегмент при этом остаётся читаемым: изменения уходят в копию, которая имеет приоритет при выдаче.

Qdrant, устройство хранения коллекции из журнала упреждающей записи, сегментов и трёх фоновых оптимизаторов

 

Принцип работы графового индекса HNSW

Быстрый приближённый поиск в Qdrant построен на алгоритме HNSW (Hierarchical Navigable Small World). Это многослойный граф, где каждый вектор становится вершиной, а рёбра связывают

его с ближайшими соседями. Верхние слои разрежены и работают как скоростная трасса, нижние плотные и уточняют результат.

Поиск начинается с верхнего слоя от случайной точки входа. Алгоритм жадно переходит к соседу, который ближе к запросу, пока улучшения не кончатся. Затем спускается слоем ниже и повторяет то же самое. Качество регулируется тремя параметрами. Параметр m задаёт число рёбер на вершину. Параметр ef_construct определяет число кандидатов при построении графа. Параметр ef делает то же самое во время поиска. Больше значение, точнее ответ и дороже ресурсы.

Важная деталь для тех, кто первый раз смотрит на статистику коллекции. Пока сегмент меньше порога indexing_threshold, а по умолчанию это 10000 килобайт, граф вообще не строится. На таком объёме полный перебор быстрее, поэтому счётчик проиндексированных векторов честно показывает ноль. Это не поломка, а штатное поведение.

 

Qdrant, путь запроса от текста через модель эмбеддингов к обходу графа HNSW с фильтром по payload

 

ИИ-агенты для оптимизации бизнес-процессов

Код курса
AGENT
Ближайшая дата курса
26 октября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
66 000

 

Фильтрация по payload и почему она ломает граф

Запрос «похожие товары дешевле трёх тысяч рублей и в наличии» кажется тривиальным. На самом деле это самое узкое место всех векторных движков. Есть два наивных подхода, и оба плохие.

  • Фильтр после поиска. Движок достаёт сто ближайших соседей, а потом выбрасывает те, что не подходят по условию. Если условию удовлетворяет один процент базы, в выдаче останется пусто.
  • Фильтр до поиска. Движок сначала отбирает подходящие записи, а потом перебирает их полностью. На широком условии это перебор миллионов векторов.

Соблазнительно взять третий вариант и просто обходить граф, пропуская неподходящие вершины. Но тогда граф рассыпается. Инженеры Qdrant разобрали эту проблему через теорию перколяции в статье Filterable HNSW. У сети есть критический порог. Ниже него связная компонента распадается на изолированные острова. Жадный обход застревает в первом же острове и до остальных кандидатов просто не доходит.

Решение Qdrant состоит в том, чтобы достроить графу дополнительные рёбра. Для категориальных полей внутри каждой категории строится свой набор связей. Общее число рёбер вырастает максимум вдвое независимо от количества категорий. Для диапазонов и географии данные бьются на корзины, а соседние корзины связываются между собой. Получается фильтруемый индекс, который переживает отсечение большей части точек без потери связности. Отсюда же следует практическое правило: индекс по полю payload создаётся до загрузки данных, иначе дополнительные рёбра просто не появятся.

Перед выбором условия Qdrant оценивает кардинальность фильтра. Если под условие попадает совсем мало точек, дешевле пройти их перебором, и движок так и делает. Решение принимается автоматически на каждом запросе.

 

Квантование векторов как способ уложиться в память

Вектор из 768 чисел с плавающей точкой занимает 3 килобайта. Миллион таких векторов это 3 гигабайта только под сами данные, без графа. Квантование сжимает представление, размещая индекс в оперативной памяти вместо диска. Точность при этом просаживается, и её возвращают переоценкой. Движок берёт с запасом больше кандидатов по сжатым векторам. Финальный список он пересчитывает по исходным.

Метод Сжатие Когда брать
Скалярное (int8) в 4 раза универсальный выбор по умолчанию, потери около процента
Бинарное до 32 раз очень большие размерности и центрированные распределения
Произведений (PQ) до 64 раз память дороже скорости, расстояния считаются медленнее
TurboQuant от 8 до 32 раз появился в версии 1.18, случайный поворот вектора перед сжатием

TurboQuant стоит отдельного слова. Метод разработан в Google Research, а в Qdrant он дополнен идеями из RaBitQ. Перед сжатием вектор поворачивается случайным образом, что делает метод устойчивым к любому распределению значений. Запрос при этом остаётся в полной точности, сжимаются только хранимые векторы. В версии 1.19 появился тип данных Turbo4. Он хранит лишь четырёхбитное представление и убирает копию исходных векторов вовсе, экономя память ещё девятикратно. Плата понятная: переоценивать результат становится не по чему.

 

Когда Qdrant подходит, а когда нет

Сильные стороны движка складываются в довольно узкий, но частый профиль нагрузки.

  • Семантический поиск и RAG. Хранилище внешней памяти для языковой модели, где к похожести добавляются жёсткие условия по правам доступа или дате.
  • Мультиарендность. Индекс арендатора и раскладка данных по этому полю позволяют держать тысячи клиентов в одной коллекции.
  • Рекомендации и дедупликация. Поиск по нескольким векторам сразу и запросы вида «похоже на это, но не похоже на то».

Есть и обратная сторона. Qdrant не заменяет основное хранилище: он не даёт транзакций и соединений таблиц, поэтому исходные документы всё равно живут где-то ещё. Если объём коллекции измеряется десятками тысяч записей, отдельный сервис избыточен, и расширение pgvector в существующем PostgreSQL решит задачу дешевле. Наконец, векторный поиск в принципе плохо отвечает на вопросы, ответ на которые собирается из цепочки разных документов. Границы подхода и альтернатива на графах разобраны в материале Не только векторные БД.

Отдельно стоит помнить, что качество выдачи определяет модель эмбеддингов, а не база. Слабая модель на коротких текстах промахивается мимо очевидного ответа, и никакой параметр HNSW этого не исправит. Разбору такой связки посвящён курс ИИ-агенты для оптимизации бизнес-процессов.

 

Архитектура ML-систем

Код курса
ARML
Ближайшая дата курса
12 октября, 2026
Продолжительность
24/32 ак.часов
Стоимость обучения
76 800

 

Практический пример на Python

Весь код используемый в статье мы опубликовали на наш GitHub репозиторий

Qdrant from airflow import DAG from airflow.operators.bash import BashOperator from datetime import datetime with DAG( dag_id="spark_submit_demo", start_date=datetime(2025, 1, 1), schedule="@daily", catchup=False ) as dag: run = BashOperator( task_id="run_job", bash_command="spark-submit app.py" ) GitHub code example Qdrant

Стенд поднимается одним контейнером, векторы считает локальная модель nomic-embed-text через Ollama. Сначала создаём коллекцию и, что важно, индекс по полю payload до загрузки точек.

# Прогон: Qdrant 1.19.0 в Docker, qdrant-client 1.19.0, Python 3.12.13
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")

# Размерность и метрика фиксируются на всю жизнь коллекции
client.create_collection(
    collection_name="bds_courses",
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE),
    hnsw_config=models.HnswConfigDiff(m=16, ef_construct=100),
    quantization_config=models.ScalarQuantization(
        scalar=models.ScalarQuantizationConfig(
            type=models.ScalarType.INT8, quantile=0.99, always_ram=True
        )
    ),
)

# Индекс создаётся ДО загрузки точек, иначе граф не получит рёбра под фильтр
client.create_payload_index(
    collection_name="bds_courses",
    field_name="topic",
    field_schema=models.PayloadSchemaType.KEYWORD,
)

Дальше загружаем точки и спрашиваем базу дважды: свободным поиском и с жёстким условием по полю topic.

# Прогон: qdrant-client 1.19.0, вектор запроса получен моделью nomic-embed-text
from qdrant_client import QdrantClient, models

client = QdrantClient(url="http://localhost:6333")
qvec = embed("как обрабатывать события в реальном времени")  # список из 768 чисел

# Условие must применяется во время обхода графа, а не после выдачи
flt = models.Filter(
    must=[models.FieldCondition(key="topic", match=models.MatchValue(value="dwh"))]
)
found = client.query_points(
    "bds_courses", query=qvec, limit=3, query_filter=flt, with_payload=True
)
for point in found.points:
    print(f"{point.score:.4f} {point.payload['title']}")

Реальный вывод прогона на восьми точках выглядит так.

Результаты прогона

Две вещи в этом выводе стоят внимания. Счётчик проиндексированных векторов равен нулю по причине, описанной выше, восемь точек до порога индексации не дотягивают. А первое место в свободном поиске заняла статья про нейронные сети, хотя спрашивали про потоковую обработку. Это промах модели эмбеддингов на коротких описаниях, и он хорошо показывает, где проходит граница ответственности базы.

 

Заключение

Qdrant решает не задачу «сложить векторы», а задачу «искать по похожести вместе с условиями и уложиться в память». Отсюда и все его особенности: фильтруемый индекс с достроенными рёбрами, четыре схемы квантования, фоновые оптимизаторы сегментов. Порог входа низкий, один контейнер и клиент на несколько строк. Сложность начинается там, где появляются реальные объёмы, узкие фильтры и требование держать индекс в оперативной памяти.

 

Референсные ссылки