인덱싱 (Indexing)

인덱싱 (Indexing)

Qdrant의 핵심 기능 하나가 벡터 인덱스와 전통적인 인덱스를 효과적으로 결합한다는 점이에요. 이 조합이 꼭 필요한 이유는, 필터와 함께 벡터 검색이 제대로 동작하려면 벡터 인덱스만으로는 부족하기 때문이죠. 쉽게 말하면, 벡터 인덱스는 벡터 검색을 빠르게 하고, 페이로드 인덱스는 필터링을 빠르게 해요.

세그먼트 안의 인덱스들은 서로 독립적으로 존재하지만, 인덱스 자체의 파라미터는 컬렉션 전체 단위로 설정돼요.

모든 세그먼트가 자동으로 인덱스를 갖는 건 아니에요. 인덱스가 필요한지는 옵티마이저 설정이 결정하고, 일반적으로 저장된 포인트 수에 따라 달라져요.

페이로드 인덱스

Qdrant의 페이로드 인덱스는 일반적인 문서 지향 데이터베이스의 인덱스와 비슷해요. 이 인덱스는 특정 필드와 타입을 대상으로 만들어지고, 해당하는 필터링 조건으로 포인트를 빠르게 조회하는 데 사용돼요. 또한 필터의 카디널리티를 정확하게 추정하는 데도 쓰이는데, 이 추정값이 쿼리 플래닝이 검색 전략을 고르는 데 도움을 줘요.

인덱스를 만드는 데는 추가적인 계산 리소스와 메모리가 필요하므로, 어떤 필드를 인덱싱할지 고르는 일이 중요해요. Qdrant는 이 선택을 대신 하지 않고 사용자에게 맡겨요.

페이로드 인덱싱을 지원하는 필드 타입은 다음과 같아요.

  • keyword - keyword 페이로드용으로, Match 필터링 조건에 영향을 줘요. 선택적으로 접두사 일치(prefix matching)를 켤 수 있어요.
  • integer - integer 페이로드용으로, MatchRange 필터링 조건에 영향을 줘요.
  • float - float 페이로드용으로, Range 필터링 조건에 영향을 줘요.
  • bool - bool 페이로드용으로, Match 필터링 조건에 영향을 줘요 (v1.4.0부터 사용 가능).
  • geo - geo 페이로드용으로, Geo Bounding BoxGeo Radius 필터링 조건에 영향을 줘요.
  • datetime - datetime 페이로드용으로, Range 필터링 조건에 영향을 줘요 (v1.8.0부터 사용 가능).
  • text - keyword / string 페이로드에서 쓸 수 있는 특별한 종류의 인덱스로, 전문 검색(Full Text search) 필터링 조건에 영향을 줘요. 텍스트 인덱스 설정에 대해 더 알아보기
  • uuid - keyword와 비슷하지만 UUID 값에 최적화된 특별한 타입의 인덱스예요. Match 필터링 조건에 영향을 줘요 (v1.11.0부터 사용 가능).

페이로드 인덱스는 추가적인 메모리와 디스크 공간을 차지하므로, 필터링 조건에 쓰는 필드에만 페이로드 인덱스를 적용하는 걸 권장해요. 많은 필드로 필터링해야 하는데 메모리 한도상 전부 인덱싱할 수 없다면, 검색 결과를 가장 많이 줄여주는 필드를 고르는 게 좋아요. 일반적으로, 페이로드 값이 가진 서로 다른 값이 많을수록 인덱스가 더 효율적으로 사용돼요.

페이로드 인덱스 만들기

필드의 페이로드 인덱스를 만드는 방법은 다음과 같아요.

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=models.PayloadSchemaType.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(),
})

점 표기법(dot notation)을 써서 중첩된 필드를 인덱싱 대상으로 지정할 수도 있어요. 중첩 필터를 지정하는 방식과 비슷해요.

페이로드 키 자체가 열려 있는(open-ended) 경우에는, 각 키를 따로 인덱싱하는 방식이 확장되지 않아요. 키를 고정된 필드 아래의 값으로 재구성한 다음 컬렉션 설정 시점에 인덱싱하세요. 모델링 패턴은 무작위 형태의 페이로드 인덱싱(Indexing Payloads of Random Shape) 문서를 참고해요.

