Split View: Qdrant 벡터DB 운영 및 사용 방법 총정리 — 컬렉션 설계부터 RAG 연동까지
Qdrant 벡터DB 운영 및 사용 방법 총정리 — 컬렉션 설계부터 RAG 연동까지
- 들어가며
- 1. 핵심 개념
- 2. 아키텍처
- 3. 컬렉션 설계 및 인덱스 전략
- 4. CRUD 및 검색
- 5. 필터링과 페이로드
- 6. RAG 파이프라인 연동
- 7. 운영 체크리스트
- 8. 흔한 실수
- 9. 요약
- 퀴즈
들어가며
LLM 기반 애플리케이션이 확산되면서 벡터 데이터베이스는 이제 선택이 아닌 필수 인프라가 되었습니다. 그중 Qdrant는 Rust로 작성되어 뛰어난 성능을 보여주고, gRPC와 REST API를 모두 지원하며, 페이로드 필터링과 멀티테넌시 등 프로덕션에 필요한 기능을 갖추고 있습니다. 이 글에서는 Qdrant를 실서비스에 도입하려는 분들을 위해 컬렉션 설계, CRUD, 필터링, RAG 연동, 운영 모니터링까지 실전 중심으로 정리합니다.
1. 핵심 개념
벡터 데이터베이스란?
벡터 데이터베이스는 고차원 벡터(임베딩)를 저장하고, **유사도 검색(Similarity Search)**을 밀리초 단위로 수행하는 전문 데이터베이스입니다. 텍스트, 이미지, 오디오 등을 임베딩 모델로 변환한 벡터를 저장한 뒤, 쿼리 벡터와 가장 가까운 벡터를 찾아 반환합니다.
Qdrant의 특징
- Rust 기반: 메모리 안전성과 높은 처리량을 동시에 달성
- 페이로드(Payload): 벡터에 JSON 메타데이터를 함께 저장하여 필터링 가능
- 멀티테넌시: 컬렉션 내에서 테넌트별 데이터 격리 지원
- 양자화(Quantization): 메모리 사용량을 최대 4배 절감
- 분산 모드: Raft 합의 프로토콜 기반 클러스터 구성
거리 메트릭 비교
| 메트릭 | 수식 | 사용처 | 범위 |
|---|---|---|---|
| Cosine | 1 - cos(a, b) | 텍스트 임베딩 (OpenAI, Cohere) | 0 ~ 2 |
| Euclid | ||a - b|| | 이미지 특징 벡터, 좌표 기반 | 0 ~ +inf |
| Dot Product | -a . b | 정규화된 임베딩, 추천 시스템 | -inf ~ +inf |
| Manhattan | sum(|a_i - b_i|) | 희소 벡터, 특수 도메인 | 0 ~ +inf |
팁: OpenAI
text-embedding-3-small/large는 정규화된 벡터를 반환하므로 Cosine과 Dot Product가 동일한 결과를 냅니다. 이 경우 Dot Product가 연산이 더 빠릅니다.
2. 아키텍처
핵심 구성 요소
- Segment: 벡터와 페이로드를 물리적으로 분리 저장하는 단위. 병렬 검색의 기본 단위
- WAL (Write-Ahead Log): 쓰기 연산의 내구성 보장. 장애 복구 시 재생
- HNSW Index: Hierarchical Navigable Small World 그래프 기반의 ANN 인덱스
- Payload Index: 메타데이터 필터링을 위한 보조 인덱스 (keyword, integer, geo 등)
클러스터 모드
클러스터 모드에서는 Shard 단위로 데이터를 분산하고, Raft 합의 프로토콜로 메타데이터 일관성을 유지합니다. 각 샤드는 여러 노드에 복제본(replica)을 둘 수 있습니다. Docker Compose로 구성하려면 환경변수 QDRANT__CLUSTER__ENABLED=true와 P2P 포트를 설정하고, 두 번째 노드부터는 --bootstrap 플래그로 첫 번째 노드를 지정합니다.
3. 컬렉션 설계 및 인덱스 전략
컬렉션 생성
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, HnswConfigDiff, OptimizersConfigDiff
from qdrant_client.models import ScalarQuantization, ScalarQuantizationConfig, ScalarType
client = QdrantClient(host="localhost", port=6333)
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(
size=1536, # OpenAI text-embedding-3-small 차원
distance=Distance.COSINE,
),
hnsw_config=HnswConfigDiff(
m=16, # 그래프 연결 수
ef_construct=128, # 인덱스 빌드 시 탐색 폭
),
optimizers_config=OptimizersConfigDiff(
indexing_threshold=20000,
),
quantization_config=ScalarQuantization(
scalar=ScalarQuantizationConfig(type=ScalarType.INT8, quantile=0.99, always_ram=True),
),
)
HNSW 파라미터 가이드
| 파라미터 | 기본값 | 권장 범위 | 설명 |
|---|---|---|---|
m | 16 | 8 ~ 64 | 그래프 연결 수. 높을수록 recall 향상, 메모리 증가 |
ef_construct | 100 | 64 ~ 512 | 인덱스 빌드 품질. 높을수록 정확하지만 느림 |
ef (검색 시) | 128 | 64 ~ 512 | 검색 시 탐색 폭. recall/latency 트레이드오프 |
양자화(Quantization) 전략
# Scalar Quantization: float32 → int8 (메모리 4배 절감)
# 일반적인 텍스트 검색에 적합, recall 손실 < 1%
# Binary Quantization: float32 → 1-bit (메모리 32배 절감)
# 매우 공격적. 고차원(1536+)에서만 사용. oversampling 필요
from qdrant_client.models import BinaryQuantization, BinaryQuantizationConfig
client.update_collection(
collection_name="documents",
quantization_config=BinaryQuantization(
binary=BinaryQuantizationConfig(
always_ram=True,
),
),
)
4. CRUD 및 검색
벡터 삽입 (Upsert)
from qdrant_client.models import PointStruct
import openai
def get_embedding(text: str) -> list[float]:
resp = openai.embeddings.create(model="text-embedding-3-small", input=text)
return resp.data[0].embedding
# 단건 upsert
client.upsert(
collection_name="documents",
points=[
PointStruct(
id=1,
vector=get_embedding("Qdrant는 Rust로 작성된 벡터DB입니다."),
payload={"title": "Qdrant 소개", "category": "database", "tenant_id": "team-alpha"},
),
],
)
# 대량 upsert — 100건씩 배치 처리
BATCH_SIZE = 100
for i in range(0, len(points), BATCH_SIZE):
client.upsert(collection_name="documents", points=points[i : i + BATCH_SIZE])
유사도 검색 (Search)
from qdrant_client.models import SearchParams
results = client.search(
collection_name="documents",
query_vector=get_embedding("벡터 데이터베이스 성능 최적화"),
limit=10,
score_threshold=0.7,
search_params=SearchParams(
hnsw_ef=128, # 검색 시 ef 값 (정확도 조절)
exact=False, # True면 brute-force (정확하지만 느림)
),
with_payload=True,
with_vectors=False, # 벡터 자체는 반환하지 않음 (응답 크기 절감)
)
for result in results:
print(f"ID: {result.id}, Score: {result.score:.4f}")
print(f" Title: {result.payload['title']}")
REST API로 검색
curl -X POST "http://localhost:6333/collections/documents/points/search" \
-H "Content-Type: application/json" \
-d '{
"vector": [0.1, 0.2, ...],
"limit": 10,
"with_payload": true,
"params": {
"hnsw_ef": 128
}
}'
업데이트 및 삭제
from qdrant_client.models import PointIdsList, FilterSelector, Filter, FieldCondition, MatchValue
# 페이로드 업데이트
client.set_payload(collection_name="documents", payload={"category": "vector-database"}, points=[1, 2, 3])
# 포인트 삭제 (ID 기반)
client.delete(collection_name="documents", points_selector=PointIdsList(points=[10, 11, 12]))
# 필터 기반 삭제
client.delete(
collection_name="documents",
points_selector=FilterSelector(
filter=Filter(must=[FieldCondition(key="category", match=MatchValue(value="deprecated"))])
),
)
5. 필터링과 페이로드
페이로드 인덱스 생성
필터링 성능을 높이려면 반드시 페이로드 인덱스를 생성해야 합니다.
from qdrant_client.models import PayloadSchemaType, TextIndexParams, TokenizerType
# 키워드 인덱스 (정확 매칭)
client.create_payload_index(collection_name="documents", field_name="category", field_schema=PayloadSchemaType.KEYWORD)
# 정수 인덱스 (범위 검색)
client.create_payload_index(collection_name="documents", field_name="page_count", field_schema=PayloadSchemaType.INTEGER)
# 텍스트 인덱스 (전문 검색 — 다국어 토크나이저)
client.create_payload_index(
collection_name="documents",
field_name="content",
field_schema=TextIndexParams(type="text", tokenizer=TokenizerType.MULTILINGUAL, min_token_len=2, max_token_len=20),
)
복합 필터 검색
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range
# 카테고리가 "database"이고 페이지 수가 10 이상인 문서 중 유사도 검색
results = client.search(
collection_name="documents",
query_vector=get_embedding("벡터 인덱스 최적화"),
query_filter=Filter(
must=[
FieldCondition(
key="category",
match=MatchValue(value="database"),
),
FieldCondition(
key="page_count",
range=Range(gte=10),
),
],
must_not=[
FieldCondition(
key="status",
match=MatchValue(value="archived"),
),
],
),
limit=5,
)
하이브리드 검색 (벡터 + 텍스트)
Qdrant는 Sparse Vector를 지원하여 BM25 스타일의 키워드 검색과 벡터 검색을 결합할 수 있습니다.
from qdrant_client.models import SparseVector, SparseVectorParams, Prefetch, FusionQuery, Fusion
# dense + sparse 벡터 설정으로 컬렉션 생성
client.create_collection(
collection_name="hybrid_docs",
vectors_config={"dense": VectorParams(size=1536, distance=Distance.COSINE)},
sparse_vectors_config={"sparse": SparseVectorParams()},
)
# Reciprocal Rank Fusion 기반 하이브리드 검색
results = client.query_points(
collection_name="hybrid_docs",
prefetch=[
Prefetch(query=get_embedding("벡터 검색 성능"), using="dense", limit=20),
Prefetch(query=SparseVector(indices=[1, 42, 1337], values=[0.9, 0.3, 0.7]), using="sparse", limit=20),
],
query=FusionQuery(fusion=Fusion.RRF),
limit=10,
)
6. RAG 파이프라인 연동
LangChain 연동
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_qdrant import QdrantVectorStore
from langchain.chains import RetrievalQA
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. 문서 분할
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = text_splitter.split_documents(documents)
# 2. 벡터 스토어 생성
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = QdrantVectorStore.from_documents(
documents=chunks,
embedding=embeddings,
url="http://localhost:6333",
collection_name="rag_documents",
force_recreate=True,
)
# 3. RAG 체인 구성
retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={
"k": 5,
"score_threshold": 0.7,
},
)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
)
# 4. 질문 응답
response = qa_chain.invoke({"query": "Qdrant의 HNSW 인덱스 파라미터를 어떻게 튜닝하나요?"})
print(response["result"])
LlamaIndex 연동
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
import qdrant_client
# 1. Qdrant 클라이언트 & 벡터 스토어
qclient = qdrant_client.QdrantClient(host="localhost", port=6333)
vector_store = QdrantVectorStore(
client=qclient,
collection_name="llamaindex_docs",
)
# 2. 설정
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.llm = OpenAI(model="gpt-4o", temperature=0)
Settings.chunk_size = 512
Settings.chunk_overlap = 50
# 3. 문서 로드 및 인덱싱
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(
documents,
vector_store=vector_store,
)
# 4. 쿼리 엔진
query_engine = index.as_query_engine(
similarity_top_k=5,
)
response = query_engine.query("Qdrant에서 필터링과 벡터 검색을 동시에 하는 방법은?")
print(response)
7. 운영 체크리스트
모니터링
Qdrant는 Prometheus 형식의 메트릭을 /metrics 엔드포인트로 제공합니다.
# 메트릭 확인
curl http://localhost:6333/metrics
# 주요 메트릭
# qdrant_grpc_responses_total — gRPC 요청 수
# qdrant_rest_responses_total — REST 요청 수
# qdrant_collection_points_total — 컬렉션별 포인트 수
# qdrant_collection_search_latency — 검색 지연 시간
운영 체크리스트 표
| 항목 | 확인 사항 | 빈도 |
|---|---|---|
| 메모리 | RAM 사용률 80% 미만 유지 | 매일 |
| 디스크 | 스토리지 여유 공간 20% 이상 | 매일 |
| 검색 지연 | p99 latency < 100ms | 실시간 |
| 인덱스 상태 | optimizer_status = "ok" | 매시간 |
| 스냅샷 | 자동 스냅샷 정상 생성 확인 | 매일 |
| 복제본 | 모든 샤드의 replica 상태 active | 매시간 |
| WAL 크기 | WAL 디렉토리 비정상 증가 모니터링 | 매일 |
| API 오류율 | 5xx 응답 비율 < 0.1% | 실시간 |
스냅샷 및 백업
# 스냅샷 생성 및 목록 조회
client.create_snapshot(collection_name="documents")
snapshots = client.list_snapshots(collection_name="documents")
for snap in snapshots:
print(f"Name: {snap.name}, Size: {snap.size}")
# REST API로 스냅샷 다운로드
curl -o snapshot.tar "http://localhost:6333/collections/documents/snapshots/snapshot-2026-03-09.snapshot"
# 전체 스토리지 스냅샷 (모든 컬렉션 포함)
curl -X POST "http://localhost:6333/snapshots"
8. 흔한 실수
페이로드 인덱스 없이 필터 검색 사용: 페이로드 인덱스가 없으면 모든 포인트를 순차 스캔합니다. 필터를 쓰는 필드에는 반드시 인덱스를 생성하세요.
HNSW
ef값을 너무 낮게 설정: 기본값(128)보다 낮추면 recall이 급격히 떨어집니다. 성능이 충분하다면 기본값을 유지하세요.벡터 차원과 모델 불일치: 컬렉션 생성 시
size와 임베딩 모델의 차원이 다르면 upsert 시 에러가 발생합니다.text-embedding-3-small은 1536,text-embedding-3-large는 3072입니다.배치 크기를 너무 크게 설정: 한 번에 10,000건 이상을 upsert하면 메모리 스파이크가 발생할 수 있습니다. 100~500건 단위로 배치 처리하세요.
양자화 적용 후 테스트 생략: Scalar Quantization은 recall 손실이 적지만, Binary Quantization은 반드시 recall 벤치마크를 수행해야 합니다.
단일 컬렉션에 모든 데이터 저장: 서로 다른 임베딩 모델이나 차원을 사용하는 데이터를 하나의 컬렉션에 넣으면 검색 품질이 저하됩니다. 용도별로 컬렉션을 분리하세요.
스냅샷 백업 미설정: WAL만으로는 디스크 장애 시 복구가 불가합니다. 정기적인 스냅샷을 외부 스토리지(S3 등)에 보관하세요.
gRPC 대신 REST만 사용: 대량 데이터 처리 시 gRPC가 REST 대비 2~3배 빠릅니다. Python 클라이언트는
prefer_grpc=True로 설정하면 자동으로 gRPC를 사용합니다.
# gRPC 사용 권장
client = QdrantClient(
host="localhost",
port=6333,
grpc_port=6334,
prefer_grpc=True,
)
9. 요약
Qdrant는 Rust의 성능과 풍부한 기능을 겸비한 벡터 데이터베이스로, 특히 페이로드 필터링, 양자화, 하이브리드 검색 기능이 프로덕션 RAG 파이프라인에 적합합니다. 컬렉션 설계 시 HNSW 파라미터와 양자화 전략을 신중히 선택하고, 페이로드 인덱스를 적극 활용하세요. 운영 단계에서는 Prometheus 메트릭 모니터링과 정기 스냅샷을 반드시 설정하고, 트래픽 증가에 대비해 클러스터 모드와 샤딩 전략을 미리 계획해 두는 것이 중요합니다.
퀴즈
Q1: Qdrant는 어떤 프로그래밍 언어로 작성되었으며, 이로 인해 어떤 이점이 있나요?
Qdrant는 Rust로 작성되었습니다. Rust는 가비지 컬렉터 없이 메모리 안전성을 보장하므로, 높은 처리량과 낮은 지연 시간을 동시에 달성할 수 있습니다. 이는 대규모 벡터 검색 워크로드에서 특히 유리합니다.
Q2: Cosine 거리와 Dot Product 거리 메트릭의 차이점은 무엇이며, 언제 Dot Product를 선택해야 하나요?
Cosine 거리는 두 벡터의 방향 유사도를 측정하고 크기를 정규화합니다. Dot Product는 정규화 없이 내적을 계산합니다. 이미 정규화된 벡터(예: OpenAI 임베딩)를 사용할 때는 두 메트릭의 결과가 동일하므로, 연산이 더 빠른 Dot Product를 선택하는 것이 유리합니다.
Q3: HNSW 인덱스의 m 파라미터는 무엇을 의미하며, 값을 높이면 어떤 트레이드오프가 발생하나요?
m 파라미터는 무엇을 의미하며, 값을 높이면 어떤 트레이드오프가 발생하나요?m 파라미터는 HNSW 그래프에서 각 노드의 연결(이웃) 수를 결정합니다. 값을 높이면 검색 recall(정확도)이 향상되지만, 인덱스가 차지하는 메모리가 증가하고 인덱스 빌드 시간이 길어집니다. 일반적으로 8~64 범위에서 설정합니다.
Q4: Scalar Quantization과 Binary Quantization의 차이점은 무엇인가요?
Scalar Quantization은 float32를 int8로 변환하여 메모리를 약 4배 절감하며, recall 손실이 1% 미만으로 일반적인 텍스트 검색에 적합합니다. Binary Quantization은 float32를 1-bit로 변환하여 메모리를 32배 절감하지만, recall 손실이 크므로 반드시 oversampling과 벤치마크가 필요합니다. 고차원(1536+) 벡터에서만 사용을 권장합니다.
Q5: 페이로드 인덱스를 생성하지 않으면 필터 검색 시 어떤 문제가 발생하나요?
페이로드 인덱스가 없으면 Qdrant는 필터 조건을 평가하기 위해 모든 포인트의 페이로드를 순차적으로 스캔합니다. 데이터 규모가 커질수록 검색 지연이 선형적으로 증가하여 프로덕션 환경에서 심각한 성능 저하를 초래합니다.
Q6: Qdrant에서 하이브리드 검색(벡터 + 키워드)을 구현하는 방법은 무엇인가요?
Qdrant의 Sparse Vector 기능을 사용하여 BM25 스타일의 키워드 검색과 Dense 벡터 검색을 결합합니다. 컬렉션에 dense와 sparse 벡터를 함께 설정하고, Prefetch로 각 검색 결과를 가져온 뒤 Reciprocal Rank Fusion(RRF) 등의 fusion 전략으로 결과를 병합합니다.
Q7: Qdrant 클러스터 모드에서 데이터 분산과 일관성은 어떻게 관리되나요?
클러스터 모드에서 데이터는 Shard 단위로 여러 노드에 분산 저장됩니다. 메타데이터 일관성은 Raft 합의 프로토콜로 유지하며, 각 샤드는 여러 노드에 복제본(replica)을 두어 고가용성을 보장합니다.
Q8: REST API 대신 gRPC를 사용하면 어떤 이점이 있으며, Python 클라이언트에서 어떻게 설정하나요?
gRPC는 Protocol Buffers 기반의 바이너리 직렬화를 사용하여 REST(JSON) 대비 2~3배 빠른 처리 성능을 보입니다. Python 클라이언트에서는 QdrantClient 생성 시 grpc_port=6334와 prefer_grpc=True 옵션을 설정하면 자동으로 gRPC를 사용합니다.
Complete Guide to Qdrant Vector DB Operations — From Collection Design to RAG Integration
- Introduction
- 1. Core Concepts
- 2. Architecture
- 3. Collection Design and Index Strategy
- 4. CRUD and Search
- 5. Filtering and Payloads
- 6. RAG Pipeline Integration
- 7. Operations Checklist
- 8. Common Mistakes
- 9. Summary
- Quiz
Introduction
As LLM-powered applications continue to proliferate, vector databases have become essential infrastructure rather than optional tooling. Among them, Qdrant stands out: written in Rust for high performance, supporting both gRPC and REST APIs, and offering production-grade features like payload filtering and multi-tenancy. This guide covers everything you need to deploy Qdrant in production — from collection design, CRUD operations, and filtering to RAG integration and operational monitoring.
1. Core Concepts
What Is a Vector Database?
A vector database stores high-dimensional vectors (embeddings) and performs similarity search in milliseconds. Text, images, and audio are transformed into vectors via embedding models, stored in the database, and then queried by finding the nearest vectors to a given query vector.
Key Features of Qdrant
- Rust-based: Achieves memory safety and high throughput simultaneously
- Payload: Store JSON metadata alongside vectors for filtering
- Multi-tenancy: Isolate tenant data within a single collection
- Quantization: Reduce memory usage by up to 4x (Scalar) or 32x (Binary)
- Distributed mode: Cluster support with Raft consensus protocol
Distance Metric Comparison
| Metric | Formula | Use Case | Range |
|---|---|---|---|
| Cosine | 1 - cos(a, b) | Text embeddings (OpenAI, Cohere) | 0 to 2 |
| Euclid | ||a - b|| | Image feature vectors, coordinate-based | 0 to +inf |
| Dot Product | -a . b | Normalized embeddings, recommendation systems | -inf to +inf |
| Manhattan | sum(|a_i - b_i|) | Sparse vectors, specialized domains | 0 to +inf |
Tip: OpenAI's
text-embedding-3-small/largereturns normalized vectors, so Cosine and Dot Product yield identical results. In this case, Dot Product is computationally faster.
2. Architecture
Key Components
- Segment: The physical unit that stores vectors and payloads separately. Acts as the basic unit for parallel search
- WAL (Write-Ahead Log): Ensures durability for write operations. Replayed during crash recovery
- HNSW Index: Approximate Nearest Neighbor index based on Hierarchical Navigable Small World graphs
- Payload Index: Secondary index for metadata filtering (keyword, integer, geo, etc.)
Cluster Mode
In cluster mode, data is distributed across Shards, and the Raft consensus protocol maintains metadata consistency. Each shard can have replicas across multiple nodes for high availability. To set up a cluster with Docker Compose, set the environment variable QDRANT__CLUSTER__ENABLED=true and configure P2P ports. From the second node onward, use the --bootstrap flag pointing to the first node.
3. Collection Design and Index Strategy
Creating a Collection
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, HnswConfigDiff, OptimizersConfigDiff
from qdrant_client.models import ScalarQuantization, ScalarQuantizationConfig, ScalarType
client = QdrantClient(host="localhost", port=6333)
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(
size=1536, # OpenAI text-embedding-3-small dimensions
distance=Distance.COSINE,
),
hnsw_config=HnswConfigDiff(
m=16, # Graph connections
ef_construct=128, # Search width during index build
),
optimizers_config=OptimizersConfigDiff(
indexing_threshold=20000,
),
quantization_config=ScalarQuantization(
scalar=ScalarQuantizationConfig(type=ScalarType.INT8, quantile=0.99, always_ram=True),
),
)
HNSW Parameter Guide
| Parameter | Default | Recommended Range | Description |
|---|---|---|---|
m | 16 | 8 to 64 | Graph connections per node. Higher improves recall but increases memory |
ef_construct | 100 | 64 to 512 | Index build quality. Higher is more accurate but slower |
ef (search) | 128 | 64 to 512 | Search-time width. Recall vs. latency trade-off |
Quantization Strategy
# Scalar Quantization: float32 -> int8 (4x memory reduction)
# Suitable for general text search, recall loss < 1%
# Binary Quantization: float32 -> 1-bit (32x memory reduction)
# Very aggressive. Only use with high dimensions (1536+). Requires oversampling
from qdrant_client.models import BinaryQuantization, BinaryQuantizationConfig
client.update_collection(
collection_name="documents",
quantization_config=BinaryQuantization(
binary=BinaryQuantizationConfig(
always_ram=True,
),
),
)
4. CRUD and Search
Inserting Vectors (Upsert)
from qdrant_client.models import PointStruct
import openai
def get_embedding(text: str) -> list[float]:
resp = openai.embeddings.create(model="text-embedding-3-small", input=text)
return resp.data[0].embedding
# Single upsert
client.upsert(
collection_name="documents",
points=[
PointStruct(
id=1,
vector=get_embedding("Qdrant is a vector DB written in Rust."),
payload={"title": "Qdrant Introduction", "category": "database", "tenant_id": "team-alpha"},
),
],
)
# Batch upsert — process in batches of 100
BATCH_SIZE = 100
for i in range(0, len(points), BATCH_SIZE):
client.upsert(collection_name="documents", points=points[i : i + BATCH_SIZE])
Similarity Search
from qdrant_client.models import SearchParams
results = client.search(
collection_name="documents",
query_vector=get_embedding("vector database performance optimization"),
limit=10,
score_threshold=0.7,
search_params=SearchParams(
hnsw_ef=128, # Search-time ef value (controls accuracy)
exact=False, # True for brute-force (accurate but slow)
),
with_payload=True,
with_vectors=False, # Don't return vectors (reduce response size)
)
for result in results:
print(f"ID: {result.id}, Score: {result.score:.4f}")
print(f" Title: {result.payload['title']}")
Search via REST API
curl -X POST "http://localhost:6333/collections/documents/points/search" \
-H "Content-Type: application/json" \
-d '{
"vector": [0.1, 0.2, ...],
"limit": 10,
"with_payload": true,
"params": {
"hnsw_ef": 128
}
}'
Update and Delete
from qdrant_client.models import PointIdsList, FilterSelector, Filter, FieldCondition, MatchValue
# Update payload
client.set_payload(collection_name="documents", payload={"category": "vector-database"}, points=[1, 2, 3])
# Delete by ID
client.delete(collection_name="documents", points_selector=PointIdsList(points=[10, 11, 12]))
# Delete by filter
client.delete(
collection_name="documents",
points_selector=FilterSelector(
filter=Filter(must=[FieldCondition(key="category", match=MatchValue(value="deprecated"))])
),
)
5. Filtering and Payloads
Creating Payload Indexes
To achieve good filtering performance, you must create payload indexes for filtered fields.
from qdrant_client.models import PayloadSchemaType, TextIndexParams, TokenizerType
# Keyword index (exact matching)
client.create_payload_index(collection_name="documents", field_name="category", field_schema=PayloadSchemaType.KEYWORD)
# Integer index (range queries)
client.create_payload_index(collection_name="documents", field_name="page_count", field_schema=PayloadSchemaType.INTEGER)
# Text index (full-text search with multilingual tokenizer)
client.create_payload_index(
collection_name="documents",
field_name="content",
field_schema=TextIndexParams(type="text", tokenizer=TokenizerType.MULTILINGUAL, min_token_len=2, max_token_len=20),
)
Compound Filter Search
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range
# Search for documents where category is "database" and page_count >= 10
results = client.search(
collection_name="documents",
query_vector=get_embedding("vector index optimization"),
query_filter=Filter(
must=[
FieldCondition(
key="category",
match=MatchValue(value="database"),
),
FieldCondition(
key="page_count",
range=Range(gte=10),
),
],
must_not=[
FieldCondition(
key="status",
match=MatchValue(value="archived"),
),
],
),
limit=5,
)
Hybrid Search (Vector + Text)
Qdrant supports Sparse Vectors to combine BM25-style keyword search with dense vector search.
from qdrant_client.models import SparseVector, SparseVectorParams, Prefetch, FusionQuery, Fusion
# Create collection with dense + sparse vector configuration
client.create_collection(
collection_name="hybrid_docs",
vectors_config={"dense": VectorParams(size=1536, distance=Distance.COSINE)},
sparse_vectors_config={"sparse": SparseVectorParams()},
)
# Hybrid search with Reciprocal Rank Fusion
results = client.query_points(
collection_name="hybrid_docs",
prefetch=[
Prefetch(query=get_embedding("vector search performance"), using="dense", limit=20),
Prefetch(query=SparseVector(indices=[1, 42, 1337], values=[0.9, 0.3, 0.7]), using="sparse", limit=20),
],
query=FusionQuery(fusion=Fusion.RRF),
limit=10,
)
6. RAG Pipeline Integration
LangChain Integration
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_qdrant import QdrantVectorStore
from langchain.chains import RetrievalQA
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. Split documents
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = text_splitter.split_documents(documents)
# 2. Create vector store
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = QdrantVectorStore.from_documents(
documents=chunks,
embedding=embeddings,
url="http://localhost:6333",
collection_name="rag_documents",
force_recreate=True,
)
# 3. Build RAG chain
retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={
"k": 5,
"score_threshold": 0.7,
},
)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
)
# 4. Query
response = qa_chain.invoke({"query": "How do I tune HNSW index parameters in Qdrant?"})
print(response["result"])
LlamaIndex Integration
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
import qdrant_client
# 1. Qdrant client & vector store
qclient = qdrant_client.QdrantClient(host="localhost", port=6333)
vector_store = QdrantVectorStore(
client=qclient,
collection_name="llamaindex_docs",
)
# 2. Configure settings
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.llm = OpenAI(model="gpt-4o", temperature=0)
Settings.chunk_size = 512
Settings.chunk_overlap = 50
# 3. Load and index documents
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(
documents,
vector_store=vector_store,
)
# 4. Query engine
query_engine = index.as_query_engine(
similarity_top_k=5,
)
response = query_engine.query("How can I combine filtering and vector search in Qdrant?")
print(response)
7. Operations Checklist
Monitoring
Qdrant exposes Prometheus-format metrics at the /metrics endpoint.
# Check metrics
curl http://localhost:6333/metrics
# Key metrics
# qdrant_grpc_responses_total - gRPC request count
# qdrant_rest_responses_total - REST request count
# qdrant_collection_points_total - Points per collection
# qdrant_collection_search_latency - Search latency
Operations Checklist Table
| Item | What to Check | Frequency |
|---|---|---|
| Memory | Keep RAM usage below 80% | Daily |
| Disk | Maintain at least 20% free storage | Daily |
| Search Latency | p99 latency < 100ms | Real-time |
| Index Status | optimizer_status = "ok" | Hourly |
| Snapshots | Verify automatic snapshots are created | Daily |
| Replicas | All shard replicas in active state | Hourly |
| WAL Size | Monitor WAL directory for abnormal growth | Daily |
| API Error Rate | 5xx response ratio < 0.1% | Real-time |
Snapshots and Backup
# Create and list snapshots
client.create_snapshot(collection_name="documents")
snapshots = client.list_snapshots(collection_name="documents")
for snap in snapshots:
print(f"Name: {snap.name}, Size: {snap.size}")
# Download snapshot via REST API
curl -o snapshot.tar "http://localhost:6333/collections/documents/snapshots/snapshot-2026-03-09.snapshot"
# Full storage snapshot (all collections)
curl -X POST "http://localhost:6333/snapshots"
8. Common Mistakes
Using filter search without payload indexes: Without payload indexes, Qdrant sequentially scans all points. Always create indexes for fields used in filters.
Setting HNSW
eftoo low: Lowering it below the default (128) causes a sharp drop in recall. Keep the default unless you have specific latency requirements.Vector dimension mismatch: If the collection's
sizedoesn't match the embedding model's dimensions, upsert operations will fail.text-embedding-3-smalloutputs 1536 dimensions;text-embedding-3-largeoutputs 3072.Batch size too large: Upserting more than 10,000 points at once can cause memory spikes. Process in batches of 100 to 500.
Skipping tests after applying quantization: Scalar Quantization has minimal recall loss, but Binary Quantization requires mandatory recall benchmarking before production deployment.
Storing all data in a single collection: Mixing data from different embedding models or dimensions in one collection degrades search quality. Separate collections by use case.
No snapshot backup configured: WAL alone cannot recover from disk failures. Store regular snapshots in external storage (S3, etc.).
Using only REST instead of gRPC: For bulk data operations, gRPC is 2-3x faster than REST. In the Python client, set
prefer_grpc=Trueto automatically use gRPC.
# Recommended: use gRPC
client = QdrantClient(
host="localhost",
port=6333,
grpc_port=6334,
prefer_grpc=True,
)
9. Summary
Qdrant combines Rust's performance with a rich feature set, making it particularly well-suited for production RAG pipelines thanks to payload filtering, quantization, and hybrid search capabilities. When designing collections, carefully choose HNSW parameters and quantization strategies, and make full use of payload indexes. For operations, always configure Prometheus metrics monitoring and regular snapshots, and plan your cluster mode and sharding strategy ahead of traffic growth.
Quiz
Q1: What programming language is Qdrant written in, and what advantages does this provide?
Qdrant is written in Rust. Rust guarantees memory safety without a garbage collector, enabling both high throughput and low latency simultaneously. This is particularly beneficial for large-scale vector search workloads.
Q2: What is the difference between Cosine and Dot Product distance metrics, and when should you choose Dot Product?
Cosine distance measures directional similarity between two vectors and normalizes for magnitude. Dot Product computes the inner product without normalization. When using pre-normalized vectors (e.g., OpenAI embeddings), both metrics produce identical results, so Dot Product is preferred because it is computationally faster.
Q3: What does the HNSW m parameter control, and what trade-offs occur when increasing it?
m parameter control, and what trade-offs occur when increasing it?The m parameter determines the number of connections (neighbors) per node in the HNSW graph. Increasing it improves search recall (accuracy) but increases memory consumption and index build time. It is typically set within the range of 8 to 64.
Q4: What is the difference between Scalar Quantization and Binary Quantization?
Scalar Quantization converts float32 to int8, reducing memory by approximately 4x with less than 1% recall loss, making it suitable for general text search. Binary Quantization converts float32 to 1-bit, reducing memory by 32x but with significant recall loss, requiring mandatory oversampling and benchmarking. It is recommended only for high-dimensional vectors (1536+).
Q5: What happens if you perform filtered searches without creating payload indexes?
Without payload indexes, Qdrant must sequentially scan the payload of every point to evaluate filter conditions. As data volume grows, search latency increases linearly, causing severe performance degradation in production environments.
Q6: How do you implement hybrid search (vector + keyword) in Qdrant?
Use Qdrant's Sparse Vector feature to combine BM25-style keyword search with dense vector search. Configure both dense and sparse vectors in the collection, use Prefetch to retrieve results from each search, then merge results using a fusion strategy like Reciprocal Rank Fusion (RRF).
Q7: How are data distribution and consistency managed in Qdrant's cluster mode?
In cluster mode, data is distributed across nodes at the Shard level. Metadata consistency is maintained through the Raft consensus protocol, and each shard can have replicas across multiple nodes to ensure high availability.
Q8: What are the benefits of using gRPC instead of REST, and how do you configure it in the Python client?
gRPC uses Protocol Buffers binary serialization, delivering 2-3x faster processing compared to REST (JSON). In the Python client, set grpc_port=6334 and prefer_grpc=True when creating the QdrantClient to automatically use gRPC.