索引

Qdrant的一个关键特性是向量和传统索引的有效结合。这一点至关重要,因为要使向量搜索在过滤条件下有效工作,仅拥有向量索引是不够的。简单来说,向量索引加速了向量搜索,而有效负载索引则加速了过滤。

段中的索引独立存在,但索引本身的参数是为整个集合配置的。

并非所有段都会自动拥有索引。 它们的必要性由优化器设置决定,通常取决于存储点的数量。

有效载荷索引

Qdrant中的Payload索引类似于传统面向文档数据库中的索引。 此索引是为特定字段和类型构建的,并用于通过相应的过滤条件快速请求点。

索引还用于准确估计过滤器的基数,这有助于查询规划选择搜索策略。

创建索引需要额外的计算资源和内存,因此选择要索引的字段至关重要。Qdrant 不会做出这个选择,而是将其授予用户。

要将字段标记为可索引,您可以使用以下内容:

PUT /collections/{collection_name}/index
{
    "field_name": "name_of_the_field_to_index",
    "field_schema": "keyword"
}
from qdrant_client import QdrantClient

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.PayloadSchemaType.KEYWORD,
)
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: "keyword",
});
use qdrant_client::qdrant::{CreateFieldIndexCollectionBuilder, FieldType};

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::Keyword,
    ))
    .await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
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.Keyword,
        null,
        null,
        null,
        null)
    .get();
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 - 用于 关键字 有效载荷,影响 匹配 过滤条件。
  • integer - 用于整数负载,影响匹配范围过滤条件。
  • float - 用于float负载,影响范围过滤条件。
  • bool - 用于 bool 负载,影响 匹配 过滤条件(自 v1.4.0 起可用)。
  • geo - 用于 geo 负载,影响 地理边界框地理半径 过滤条件。
  • datetime - 用于datetime负载,影响范围过滤条件(自v1.8.0起可用)。
  • text - 一种特殊类型的索引,适用于关键字 / 字符串有效载荷,影响全文搜索过滤条件。
  • uuid - 一种特殊类型的索引,类似于keyword,但针对UUID值进行了优化。 影响匹配过滤条件。(自v1.11.0起可用)

有效载荷索引可能会占用一些额外的内存,因此建议仅对那些用于过滤条件的字段使用索引。 如果需要通过许多字段进行过滤,而内存限制不允许为所有字段建立索引,则建议选择最能限制搜索结果的字段。 通常,有效载荷值的不同值越多,索引的使用效率就越高。

全文索引

自 v0.10.0 起可用

Qdrant 支持对字符串负载进行全文搜索。 全文索引允许您通过负载字段中是否存在单词或短语来过滤点。

全文索引配置比其他索引稍微复杂一些,因为您可以指定分词参数。 分词是将字符串分割成标记的过程,这些标记随后会被索引到倒排索引中。

要创建全文索引,您可以使用以下内容:

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文档。在默认的构建配置中,由于生成的二进制文件大小不断增加,qdrant 并不包括对所有语言的支持。默认情况下,中文、日语和韩语并未启用,但可以通过使用--features multiling-chinese,multiling-japanese,multiling-korean标志从源代码构建 qdrant 来启用这些语言。

请参阅全文匹配以获取使用全文索引进行查询的示例。

参数化索引

自 v1.8.0 版本起可用

我们为integer索引添加了一个参数化变体,它允许您微调索引和搜索性能。

常规和参数化的integer索引都使用以下标志:

  • lookup: 支持使用匹配过滤器进行直接查找。
  • range: 支持 范围 过滤器。

常规的 integer 索引假设 lookuprange 都为 true。相反,要配置一个参数化的索引,你只需将其中一个过滤器设置为 true

lookuprange结果
truetrue常规整数索引
truefalse参数化整数索引
falsetrue参数化整数索引
falsefalse没有整数索引

参数化索引可以增强包含数百万个点的集合的性能。我们鼓励您尝试一下。如果在您的使用场景中没有增强性能,您可以随时恢复常规的integer索引。

注意:如果您在范围过滤器中设置"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,
		}),
})

磁盘上的有效载荷索引

自 v1.11.0 起可用

默认情况下,所有与有效负载相关的结构都存储在内存中。这样,向量索引在搜索过程中可以快速访问有效负载值。 由于在这种情况下延迟至关重要,建议将热有效负载索引保留在内存中。

然而,在某些情况下,有效载荷索引可能过大或很少使用。在这些情况下,可以将有效载荷索引存储在磁盘上。