페이로드 인덱스는 데이터를 넣기 전에 만들어야 해요. Qdrant의 필터 가능한 HNSW 인덱스는 페이로드 인덱스가 만들어진 뒤에 생성될 때만 추가적인 필터 인지 엣지(filter-aware edges)의 이점을 얻어요. 데이터를 이미 넣은 뒤에 페이로드 인덱스를 만들었다면, 새 페이로드 인덱스를 활용하려면 HNSW 인덱스를 다시 구축해야 해요.

인덱스되지 않은 필드에 필터링하는 쿼리 차단하기

인덱스되지 않은 필드로 필터링하는 쿼리는 단순히 느릴 뿐 아니라, 불필요하게 클러스터 리소스를 소비해서 다른 검색 쿼리의 지연 시간까지 나쁘게 만들어요. 이를 막기 위해 Qdrant는 인덱스되지 않은 필드로 필터링하는 쿼리를 차단하는 옵션을 제공해요. 이 옵션은 다음과 같은 이점을 줘요.

  • 빠른 실패(Fail-fast) 동작: 성능을 떨어뜨릴 쿼리가 API 경계에서 거부돼요. 잘못 설정된 인덱스가 지연 시간 폭증이 아니라 오류로 드러나요.
  • 성능 보장: 성공하는 모든 쿼리는 인덱스에 기반을 두게 돼서, 인덱스되지 않은 필드에 실수로 거는 필터가 운영 환경까지 도달하지 않아요.
  • 운영 가시성: 엄격 모드가 없으면, 쿼리가 느리긴 해도 결과를 반환하기 때문에 누락된 인덱스가 오랫동안 눈에 띄지 않을 수 있어요.

인덱스되지 않은 필드로 필터링하는 쿼리를 차단하려면 엄격 모드(strict mode)를 켜고 unindexed_filtering_retrievefalse로 설정하세요. 그러면 검색 쿼리가 인덱스되지 않은 필드로 필터링하려고 할 때 Qdrant가 오류를 돌려줘요. Qdrant Cloud에서는 이 설정이 기본적으로 모든 컬렉션에 적용돼요.

더 자세한 내용은 인덱스되지 않은 페이로드로 검색 비활성화(Disable Retrieving via Non Indexed Payload) 문서를 참고하세요.

파라미터화된 인덱스

필드 타입을 고르는 것 외에도, 페이로드 인덱스에 파라미터를 설정해서 인덱스가 저장되는 방식과 어떤 필터링 조건을 처리할 수 있는지를 세밀하게 조정할 수 있어요. 사용 가능한 파라미터는 필드 타입에 따라 달라지며, 아래 하위 섹션들에서 설명해요.

정수 인덱스에서 lookuprange 사용하기

v1.8.0부터 사용 가능

integer 인덱스의 파라미터화된 변형은 인덱싱과 검색 성능을 세밀하게 조정할 수 있게 해줘요.

파라미터화된 integer 인덱스는 다음 플래그를 사용해요.

  • lookup: Match 필터를 이용한 직접 조회(direct lookup) 지원을 켜요.
  • range: Range 필터 지원을 켜요.

integer 인덱스는 기본적으로 lookuprange가 모두 true라고 가정해요. 파라미터화된 인덱스를 구성하려면, 이 두 필터 중 하나만 true로 설정하세요.

lookup range 결과
true true 정수 인덱스의 기본 동작
true false 파라미터화된 정수 인덱스
false true 파라미터화된 정수 인덱스
false false 정수 인덱스 없음

lookup이나 rangefalse로 설정하면 대규모 컬렉션에서 튜닝을 통해 메모리 사용을 줄이는 데 도움이 될 수 있어요. 둘 중 하나를 false로 설정해서 메모리 사용이 개선되는지 직접 시도해 보는 걸 권장해요. 개선이 없거나 어떤 종류의 페이로드 필터를 쓰는지 확실하지 않다면, 일반 integer 인덱스를 사용하세요.

참고: "range": false로 설정해 놓고 그대로 range 필터를 쓰면 심각한 성능 문제가 생길 수 있어요. lookup 파라미터와 그에 해당하는 필터에도 똑같이 적용돼요.

예를 들어, 다음 코드는 range 필터만 지원하는 파라미터화된 정수 인덱스를 설정해요.

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;
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder, FieldType, IntegerIndexParamsBuilder,
};

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(IntegerIndexParamsBuilder::new(false, true).build()),
    )
    .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: qdrant.PtrOf(false),
			Range:  qdrant.PtrOf(true),
		}),
})

