Индексирование#
Ключевой особенностью Platform V Vector DB (далее - Vector DB) является эффективное сочетание векторных и традиционных индексов. Это важно иметь в виду, потому что для эффективного поиска по векторам с фильтрами недостаточно только векторного индекса. Проще говоря, векторный индекс ускоряет поиск по векторам, а индексы полезной нагрузки ускоряют фильтрацию.
Индексы в сегментах существуют независимо друг от друга, но параметры самих индексов настраиваются для всей коллекции.
Не все сегменты автоматически имеют индексы. Их необходимость определяется настройками оптимизатора и обычно зависит от количества сохраненных точек.
Индекс полезной нагрузки#
Индекс полезной нагрузки в Vector DB аналогичен индексу в обычных документоориентированных базах данных. Данный индекс строится для конкретного поля и типа. Используется для быстрого запроса точек по соответствующему условию фильтрации.
Индекс также используется для точного расчета кардинальности фильтров, что помогает планировщику запросов выбрать стратегию поиска.
Создание индекса требует дополнительных вычислительных ресурсов и памяти, поэтому выбор полей для индексирования имеет важное значение. Vector DB не делает этот выбор, а предоставляет его пользователю.
Чтобы пометить поле как индексируемое используйте:
PUT /collections/{collection_name}/index
{
"field_name": "name_of_the_field_to_index",
"field_schema": "keyword"
}
client.create_payload_index(
collection_name="{collection_name}",
field_name="name_of_the_field_to_index",
field_schema="keyword",
)
client.createPayloadIndex("{collection_name}", {
field_name: "name_of_the_field_to_index",
field_schema: "keyword",
});
use qdrant_client::qdrant::{CreateFieldIndexCollectionBuilder, FieldType};
client
.create_field_index(
CreateFieldIndexCollectionBuilder::new(
"{collection_name}",
"name_of_the_field_to_index",
FieldType::Keyword,
)
.wait(true),
)
.await?;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;
client.createPayloadIndexAsync(
"{collection_name}",
"name_of_the_field_to_index",
PayloadSchemaType.Keyword,
null,
true,
null,
null);
using Qdrant.Client;
var client = new QdrantClient("localhost", 6334);
await client.CreatePayloadIndexAsync(
collectionName: "{collection_name}",
fieldName: "name_of_the_field_to_index"
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateFieldIndex(context.Background(), &qdrant.CreateFieldIndexCollection{
CollectionName: "{collection_name}",
FieldName: "name_of_the_field_to_index",
FieldType: qdrant.FieldType_FieldTypeKeyword.Enum(),
})
Можно использовать точечную нотацию для указания вложенного поля индексирования. Аналогично указанию вложенных фильтров.
Доступные типы полей:
keyword- для полезной нагрузки типа ключевое слово, влияет на условия фильтрации Match;integer- для полезной нагрузки типа целочисленное, влияет на условия фильтрации Match и Range;float- для полезной нагрузки типа с плавающей точкой, влияет на условия фильтрации Range;bool- для полезной нагрузки бинарного типа, влияет на условия фильтрации Match;geo- для полезной нагрузки типа геоданных, влияет на условия фильтрации Границы геопрямоугольника и Георадиус;datetime- для полезной нагрузки типа дата и время, влияет на условия фильтрации Range;text- специальный вид индекса, доступный для полезной нагрузки типа ключевое слово/строки, влияет на условия фильтрации поиск по всему тексту;uuid- особый тип индекса, аналогичныйkeyword, но оптимизированный для значений UUID. Влияет на условия фильтрации Match.
Индекс полезной нагрузки может занимать дополнительную память, поэтому рекомендуется использовать индекс только для тех полей, которые используются в условиях фильтрации. Если необходимо фильтровать по многим полям, а ограничения по памяти не позволяют проиндексировать их все, рекомендуется выбирать поле, которое наиболее эффективно ограничивает результаты поиска. Как правило, чем больше различных значений имеет полезная нагрузка, тем эффективнее будет использоваться индекс.
Полнотекстовый индекс#
Vector DB поддерживает полнотекстовый поиск по строковой полезной нагрузке. Полнотекстовый индекс позволяет фильтровать точки по наличию слова или фразы в поле полезной нагрузки.
Конфигурация полнотекстового индекса немного сложнее, чем у других индексов, так как можно указать параметры токенизации. Токенизация — процесс разбиения строки на токены, которые затем индексируются в инвертированном индексе.
Для создания полнотекстового индекса используйте:
PUT /collections/{collection_name}/index
{
"field_name": "name_of_the_field_to_index",
"field_schema": {
"type": "text",
"tokenizer": "word",
"min_token_len": 2,
"max_token_len": 20,
"lowercase": true
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_payload_index(
collection_name="{collection_name}",
field_name="name_of_the_field_to_index",
field_schema=models.TextIndexParams(
type="text",
tokenizer=models.TokenizerType.WORD,
min_token_len=2,
max_token_len=15,
lowercase=True,
),
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createPayloadIndex("{collection_name}", {
field_name: "name_of_the_field_to_index",
field_schema: {
type: "text",
tokenizer: "word",
min_token_len: 2,
max_token_len: 15,
lowercase: true,
},
});
use qdrant_client::qdrant::{
payload_index_params::IndexParams, CreateFieldIndexCollectionBuilder, FieldType,
PayloadIndexParams, TextIndexParams, TokenizerType,
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.create_field_index(
CreateFieldIndexCollectionBuilder::new(
"{collection_name}",
"name_of_the_field_to_index",
FieldType::Text,
)
.field_index_params(PayloadIndexParams {
index_params: Some(IndexParams::TextIndexParams(TextIndexParams {
tokenizer: TokenizerType::Word as i32,
min_token_len: Some(2),
max_token_len: Some(10),
lowercase: Some(true),
})),
}),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;
import io.qdrant.client.grpc.Collections.TextIndexParams;
import io.qdrant.client.grpc.Collections.TokenizerType;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createPayloadIndexAsync(
"{collection_name}",
"name_of_the_field_to_index",
PayloadSchemaType.Text,
PayloadIndexParams.newBuilder()
.setTextIndexParams(
TextIndexParams.newBuilder()
.setTokenizer(TokenizerType.Word)
.setMinTokenLen(2)
.setMaxTokenLen(10)
.setLowercase(true)
.build())
.build(),
null,
null,
null)
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreatePayloadIndexAsync(
collectionName: "{collection_name}",
fieldName: "name_of_the_field_to_index",
schemaType: PayloadSchemaType.Text,
indexParams: new PayloadIndexParams
{
TextIndexParams = new TextIndexParams
{
Tokenizer = TokenizerType.Word,
MinTokenLen = 2,
MaxTokenLen = 10,
Lowercase = true
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateFieldIndex(context.Background(), &qdrant.CreateFieldIndexCollection{
CollectionName: "{collection_name}",
FieldName: "name_of_the_field_to_index",
FieldType: qdrant.FieldType_FieldTypeText.Enum(),
FieldIndexParams: qdrant.NewPayloadIndexParamsText(
&qdrant.TextIndexParams{
Tokenizer: qdrant.TokenizerType_Whitespace,
MinTokenLen: qdrant.PtrOf(uint64(2)),
MaxTokenLen: qdrant.PtrOf(uint64(10)),
Lowercase: qdrant.PtrOf(true),
}),
})
Доступны следующие токенизаторы:
word- разбивает строку на слова, разделенные пробелами, знаками препинания и специальными символами;whitespace- разбивает строку на слова, разделенные пробелами;prefix- разбивает строку на слова, разделенные пробелами, знаками препинания и специальными символами, после чего создает префиксный индекс для каждого слова. Например:helloбудет проиндексировано какh,he,hel,hell,hello;multilingual- специальный тип токенизатора, основанный на пакете charabia. Он обеспечивает правильную токенизацию и лемматизацию для множества языков, включая языки с нелатинскими алфавитами и делимитерами без пробелов. См. документацию charabia для полного списка поддерживаемых языков и опций нормализации.
Примеры запросов с использованием полнотекстового индекса представлены в разделе Full Text Match (точное совпадение текста)
Параметризованный индекс#
Используется параметризованная версия индекса integer, которая позволяет тонко настроить производительность индексирования и поиска.
Оба варианта индекса integer используют следующие флаги:
lookup: разрешает использование прямого поиска с помощью фильтра Match;range: разрешает использование фильтра Range.
Регулярный индекс integer предполагает, что оба флага lookup и range установлены в true. Напротив, чтобы сконфигурировать параметризованный индекс, нужно установить один из этих фильтров в true:
|
|
Результат |
|---|---|---|
|
|
Регулярный целочисленный индекс |
|
|
Параметризованный целочисленный индекс |
|
|
Параметризованный целочисленный индекс |
|
|
Отсутствует целочисленный индекс |
Параметризованный индекс может повысить производительность в коллекциях с миллионами точек.
Примечание
Если установить "lookup": true с диапазоном фильтра, это может привести к значительным проблемам с производительностью.
Например, следующий код устанавливает параметризованный целочисленный индекс, который поддерживает только фильтры диапазона:
PUT /collections/{collection_name}/index
{
"field_name": "name_of_the_field_to_index",
"field_schema": {
"type": "integer",
"lookup": false,
"range": true
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_payload_index(
collection_name="{collection_name}",
field_name="name_of_the_field_to_index",
field_schema=models.IntegerIndexParams(
type=models.IntegerIndexType.INTEGER,
lookup=False,
range=True,
),
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createPayloadIndex("{collection_name}", {
field_name: "name_of_the_field_to_index",
field_schema: {
type: "integer",
lookup: false,
range: true,
},
});
use qdrant_client::qdrant::{
payload_index_params::IndexParams, CreateFieldIndexCollectionBuilder, FieldType,
IntegerIndexParams, PayloadIndexParams,
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.create_field_index(
CreateFieldIndexCollectionBuilder::new(
"{collection_name}",
"name_of_the_field_to_index",
FieldType::Integer,
)
.field_index_params(PayloadIndexParams {
index_params: Some(IndexParams::IntegerIndexParams(IntegerIndexParams {
lookup: false,
range: true,
})),
}),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.IntegerIndexParams;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createPayloadIndexAsync(
"{collection_name}",
"name_of_the_field_to_index",
PayloadSchemaType.Integer,
PayloadIndexParams.newBuilder()
.setIntegerIndexParams(
IntegerIndexParams.newBuilder().setLookup(false).setRange(true).build())
.build(),
null,
null,
null)
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreatePayloadIndexAsync(
collectionName: "{collection_name}",
fieldName: "name_of_the_field_to_index",
schemaType: PayloadSchemaType.Integer,
indexParams: new PayloadIndexParams
{
IntegerIndexParams = new()
{
Lookup = false,
Range = true
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateFieldIndex(context.Background(), &qdrant.CreateFieldIndexCollection{
CollectionName: "{collection_name}",
FieldName: "name_of_the_field_to_index",
FieldType: qdrant.FieldType_FieldTypeInteger.Enum(),
FieldIndexParams: qdrant.NewPayloadIndexParamsInt(
&qdrant.IntegerIndexParams{
Lookup: false,
Range: true,
}),
})
Хранение индекса полезной нагрузки на диске#
По умолчанию все структуры, связанные с полезной нагрузкой, хранятся в оперативной памяти. Таким образом, векторный индекс может быстро получить доступ к значениям полезной нагрузки во время поиска. Поскольку задержка в этом случае критична, рекомендуется хранить горячие индексы полезной нагрузки в памяти.
Однако бывают случаи, когда индексы полезной нагрузки слишком велики или редко используются. В таких случаях возможна запись индексов полезной нагрузки на диск.
Важно
Использование на-дисковых индексов полезной нагрузки может повлиять на латентность холодных запросов, поскольку оно требует дополнительных операций ввода/вывода диска.
Чтобы настроить хранение индекса полезной нагрузки на диске, можно использовать следующие параметры индекса:
PUT /collections/{collection_name}/index
{
"field_name": "payload_field_name",
"field_schema": {
"type": "keyword",
"on_disk": true
}
}
client.create_payload_index(
collection_name="{collection_name}",
field_name="payload_field_name",
field_schema=models.KeywordIndexParams(
type=models.KeywordIndexType.KEYWORD,
on_disk=True,
),
)
client.createPayloadIndex("{collection_name}", {
field_name: "payload_field_name",
field_schema: {
type: "keyword",
on_disk: true
},
});
use qdrant_client::qdrant::{
CreateFieldIndexCollectionBuilder,
KeywordIndexParamsBuilder,
FieldType
};
use qdrant_client::{Qdrant, QdrantError};
let client = Qdrant::from_url("http://localhost:6334").build()?;
client.create_field_index(
CreateFieldIndexCollectionBuilder::new(
"{collection_name}",
"payload_field_name",
FieldType::Keyword,
)
.field_index_params(
KeywordIndexParamsBuilder::default()
.on_disk(true),
),
);
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;
import io.qdrant.client.grpc.Collections.KeywordIndexParams;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createPayloadIndexAsync(
"{collection_name}",
"payload_field_name",
PayloadSchemaType.Keyword,
PayloadIndexParams.newBuilder()
.setKeywordIndexParams(
KeywordIndexParams.newBuilder()
.setOnDisk(true)
.build())
.build(),
null,
null,
null)
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreatePayloadIndexAsync(
collectionName: "{collection_name}",
fieldName: "payload_field_name",
schemaType: PayloadSchemaType.Keyword,
indexParams: new PayloadIndexParams
{
KeywordIndexParams = new KeywordIndexParams
{
OnDisk = true
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateFieldIndex(context.Background(), &qdrant.CreateFieldIndexCollection{
CollectionName: "{collection_name}",
FieldName: "name_of_the_field_to_index",
FieldType: qdrant.FieldType_FieldTypeKeyword.Enum(),
FieldIndexParams: qdrant.NewPayloadIndexParamsKeyword(
&qdrant.KeywordIndexParams{
OnDisk: qdrant.PtrOf(true),
}),
})
Поддерживаемые типы индексов на диске:
keyword;integer;float;datetime;uuid;text;geo.
Тенант-индекс#
Во многих сценариях поиска по векторам требуется мультитенантность. В мультитенантном сценарии предполагается, что коллекция содержит несколько подмножеств данных, где каждое подмножество принадлежит разным тенантами.
Vector DB поддерживает эффективный мультитенантный поиск путем включения специального конфигурирования векторного индекса, которое отключает глобальный поиск и строит подиндексы для каждого тенанта.
В Vector DB тенанты необязательно являются непересекающимися. Возможно наличие подмножеств данных, принадлежащих нескольким тенантам одновременно.
Тем не менее знание, что коллекция содержит множество тенантов, открывает дополнительные возможности для оптимизации. Чтобы еще больше оптимизировать хранение в Vector DB, можно включить индексацию тенантов для полей полезной нагрузки.
Эта возможность сообщит Vector DB, какие поля используются для идентификации тенантов, и позволит Vector DB структурировать хранилище для более быстрого поиска специфичных для тенанта данных. Примером такой оптимизации является локализация специфических для тенанта данных ближе на диске, что уменьшит количество обращений к диску во время поиска.
Чтобы включить индекс тенанта для поля, можно использовать следующие параметры индекса:
PUT /collections/{collection_name}/index
{
"field_name": "payload_field_name",
"field_schema": {
"type": "keyword",
"is_tenant": true
}
}
client.create_payload_index(
collection_name="{collection_name}",
field_name="payload_field_name",
field_schema=models.KeywordIndexParams(
type=models.KeywordIndexType.KEYWORD,
is_tenant=True,
),
)
client.createPayloadIndex("{collection_name}", {
field_name: "payload_field_name",
field_schema: {
type: "keyword",
is_tenant: true
},
});
use qdrant_client::qdrant::{
CreateFieldIndexCollectionBuilder,
KeywordIndexParamsBuilder,
FieldType
};
use qdrant_client::{Qdrant, QdrantError};
let client = Qdrant::from_url("http://localhost:6334").build()?;
client.create_field_index(
CreateFieldIndexCollectionBuilder::new(
"{collection_name}",
"payload_field_name",
FieldType::Keyword,
)
.field_index_params(
KeywordIndexParamsBuilder::default()
.is_tenant(true),
),
);
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;
import io.qdrant.client.grpc.Collections.KeywordIndexParams;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createPayloadIndexAsync(
"{collection_name}",
"payload_field_name",
PayloadSchemaType.Keyword,
PayloadIndexParams.newBuilder()
.setKeywordIndexParams(
KeywordIndexParams.newBuilder()
.setIsTenant(true)
.build())
.build(),
null,
null,
null)
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreatePayloadIndexAsync(
collectionName: "{collection_name}",
fieldName: "payload_field_name",
schemaType: PayloadSchemaType.Keyword,
indexParams: new PayloadIndexParams
{
KeywordIndexParams = new KeywordIndexParams
{
IsTenant = true
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateFieldIndex(context.Background(), &qdrant.CreateFieldIndexCollection{
CollectionName: "{collection_name}",
FieldName: "name_of_the_field_to_index",
FieldType: qdrant.FieldType_FieldTypeKeyword.Enum(),
FieldIndexParams: qdrant.NewPayloadIndexParamsKeyword(
&qdrant.KeywordIndexParams{
IsTenant: qdrant.PtrOf(true),
}),
})
Оптимизация тенанта поддерживается для следующих типов данных:
keyword;uuid.
Основной индекс#
Аналогично тенант-индексу, основной индекс используется для оптимизации хранения ради более быстрого поиска, предполагая, что запрос поиска в основном фильтруется полем основного объекта.
Хороший пример случая применения основного индекса — временные данные, где каждая точка связана со штампом времени. В данном сценарии основной индекс может быть использован для оптимизации хранения ради более быстрого поиска с временными фильтрами.
PUT /collections/{collection_name}/index
{
"field_name": "timestamp",
"field_schema": {
"type": "integer",
"is_principal": true
}
}
client.create_payload_index(
collection_name="{collection_name}",
field_name="timestamp",
field_schema=models.IntegerIndexParams(
type=models.IntegerIndexType.INTEGER,
is_principal=True,
),
)
client.createPayloadIndex("{collection_name}", {
field_name: "timestamp",
field_schema: {
type: "integer",
is_principal: true
},
});
use qdrant_client::qdrant::{
CreateFieldIndexCollectionBuilder,
IntegerIndexParamsBuilder,
FieldType
};
use qdrant_client::{Qdrant, QdrantError};
let client = Qdrant::from_url("http://localhost:6334").build()?;
client.create_field_index(
CreateFieldIndexCollectionBuilder::new(
"{collection_name}",
"timestamp",
FieldType::Integer,
)
.field_index_params(
IntegerIndexParamsBuilder::default()
.is_principal(true),
),
);
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;
import io.qdrant.client.grpc.Collections.IntegerIndexParams;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createPayloadIndexAsync(
"{collection_name}",
"timestamp",
PayloadSchemaType.Integer,
PayloadIndexParams.newBuilder()
.setIntegerIndexParams(
KeywordIndexParams.newBuilder()
.setIsPrincipa(true)
.build())
.build(),
null,
null,
null)
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreatePayloadIndexAsync(
collectionName: "{collection_name}",
fieldName: "timestamp",
schemaType: PayloadSchemaType.Integer,
indexParams: new PayloadIndexParams
{
IntegerIndexParams = new IntegerIndexParams
{
IsPrincipal = true
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateFieldIndex(context.Background(), &qdrant.CreateFieldIndexCollection{
CollectionName: "{collection_name}",
FieldName: "name_of_the_field_to_index",
FieldType: qdrant.FieldType_FieldTypeInteger.Enum(),
FieldIndexParams: qdrant.NewPayloadIndexParamsInt(
&qdrant.IntegerIndexParams{
IsPrincipal: qdrant.PtrOf(true),
}),
})
Оптимизация главного объекта поддерживается для следующих типов:
integer;float;datetime.
Векторный индекс#
Векторный индекс представляет собой структуру данных, построенную на основе векторов посредством определенной математической модели. С помощью векторного индекса можно эффективно запрашивать несколько векторов, похожих на целевой вектор.
Сейчас Vector DB использует только HNSW в качестве плотного векторного индекса.
HNSW (иерархический навигационный граф малого мира) - алгоритм индексирования на основе графа. Он строит многослойную навигационную структуру изображения согласно определенным правилам. В этой структуре верхние слои более разрежены, расстояния между узлами больше. Нижние слои плотнее, расстояние между узлами меньше. Поиск начинается с верхнего слоя, находит узел, ближайший к цели в данном слое, а затем переходит на следующий слой, чтобы начать новый поиск. После нескольких итераций можно быстро приблизиться к целевому положению.
Для повышения производительности HNSW ограничивает максимальную степень узлов на каждом уровне графа значением m. Кроме того, можно задать диапазон поиска с помощью ef_construct (при создании индекса) или ef (при поиске целей), чтобы уточнить область поиска.
Соответствующие параметры могут быть настроены в файле конфигурации:
storage:
# Default parameters of HNSW Index. Could be overridden for each collection or named vector individually
hnsw_index:
# Number of edges per node in the index graph.
# Larger the value - more accurate the search, more space required.
m: 16
# Number of neighbours to consider during the index building.
# Larger the value - more accurate the search, more time required to build index.
ef_construct: 100
# Minimal size (in KiloBytes) of vectors for additional payload-based indexing.
# If payload chunk is smaller than `full_scan_threshold_kb` additional indexing won't be used -
# in this case full-scan search should be preferred by query planner and additional indexing is not required.
# Note: 1Kb = 1 vector of size 256
full_scan_threshold: 10000
А также в процессе создания коллекции. Параметр ef задается во время поиска и по умолчанию равен ef_construct.
HNSW выбран по ряду причин. Во-первых, HNSW хорошо совместим с модификацией, позволяющей Vector DB использовать фильтры во время поиска. Во-вторых, это один из самых точных и быстрых алгоритмов, согласно публичным бенчмаркам.
Параметры HNSW также могут быть настроены на уровне коллекции и именованного вектора путем установки hnsw_config для тонкой настройки производительности поиска.
Разреженный векторный индекс#
Разреженные векторы в Vector DB индексируются специальной структурой данных, оптимизированной для векторов с высоким процентом нулей. В некотором смысле этот метод индексирования схож с инвертированным индексом, используемым в поисковых движках текстов.
Разреженный векторный индекс в Vector DB точный, то есть он не использует никаких приближенных алгоритмов.
Все разреженные векторы, добавляемые в коллекцию, сразу индексируются в изменяемой версии разреженного индекса.
С помощью Vector DB можно воспользоваться более компактным и эффективным неизменяемым разреженным индексом, который создается в ходе того же процесса оптимизации, что и плотный векторный индекс.
Такой подход особенно полезен для коллекций, хранящих как плотные, так и разреженные векторы.
Чтобы настроить разреженный векторный индекс, создайте коллекцию с параметрами:
PUT /collections/{collection_name}
{
"sparse_vectors": {
"text": {
"index": {
"on_disk": false
}
}
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config={},
sparse_vectors_config={
"text": models.SparseVectorParams(
index=models.SparseIndexParams(on_disk=False),
)
},
)
import { QdrantClient, Schemas } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
sparse_vectors: {
"splade-model-name": {
index: {
on_disk: false
}
}
}
});
use qdrant_client::qdrant::{
CreateCollectionBuilder, SparseIndexConfigBuilder, SparseVectorParamsBuilder,
SparseVectorsConfigBuilder,
};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
let mut sparse_vectors_config = SparseVectorsConfigBuilder::default();
sparse_vectors_config.add_named_vector_params(
"splade-model-name",
SparseVectorParamsBuilder::default()
.index(SparseIndexConfigBuilder::default().on_disk(true)),
);
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.sparse_vectors_config(sparse_vectors_config),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections;
QdrantClient client = new QdrantClient(
QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client.createCollectionAsync(
Collections.CreateCollection.newBuilder()
.setCollectionName("{collection_name}")
.setSparseVectorsConfig(
Collections.SparseVectorConfig.newBuilder().putMap(
"splade-model-name",
Collections.SparseVectorParams.newBuilder()
.setIndex(
Collections.SparseIndexConfig
.newBuilder()
.setOnDisk(false)
.build()
).build()
).build()
).build()
).get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateCollectionAsync(
collectionName: "{collection_name}",
sparseVectorsConfig: ("splade-model-name", new SparseVectorParams{
Index = new SparseIndexConfig {
OnDisk = false,
}
})
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateCollection(context.Background(), &qdrant.CreateCollection{
CollectionName: "{collection_name}",
SparseVectorsConfig: qdrant.NewSparseVectorsConfig(
map[string]*qdrant.SparseVectorParams{
"splade-model-name": {
Index: &qdrant.SparseIndexConfig{
OnDisk: qdrant.PtrOf(false),
}},
}),
})
Следующие параметры могут влиять на производительность:
on_disk: true- индекс хранится на диске, что позволяет экономить оперативную память. Это может замедлить производительность поиска.on_disk: false- индекс сохраняется на диске, но загружается в память для ускорения поиска.
В отличие от плотного векторного индекса, разреженному векторному индексу не требуется заранее заданный размер вектора. Он автоматически адаптируется к размеру векторов, добавляемых в коллекцию.
Примечание
Разреженный векторный индекс поддерживает только поиск подобия по скалярному произведению. Другие метрики расстояний не поддерживаются.
Модификатор IDF (обратная частота документа)#
Для многих алгоритмов поиска важно учитывать частоту появления элемента в коллекции. Интуитивно понятно, что чем реже элемент встречается в коллекции, тем важнее он становится в поиске.
Это также известно как обратная частота документа (IDF). Она используется в поисковых системах текстов для ранжирования результатов поиска на основании редкости слова в коллекции.
Значение IDF зависит от текущих документов в коллекции и поэтому не может быть предварительно вычислено в потоковом режиме вывода разреженных векторов. Для поддержки IDF в разреженном векторном индексе Vector DB предлагает возможность модифицировать запрос разреженного вектора с автоматической статистикой IDF.
Единственное требование — включить модификатор IDF в конфигурации коллекции:
PUT /collections/{collection_name}
{
"sparse_vectors": {
"text": {
"modifier": "idf"
}
}
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="{collection_name}",
vectors_config={},
sparse_vectors_config={
"text": models.SparseVectorParams(
modifier=models.Modifier.IDF,
),
},
)
import { QdrantClient, Schemas } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createCollection("{collection_name}", {
sparse_vectors: {
"text": {
modifier: "idf"
}
}
});
use qdrant_client::qdrant::{
CreateCollectionBuilder, Modifier, SparseVectorParamsBuilder, SparseVectorsConfigBuilder,
};
use qdrant_client::{Qdrant, QdrantError};
let client = Qdrant::from_url("http://localhost:6334").build()?;
let mut sparse_vectors_config = SparseVectorsConfigBuilder::default();
sparse_vectors_config.add_named_vector_params(
"text",
SparseVectorParamsBuilder::default().modifier(Modifier::Idf),
);
client
.create_collection(
CreateCollectionBuilder::new("{collection_name}")
.sparse_vectors_config(sparse_vectors_config),
)
.await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.CreateCollection;
import io.qdrant.client.grpc.Collections.Modifier;
import io.qdrant.client.grpc.Collections.SparseVectorConfig;
import io.qdrant.client.grpc.Collections.SparseVectorParams;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.createCollectionAsync(
CreateCollection.newBuilder()
.setCollectionName("{collection_name}")
.setSparseVectorsConfig(
SparseVectorConfig.newBuilder()
.putMap("text", SparseVectorParams.newBuilder().setModifier(Modifier.Idf).build()))
.build())
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.CreateCollectionAsync(
collectionName: "{collection_name}",
sparseVectorsConfig: ("text", new SparseVectorParams {
Modifier = Modifier.Idf,
})
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.CreateCollection(context.Background(), &qdrant.CreateCollection{
CollectionName: "{collection_name}",
SparseVectorsConfig: qdrant.NewSparseVectorsConfig(
map[string]*qdrant.SparseVectorParams{
"text": {
Modifier: qdrant.Modifier_Idf.Enum(),
},
}),
})
Vector DB использует следующую формулу для расчета модификатора IDF:
где N - общее число документов в коллекции, а n - количество документов, содержащих ненулевые значения для данного элемента вектора.
Фильтруемый индекс#
Отдельно индекс полезной нагрузки и векторный индекс не могут полностью решить проблему поиска с использованием фильтра.
При слабых фильтрах можно использовать сам индекс HNSW. При строгих фильтрах можно использовать индекс полезной нагрузки и полное пересчетное оценивание. Однако для промежуточных случаев данный подход работает плохо.
С одной стороны, не возможно выполнить полный обход по большому количеству векторов. С другой стороны, граф HNSW начинает разваливаться при использовании слишком жестких фильтров.


Vector DB решает эту проблему путем расширения графа HNSW дополнительными ребрами на основе хранимых значений полезной нагрузки.
Дополнительные ребра позволяют эффективно искать ближайшие векторы с использованием индекса HNSW и применять фильтры непосредственно при поиске в графе.
Данный подход минимизирует накладные расходы на проверку условий, так как нужно рассчитать условия только для небольшой доли точек, участвующих в поиске.