要配置磁盘上的有效负载索引,您可以使用以下索引参数:

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

该列表将在未来的版本中扩展。

租户索引

自 v1.11.0 起可用

许多向量搜索用例需要多租户支持。在多租户场景中,集合预计会包含多个数据子集,每个子集属于不同的租户。

Qdrant 通过启用特殊配置向量索引来支持高效的多租户搜索,该配置禁用全局搜索,仅为每个租户构建子索引。

然而,知道集合包含多个租户可以解锁更多的优化机会。 为了进一步优化Qdrant中的存储,您可以启用租户索引以用于有效载荷字段。

此选项将告诉Qdrant哪些字段用于租户识别,并允许Qdrant构建存储以加快租户特定数据的搜索速度。 这种优化的一个例子是将租户特定数据在磁盘上更接近地定位,这将减少搜索期间的磁盘读取次数。

要为字段启用租户索引,您可以使用以下索引参数:

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

主要指标

自 v1.11.0 起可用

类似于租户索引,主体索引用于优化存储以实现更快的搜索,假设搜索请求主要通过主体字段进行过滤。

主索引的一个很好的用例是与时间相关的数据,其中每个点都与时间戳相关联。在这种情况下,主索引可以用于优化存储,以便在使用基于时间的过滤器时更快地进行搜索。

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

向量索引

向量索引是通过特定数学模型建立在向量上的数据结构。 通过向量索引,我们可以高效地查询与目标向量相似的几个向量。

Qdrant 目前仅使用 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与允许Qdrant在搜索期间使用过滤器的修改非常兼容。 其次,根据公开基准测试,它是最准确和最快的算法之一。

自 v1.1.1 版本起可用

HNSW参数也可以通过设置hnsw_config在集合和命名向量级别进行配置,以微调搜索性能。

稀疏向量索引

自 v1.7.0 版本起可用

Qdrant中的稀疏向量使用一种特殊的数据结构进行索引,这种数据结构针对具有高比例零值的向量进行了优化。在某些方面,这种索引方法与文本搜索引擎中使用的倒排索引类似。

  • Qdrant中的稀疏向量索引是精确的,意味着它不使用任何近似算法。
  • 添加到集合中的所有稀疏向量会立即在稀疏索引的可变版本中被索引。

使用Qdrant,您可以受益于更紧凑和高效的不可变稀疏索引,该索引在与密集向量索引相同的优化过程中构建。

这种方法特别适用于存储密集和稀疏向量的集合。

要配置稀疏向量索引,请使用以下参数创建一个集合:

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 修饰符

自 v1.10.0 起可用

对于许多搜索算法来说,考虑一个项目在集合中出现的频率是很重要的。 直观地说,一个项目在集合中出现的频率越低,它在搜索中的重要性就越高。

这也被称为逆文档频率(IDF)。它用于文本搜索引擎中,根据单词在集合中的稀有性来对搜索结果进行排名。

IDF 依赖于当前存储的文档,因此在流式推理模式下无法在稀疏向量中预先计算。 为了支持稀疏向量索引中的 IDF,Qdrant 提供了一个选项,可以自动使用 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(),
			},
		}),
})

Qdrant 使用以下公式来计算 IDF 修饰符:

$$ \text{IDF}(q_i) = \ln \left(\frac{N - n(q_i) + 0.5}{n(q_i) + 0.5}+1\right) $$

其中:

  • N 是集合中文档的总数。
  • n 是包含给定向量元素非零值的文档数量。

可过滤索引

单独使用有效载荷索引和向量索引无法完全解决使用过滤器进行搜索的问题。

在过滤器较弱的情况下,您可以直接使用HNSW索引。在过滤器严格的情况下,您可以使用有效载荷索引并完成重新评分。 然而,对于中间情况,这种方法效果不佳。

一方面,我们不能对太多向量进行全扫描。另一方面,当使用过于严格的过滤器时,HNSW图开始崩溃。

HNSW fail

hnsw graph

您可以在我们的博客文章中找到更多关于为什么会发生这种情况的信息。 Qdrant通过基于存储的有效载荷值扩展HNSW图来解决这个问题。

额外的边允许您使用HNSW索引高效地搜索附近的向量,并在图中搜索时应用过滤器。

这种方法最小化了条件检查的开销,因为你只需要计算搜索中涉及的一小部分点的条件。

这个页面有用吗?

感谢您的反馈!🙏

我们很抱歉听到这个消息。😔 你可以在GitHub上编辑这个页面,或者创建一个GitHub问题。