키워드 인덱스에서 접두사 일치(Prefix Matching)

v1.19.0부터 사용 가능

기본적으로 keyword 인덱스는 정확히 일치하는 매칭만 지원해요. prefix 플래그를 true로 설정하면 접두사 일치가 추가로 활성화되어서, Prefix Match 조건을 이용해 주어진 문자열로 시작하는 키워드 값을 필터링할 수 있어요.

이 기능은 URL, 경로, SKU 같은 식별자 형태의 값에 대한 접두사 필터링과, 필터 값 자동완성(예: 같은 필드에 facet 요청과 접두사 필터를 결합하는 방식)을 만들 때 유용해요. 이런 경우 text 인덱스는 잘 맞지 않아요. 토큰화가 식별자를 쪼개 버리고, text 스키마는 정확한 키워드 매칭을 잃어버리거든요.

접두사 일치를 활성화하려면 keyword 인덱스를 만들 때 prefix 플래그를 true로 설정하세요.

PUT /collections/{collection_name}/index
{
    "field_name": "url",
    "field_schema": {
        "type": "keyword",
        "prefix": true
    }
}
client.create_payload_index(
    collection_name="{collection_name}",
    field_name="url",
    field_schema=models.KeywordIndexParams(
        type=models.KeywordIndexType.KEYWORD,
        prefix=True,
    ),
)
client.createPayloadIndex("{collection_name}", {
  field_name: "url",
  field_schema: {
    type: "keyword",
    prefix: true
  },
});
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder,
    KeywordIndexParamsBuilder,
    FieldType
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

client.create_field_index(
    CreateFieldIndexCollectionBuilder::new(
        "{collection_name}",
        "url",
        FieldType::Keyword,
    )
    .field_index_params(
        KeywordIndexParamsBuilder::default()
            .prefix(true),
    ),
).await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.KeywordIndexParams;
import io.qdrant.client.grpc.Collections.KeywordPrefixParams;
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}",
        "url",
        PayloadSchemaType.Keyword,
        PayloadIndexParams.newBuilder()
            .setKeywordIndexParams(
                KeywordIndexParams.newBuilder()
                    .setPrefix(KeywordPrefixParams.newBuilder().build())
                    .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: "url",
 schemaType: PayloadSchemaType.Keyword,
 indexParams: new PayloadIndexParams
 {
  KeywordIndexParams = new KeywordIndexParams
  {
   Prefix = new KeywordPrefixParams()
  }
 }
);
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:      "url",
	FieldType:      qdrant.FieldType_FieldTypeKeyword.Enum(),
	FieldIndexParams: qdrant.NewPayloadIndexParamsKeyword(
		&qdrant.KeywordIndexParams{
			Prefix: &qdrant.KeywordPrefixParams{},
		}),
})

prefix를 켜면 전용 인덱스 구조가 만들어져서, 해당 필드의 접두사 필터는 인덱스가 처리하고 다른 인덱스된 필터만큼 빨라져요. 매칭은 바이트 단위로 이뤄지고(따라서 유효한 UTF-8에서는 문자 단위로), 대소문자를 구분하며, 정확한 키워드 매칭과 일관돼요.

prefix 플래그는 새 인덱스에서 활성화할 수 있어요. 기존 keyword 인덱스에서 활성화하면, 스키마가 이전 것과 호환되지 않기 때문에 인덱스 전체가 다시 구축돼요.

엄격 모드(strict mode)가 활성화되고 unindexed_filtering_retrieveunindexed_filtering_updatefalse로 설정되면, 접두사가 활성화된 keyword 인덱스가 없는 필드에 대한 접두사 조건은 거부돼요.

온디스크 페이로드 인덱스

v1.11.0부터 사용 가능

페이로드 인덱스는 항상 디스크에 영속화돼요. 기본적으로는 pinned 메모리 티어에도 로드돼요. 이렇게 하면 인덱스가 힙에 유지되어, 검색 중에 추가적인 디스크 I/O 없이 페이로드 값에 접근할 수 있어요.

하지만 페이로드 인덱스가 너무 크거나 거의 쓰이지 않는 경우도 있어요. 그런 경우라면 페이로드 인덱스를 cachedcold 티어로 옮길 수 있어요.

페이로드 인덱스의 메모리 티어를 구성하려면 memory 파라미터를 사용하세요.

PUT /collections/{collection_name}/index
{
    "field_name": "payload_field_name",
    "field_schema": {
        "type": "keyword",
        "memory": "cold"
    }
}
from qdrant_client import QdrantClient, models

client.create_payload_index(
    collection_name="{collection_name}",
    field_name="payload_field_name",
    field_schema=models.KeywordIndexParams(
        type=models.KeywordIndexType.KEYWORD,
        memory=models.Memory.COLD,
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

client.createPayloadIndex("{collection_name}", {
  field_name: "payload_field_name",
  field_schema: {
    type: "keyword",
    memory: "cold"
  },
});
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder,
    KeywordIndexParamsBuilder,
    FieldType,
    Memory
};
use qdrant_client::Qdrant;

client.create_field_index(
    CreateFieldIndexCollectionBuilder::new(
        "{collection_name}",
        "payload_field_name",
        FieldType::Keyword,
    )
    .field_index_params(
        KeywordIndexParamsBuilder::default()
            .memory(Memory::Cold),
    ),
).await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.KeywordIndexParams;
import io.qdrant.client.grpc.Collections.Memory;
import io.qdrant.client.grpc.Collections.PayloadIndexParams;
import io.qdrant.client.grpc.Collections.PayloadSchemaType;

client
    .createPayloadIndexAsync(
        "{collection_name}",
        "payload_field_name",
        PayloadSchemaType.Keyword,
        PayloadIndexParams.newBuilder()
            .setKeywordIndexParams(
                KeywordIndexParams.newBuilder()
                    .setMemory(Memory.Cold)
                    .build())
            .build(),
        null,
        null,
        null)
    .get();
using Qdrant.Client;
using Qdrant.Client.Grpc;

await client.CreatePayloadIndexAsync(
 collectionName: "{collection_name}",
 fieldName: "payload_field_name",
 schemaType: PayloadSchemaType.Keyword,
 indexParams: new PayloadIndexParams
 {
  KeywordIndexParams = new KeywordIndexParams
  {
   Memory   = Memory.Cold
  }
 }
);
import (
	"context"

	"github.com/qdrant/go-client/qdrant"
)

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{
			Memory: qdrant.Memory_Cold.Enum(),
		}),
})

테넌트 인덱스

v1.11.0부터 사용 가능

많은 벡터 검색 사용 사례가 멀티테넌시(multitenancy)를 요구해요. 멀티테넌트 시나리오에서 컬렉션은 여러 데이터 하위 집합을 포함하고, 각 하위 집합은 서로 다른 테넌트에 속할 것으로 기대돼요.

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;

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),
    ),
).await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.KeywordIndexParams;
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}",
        "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

프린시펄 인덱스(Principal Index)

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;

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),
    ),
).await?;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Collections.IntegerIndexParams;
import io.qdrant.client.grpc.Collections.KeywordIndexParams;
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}",
        "timestamp",
        PayloadSchemaType.Integer,
        PayloadIndexParams.newBuilder()
            .setIntegerIndexParams(
                IntegerIndexParams.newBuilder()
                    .setIsPrincipal(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

전문(Full-Text) 인덱스

Qdrant는 string 페이로드에 대한 전문 검색을 지원해요. 전문 인덱스를 사용하면 페이로드 필드에 특정 단어나 구절이 있는지로 포인트를 필터링할 수 있어요.

전문 인덱스 설정은 다른 인덱스보다 조금 더 복잡한데, 토큰화 파라미터를 지정할 수 있기 때문이에요. 토큰화(Tokenization)는 문자열을 토큰으로 쪼개고, 그 토큰들이 역색인(inverted index)에 인덱싱되는 과정이에요.

전문 인덱스로 쿼리하는 예시는 Full Text match 문서를 참고하세요.

전문 인덱스를 만들려면 다음을 사용할 수 있어요.

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": 10,
        "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=models.TextIndexType.TEXT,
        tokenizer=models.TokenizerType.WORD,
        min_token_len=2,
        max_token_len=10,
        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: 10,
    lowercase: true,
  },
});
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder,
    TextIndexParamsBuilder,
    FieldType,
    TokenizerType,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

let text_index_params = TextIndexParamsBuilder::new(TokenizerType::Word)
    .min_token_len(2)
    .max_token_len(10)
    .lowercase(true);

client
    .create_field_index(
        CreateFieldIndexCollectionBuilder::new(
            "{collection_name}",
            "name_of_the_field_to_index",
            FieldType::Text,
        ).field_index_params(text_index_params.build()),
    )
    .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),
		}),
})

토크나이저

토크나이저는 텍스트를 토큰이라 불리는 더 작은 단위로 쪼개는 알고리즘이에요. 이 토큰들이 전문 인덱스에 인덱싱되고 검색돼요. Qdrant의 맥락에서 토크나이저는 string 페이로드가 효율적인 검색과 필터링을 위해 어떻게 쪼개지는지를 결정해요. 토크나이저 선택은 쿼리가 인덱스된 텍스트와 어떻게 매칭되는지에 영향을 주며, 다양한 언어, 단어 경계, 접두사나 구절 매칭 같은 검색 동작을 지원해요.

사용 가능한 토크나이저는 다음과 같아요.

  • word (기본값) - 문자열을 공백, 구두점, 특수 문자로 구분된 단어들로 쪼개요.
  • whitespace - 문자열을 공백으로 구분된 단어들로 쪼개요.
  • prefix - 문자열을 공백, 구두점, 특수 문자로 구분된 단어들로 쪼갠 다음, 각 단어에 대해 접두사 인덱스를 만들어요. 예를 들어 helloh, he, hel, hell, hello로 인덱싱돼요.
  • multilingual - charabiavaporetto 같은 여러 패키지를 기반으로 하는 특별한 종류의 토크나이저로, 다양한 언어에 대해 빠르고 정확한 토큰화를 제공해요. 비라틴 알파벳이나 공백이 아닌 구분자를 쓰는 언어를 포함해 여러 언어를 제대로 토큰화해요. 지원 언어와 정규화 옵션의 전체 목록은 charabia 문서를 참고하세요. 참고: 일본어의 경우 Qdrant는 vaporetto 프로젝트를 사용하는데, 이는 charabia보다 오버헤드가 훨씬 적으면서 비슷한 성능을 유지해요.

소문자 변환(Lowercasing)

기본적으로 Qdrant의 전문 검색은 대소문자를 구분하지 않아요. 예를 들어 사용자가 소문자 tv로 검색해도 대문자 TV가 들어 있는 텍스트 필드를 찾을 수 있어요. 대소문자 무시는 인덱스의 단어와 쿼리 용어를 모두 소문자로 변환함으로써 이루어져요.

소문자 변환은 기본적으로 활성화돼 있어요. 대소문자를 구분하는 전문 검색을 쓰려면 lowercasefalse로 설정해 전문 인덱스를 구성하세요.

PUT /collections/{collection_name}/index
{
    "field_name": "name_of_the_field_to_index",
    "field_schema": {
        "type": "text",
        "tokenizer": "word",
        "lowercase": false
    }
}
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=models.TextIndexType.TEXT,
        tokenizer=models.TokenizerType.WORD,
        lowercase=False,
    ),
)
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",
    lowercase: false,
  },
});
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder,
    TextIndexParamsBuilder,
    FieldType,
    TokenizerType,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

let text_index_params = TextIndexParamsBuilder::new(TokenizerType::Word)
    .lowercase(false);

client
    .create_field_index(
        CreateFieldIndexCollectionBuilder::new(
            "{collection_name}",
            "name_of_the_field_to_index",
            FieldType::Text,
        ).field_index_params(text_index_params.build()),
    )
    .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)
                    .setLowercase(false)
                    .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,
            Lowercase = false,
        }
    }
);
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_Word,
            Lowercase:   qdrant.PtrOf(false),
        }),
})

ASCII 폴딩(ASCII Folding)

v1.16.0부터 사용 가능

활성화하면 ASCII 폴딩은 유니코드 문자를 대응하는 ASCII 동등 문자로 변환해요. 예를 들어 발음 구별 부호(diacritics)를 제거하는 식이죠. 예를 들어 ãa로, çc로, ée로 바뀌어요.

ASCII 폴딩은 인덱스의 단어와 쿼리 용어 양쪽에 적용되므로 재현율(recall)을 높여줘요. 예를 들어 사용자는 cafe로 검색해도 café라는 단어가 들어 있는 텍스트 필드를 찾을 수 있어요.

ASCII 폴딩은 기본적으로 활성화되어 있지 않아요. 활성화하려면 ascii_foldingtrue로 설정해 전문 인덱스를 구성하세요.

PUT /collections/{collection_name}/index
{
    "field_name": "name_of_the_field_to_index",
    "field_schema": {
        "type": "text",
        "tokenizer": "word",
        "ascii_folding": 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=models.TextIndexType.TEXT,
        tokenizer=models.TokenizerType.WORD,
        ascii_folding=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",
    ascii_folding: true,
  },
});
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder,
    TextIndexParamsBuilder,
    FieldType,
    TokenizerType,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

let text_index_params = TextIndexParamsBuilder::new(TokenizerType::Word)
    .ascii_folding(true);

client
    .create_field_index(
        CreateFieldIndexCollectionBuilder::new(
            "{collection_name}",
            "name_of_the_field_to_index",
            FieldType::Text,
        ).field_index_params(text_index_params.build()),
    )
    .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)
                    .setLowercase(true)
                    .setAsciiFolding(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,
            Lowercase = true,
			AsciiFolding = 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_Word,
			Lowercase:    qdrant.PtrOf(true),
			AsciiFolding: qdrant.PtrOf(true),
		}),
})

형태소 분석기(Stemmer)

**형태소 분석기(stemmer)**는 텍스트 처리에서 단어를 어근 또는 기본 형태(이를 "어간(stem)"이라 불러요)로 줄이는 데 쓰는 알고리즘이에요. 예를 들어 "running", "runner", "runs"는 모두 "run"이라는 어간으로 줄일 수 있어요. Qdrant에서 전문 인덱스를 구성할 때 특정 언어에 사용할 형태소 분석기를 지정할 수 있어요. 이렇게 하면 인덱스가 단어의 서로 다른 굴절형이나 파생형을 인식하고 매칭할 수 있게 돼요.

Qdrant는 Snowball 형태소 분석기 구현을 제공해요. 이는 널리 쓰이며 가장 인기 있는 일부 언어에 대해 성능이 좋은 변형이에요. 지원 언어 목록은 rust-stemmers 저장소를 참고하세요.

전문 인덱스에서 형태소 분석은 기본적으로 활성화되어 있지 않아요. 활성화하려면 원하는 언어로 snowball 형태소 분석기를 구성하세요.

PUT /collections/{collection_name}/index
{
    "field_name": "name_of_the_field_to_index",
    "field_schema": {
        "type": "text",
        "tokenizer": "word",
        "stemmer": {
            "type": "snowball",
            "language": "english"
        }
    }
}
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=models.TextIndexType.TEXT,
        tokenizer=models.TokenizerType.WORD,
        stemmer=models.SnowballParams(
            type=models.Snowball.SNOWBALL,
            language=models.SnowballLanguage.ENGLISH
        )
    ),
)
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",
    stemmer: {
      type: "snowball",
      language: "english"
    }
  }
});
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder,
    TextIndexParamsBuilder,
    FieldType,
    TokenizerType,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

let text_index_params = TextIndexParamsBuilder::new(TokenizerType::Word)
    .snowball_stemmer("english".to_string());

client
    .create_field_index(
        CreateFieldIndexCollectionBuilder::new(
            "{collection_name}",
            "{field_name}",
            FieldType::Text,
        ).field_index_params(text_index_params.build()),
    )
    .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.SnowballParams;
import io.qdrant.client.grpc.Collections.StemmingAlgorithm;
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)
                    .setStemmer(
                        StemmingAlgorithm.newBuilder()
                            .setSnowball(
                                SnowballParams.newBuilder().setLanguage("english").build())
                            .build())
                    .build())
            .build(),
        true,
        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,
			Stemmer = new StemmingAlgorithm
			{
				Snowball = new SnowballParams
				{
					Language = "english"
				}
			}
		}
	}
);
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_Word,
			Stemmer: qdrant.NewStemmingAlgorithmSnowball(&qdrant.SnowballParams{
				Language: "english",
			}),
		}),
})

불용어(Stopwords)

불용어(stopwords)는 "the", "is", "at", "which", "on" 같은 흔한 단어로, 검색과 검색 결과 처리에 별로 의미 정보를 담지 않기 때문에 텍스트 처리 중에 자주 걸러져요.

Qdrant에서는 전문 인덱싱과 검색 중에 무시할 불용어 목록을 지정할 수 있어요. 이렇게 하면 검색 쿼리를 단순하게 만들고 관련성을 높여줘요.

미리 정의된 언어 기반으로 불용어를 구성할 수 있고, 기존 불용어 목록에 사용자 정의 단어로 확장할 수도 있어요.

전문 인덱스에서는 불용어 제거가 기본적으로 활성화되어 있지 않아요. 활성화하려면 원하는 언어와 사용자 정의 불용어로 stopwords 파라미터를 구성하세요.

// Simple
PUT /collections/{collection_name}/index
{
    "field_name": "name_of_the_field_to_index",
    "field_schema": {
        "type": "text",
        "tokenizer": "word",
        "stopwords": "english"
    }
}

// Explicit
PUT /collections/{collection_name}/index
{
    "field_name": "name_of_the_field_to_index",
    "field_schema": {
        "type": "text",
        "tokenizer": "word",
        "stopwords": {
            "languages": [
                "english",
                "spanish"
            ],
            "custom": [
                "example"
            ]
        }
    }
}
from qdrant_client import QdrantClient, models

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

# Simple
client.create_payload_index(
    collection_name="{collection_name}",
    field_name="name_of_the_field_to_index",
    field_schema=models.TextIndexParams(
        type=models.TextIndexType.TEXT,
        tokenizer=models.TokenizerType.WORD,
        stopwords=models.Language.ENGLISH,
    ),
)

# Explicit
client.create_payload_index(
    collection_name="{collection_name}",
    field_name="name_of_the_field_to_index",
    field_schema=models.TextIndexParams(
        type=models.TextIndexType.TEXT,
        tokenizer=models.TokenizerType.WORD,
        stopwords=models.StopwordsSet(
            languages=[
                models.Language.ENGLISH,
                models.Language.SPANISH,
            ],
            custom=[
                "example"
            ]
        ),
    ),
)
import { QdrantClient } from "@qdrant/js-client-rest";

const client = new QdrantClient({ host: "localhost", port: 6333 });

// Simple
client.createPayloadIndex("{collection_name}", {
  field_name: "name_of_the_field_to_index",
  field_schema: {
    type: "text",
    tokenizer: "word",
    stopwords: "english"
  },
});

// Explicit
client.createPayloadIndex("{collection_name}", {
  field_name: "name_of_the_field_to_index",
  field_schema: {
    type: "text",
    tokenizer: "word",
    stopwords: {
      languages: [
        "english",
        "spanish"
      ],
      custom: [
        "example"
      ]
    }
  },
});
use qdrant_client::qdrant::{
    CreateFieldIndexCollectionBuilder,
    TextIndexParamsBuilder,
    FieldType,
    TokenizerType,
    StopwordsSet,
};
use qdrant_client::Qdrant;

let client = Qdrant::from_url("http://localhost:6334").build()?;

// Simple
let text_index_params = TextIndexParamsBuilder::new(TokenizerType::Word)
    .stopwords_language("english".to_string());

client
    .create_field_index(
        CreateFieldIndexCollectionBuilder::new(
            "{collection_name}",
            "name_of_the_field_to_index",
            FieldType::Text,
        ).field_index_params(text_index_params.build()),
    )
    .await?;

// Explicit
let text_index_params = TextIndexParamsBuilder::new(TokenizerType::Word)
    .stopwords(StopwordsSet {
        languages: vec!["english".to_string(), "spanish".to_string()],
        custom: vec!["example".to_string()],
    });

client
    .create_field_index(
        CreateFieldIndexCollectionBuilder::new(
            "{collection_name}",
            "{field_name}",
            FieldType::Text,
        ).field_index_params(text_index_params.build()),
    )
    .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.StopwordsSet;
import io.qdrant.client.grpc.Collections.TextIndexParams;
import io.qdrant.client.grpc.Collections.TokenizerType;
import java.util.List;

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)
                    .setStopwords(
                        StopwordsSet.newBuilder()
                            .addAllLanguages(List.of("english", "spanish"))
                            .addAllCustom(List.of("example"))
                            .build())
                    .build())
            .build(),
        true,
        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,
            Stopwords = new StopwordsSet
            {
                Languages = { "english", "spanish" },
                Custom = { "example" }
            }
        }
    }
);
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_Word,
			Stopwords: &qdrant.StopwordsSet{
				Languages: []string{"english", "spanish"},
				Custom:    []string{"example"},
			},
		}),
})