현재 구축한 RAG 파이프라인의 결과물이 기대에 못 미쳐 고민하고 계신가요? 프롬프트를 튜닝하고, 청크(chunk) 크기를 조정하고, 심지어 임베딩 모델까지 교체해 보았지만 검색 품질은 거의 개선되지 않았을 수 있습니다. 그 이유는 LLM이나 임베딩 모델 자체의 문제가 아닐 수 있습니다. 본래 스택 내에서 가장 정교하게 동작해야 할 벡터 데이터베이스(Vector Database)를, 그저 단순한 키-값 저장소(Key-Value Store)처럼 다루고 있기 때문일 가능성이 큽니다.
Qdrant는 Rust로 작성된 오픈소스 벡터 검색 엔진입니다. 현재 시중에 나온 솔루션 중 가장 기능이 풍부한 검색 시스템(Retrieval System) 중 하나이지만, 대다수의 엔지니어링 팀은 Qdrant가 가진 역량의 30%도 채 활용하지 못하고 있습니다. 이 글은 프로덕션 벡터 검색을 극대화하기 위해 엔지니어가 반드시 알아야 할 완벽한 아키텍처 맵을 제시합니다.
데이터 모델: 단순한 벡터 그 이상
Qdrant의 가장 기본 단위는 **포인트(point)**입니다. 포인트는 벡터(Vector), 선택적(optional) JSON 페이로드(Payload), 그리고 고유 ID를 하나로 묶은 레코드입니다.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"vector": [0.1, 0.2, 0.3, 0.4],
"payload": {
"category": "regulatory",
"region": "EU",
"effective_date": "2026-01-15T00:00:00Z",
"tags": ["GxP", "AI", "validation"]
}
}
포인트들은 벡터 공간을 정의하는 명명된 세트인 **컬렉션(collection)**에 저장됩니다. 각 컬렉션은 거리 메트릭(Cosine, Dot Product, Euclidean, Manhattan)과 벡터 차원수(dimensionality)를 지정합니다. 하지만 여기서 진정으로 흥미로운 점은, 단일 포인트가 각기 다른 차원과 거리 메트릭을 가진 **여러 개의 명명된 벡터(multiple named vectors)**를 동시에 보유할 수 있다는 사실입니다.
이 구조는 이어지는 모든 고급 검색 기능의 근간이 됩니다.
┌─────────────────────────────────────────────────────────────┐
│ SINGLE POINT │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ "dense-embedding" Cosine, 1536-dim │ │
│ │ [0.012, -0.034, 0.056, ..., 0.078] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ "bm25-sparse" IDF-weighted │ │
│ │ indices: [112174620, 177304315, 662344706] │ │
│ │ values: [1.669, 1.669, 1.669] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ "colbert-tokens" Multi-vector, 128-dim × N │ │
│ │ [[0.1, 0.2, ...], [0.3, 0.4, ...], ...] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ PAYLOAD (JSON) │ │
│ │ { "category": "regulatory", "region": "EU" } │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Qdrant는 세 가지 유형의 벡터를 지원합니다:
| 유형 | 구조 | 주요 사용 사례 |
|---|---|---|
| Dense (밀집 벡터) | 고정 길이 부동소수점 배열 | 표준 신경망 임베딩 (OpenAI, Cohere, sentence-transformers) |
| Sparse (희소 벡터) | 인덱스/값 쌍, 가변 길이 | BM25 키워드 검색, SPLADE, 협업 필터링(Collaborative Filtering) |
| Multi-vector (다중 벡터) | 고정 너비 매트릭스, 가변 높이 | ColBERT 후기 상호작용(Late-Interaction) 모델 |
대부분의 팀은 밀집 벡터(Dense Vector)만을 사용합니다. 그러나 어휘 매칭(lexical matching)을 위한 희소 벡터(Sparse Vector)를 추가하는 것은, 대부분의 RAG 시스템에서 즉각적으로 가장 극적인 품질 향상을 이끌어낼 수 있는 단일 업그레이드입니다.
페이로드는 데이터베이스 내부의 또 다른 데이터베이스
Qdrant의 페이로드 시스템은 단순한 태그 달기용 부가 기능이 아닙니다. 다음과 같은 기능을 지원하는 완전한 구조화 데이터 레이어(structured data layer)입니다:
- 타입 세이프 인덱싱(Type-safe indexing): keyword, integer, float, bool, geo, datetime, UUID, text 필드 지원
- 불리언 필터링(Boolean filtering): 중첩된
must(AND),should(OR),must_not(NOT) 절 지원 - 지리 공간 쿼리(Geographic queries): 바운딩 박스(bounding box) 및 반경(radius) 기반 필터링
- 전문 검색(Full-text search): 커스텀 토큰화, 형태소 분석(stemming), 불용어 제거(stopword removal), ASCII 폴딩 지원
- 패싯 집계(Faceted aggregation): 고유값(distinct values) 카운팅
- 중첩 객체 필터링(Nested object filtering): 계층적 JSON 구조 탐색
페이로드의 데이터 타입은 적용 가능한 필터링 연산을 결정하기 때문에 매우 중요합니다. 문자열 필드에 범위(range) 필터를 걸거나 정수 필드에 지리(geo) 필터를 걸면 아무런 결과도 반환되지 않습니다. Qdrant는 타입을 임의로 추측하지 않고 엄격한 타입 규율을 강제합니다.
# 이 필터는 벡터 검색 범위를 2026년 1월 이후 발행되고,
# "AI" 태그가 지정된 EU 규제 문서로 좁힙니다.
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.query_points(
collection_name="documents",
query=[0.1, 0.45, 0.67, ...],
query_filter=models.Filter(
must=[
models.FieldCondition(
key="region",
match=models.MatchValue(value="EU"),
),
models.FieldCondition(
key="effective_date",
range=models.Range(gte="2026-01-01T00:00:00Z"),
),
models.FieldCondition(
key="tags",
match=models.MatchAny(any=["AI"]),
),
]
),
limit=10,
)
이 지점이 바로 벡터 데이터베이스와 단순 벡터 라이브러리가 갈라지는 경계입니다. FAISS는 최근접 이웃(nearest neighbors)만을 찾아주지만, Qdrant는 필터링되고, 타입이 지정되며, 인덱싱된 데이터 레이어 내부에서 최근접 이웃을 찾아냅니다.
하이브리드 검색: 대다수 팀이 놓치고 있는 핵심 아키텍처
정보 검색에서 가장 흔하게 저지르는 실수는 밀집 벡터(Dense Vector)에만 전적으로 의존하는 것입니다. 밀집 임베딩은 의미적 유사성(semantic similarity)을 포착하는 데 뛰어납니다. 하지만 정확한 키워드 일치(exact keyword matching)에는 취약합니다. 사용자가 “SOP-2024-0187”이나 “ICH Q9(R1)”, 혹은 특정 제품 코드를 검색하면, 밀집 벡터는 해당 검색어를 정확히 포함하지 않는 ‘의미상으로만 유사한’ 문서를 반환하곤 합니다.
하이브리드 검색(Hybrid Search)은 두 개의 병렬 검색 경로를 실행한 뒤 결과를 융합(fusion)함으로써 이 문제를 완벽히 해결합니다.
┌─────────────────────┐
│ QUERY: "ICH Q9" │
└─────────┬───────────┘
│
┌───────────────┴───────────────┐
│ │
┌─────────▼──────────┐ ┌─────────▼──────────┐
│ DENSE PATH │ │ SPARSE PATH │
│ Semantic Embedding│ │ BM25 Embedding │
│ → HNSW Search │ │ → Sparse Index │
│ → 100 candidates │ │ → 100 candidates │
└─────────┬──────────┘ └─────────┬──────────┘
│ │
└───────────────┬───────────────┘
│
┌─────────▼──────────┐
│ FUSION │
│ RRF or DBSF │
│ → Final Top 10 │
└────────────────────┘
Qdrant는 이를 **프리페치 API(prefetch API)**를 통해 구현합니다. 하이브리드 쿼리는 서로 다른 명명된 벡터를 대상으로 여러 프리페치 하위 쿼리를 정의하고, 최종적으로 결과를 융합합니다:
client.query_points(
collection_name="documents",
prefetch=[
models.Prefetch(
query=models.Document(
text="ICH Q9 risk-based approach",
model="sentence-transformers/all-minilm-l6-v2"
),
using="dense-embedding",
),
models.Prefetch(
query=models.Document(
text="ICH Q9 risk-based approach",
model="Qdrant/bm25",
),
using="bm25-sparse",
),
],
query=models.FusionQuery(fusion=models.Fusion.RRF),
limit=10,
)
두 가지 융합 알고리즘을 지원합니다:
| 방식 | 동작 원리 | 최적의 사용처 |
|---|---|---|
| RRF (Reciprocal Rank Fusion) | 복수의 결과 집합에서 상위권에 등장하는 항목에 가중치 부여. 점수 = Σ(1/(k + rank)) | 대부분의 일반적인 경우; 단순하고 강력하며 별도 튜닝 불필요 |
| DBSF (Distribution-Based Score Fusion) | 점수를 결합하기 전 통계적으로 점수 분포를 정규화 | 서로 다른 검색 소스 간의 점수 스케일(score scale) 차이가 극심할 때 |
BM25 모델은 Qdrant 클러스터 내부의 **서버 사이드(server-side)**에서 직접 실행됩니다. 즉, 어휘 검색을 위해 별도의 외부 임베딩 서비스를 둘 필요가 없습니다. model="Qdrant/bm25"로 지정하기만 하면 Qdrant가 내부적으로 희소 벡터를 자동 생성합니다.
다단계 검색: 저비용 1차 필터링 후 고비용 2차 재랭킹
대규모 컬렉션에서 매 쿼리마다 고차원 벡터 검색을 전체 데이터에 수행하는 것은 비용 소모가 큽니다. 다단계 검색(Multi-stage retrieval)은 프리페치 API를 활용하여 1차로 저비용 탐색을 수행한 뒤, 생존한 후보군에 대해서만 고비용 모델로 재랭킹(re-rank)을 수행합니다.
마트료시카(Matryoshka) 임베딩 모델 패턴이 대표적인 예시입니다:
client.query_points(
collection_name="documents",
prefetch=models.Prefetch(
query=models.Document(
text="GxP validation requirements",
model="openai/text-embedding-3-small",
options={"mrl": 64}, # 64차원 축소 벡터
),
using="small-embedding",
limit=1000, # 1단계 저비용 탐색: 1000개 후보 추출
),
query=models.Document(
text="GxP validation requirements",
model="openai/text-embedding-3-small",
# 1536차원 전체 벡터
),
using="full-embedding",
limit=10, # 2단계 고비용 재랭킹: 최종 10개 선별
)
1단계: 전체 컬렉션을 대상으로 64차원 벡터 검색을 수행합니다. 메모리 사용량이 적고 빠르며 넓은 재현율(recall)을 확보합니다. 2단계: 상위 1,000개 후보군을 대상으로 1536차원 전체 벡터를 사용해 정밀 재랭킹을 진행합니다. 좁고 정확하며 비용 효율적입니다.
Qdrant Cloud는 마트료시카 모델을 기본 지원합니다. 한 번의 인퍼런스 요청으로 전체 벡터를 생성하고, Qdrant가 요청된 차원에 맞춰 자동으로 슬라이싱(truncate)하므로 불필요한 추가 API 호출이 발생하지 않습니다.
스코어 부스팅: 비즈니스 로직의 주입
실제 검색 환경에서는 벡터 유사도만이 유일한 판단 기준이 아닙니다. 2026년에 작성된 문서가 2019년 문서보다 관련성이 높을 수 있습니다. 본문 일치보다는 제목 일치에 더 높은 가치를 두어야 하며, 권위 있는 공식 출처가 일반 블로그 게시물보다 우선해야 합니다.
포뮬러 쿼리(Formula Query, v1.14+)를 활용하면 커스텀 수식을 작성하여 검색 점수를 재계산할 수 있습니다:
client.query_points(
collection_name="documents",
prefetch=models.Prefetch(
query=[0.1, 0.45, 0.67],
limit=50
),
query=models.FormulaQuery(
formula=models.SumExpression(sum=[
"$score",
models.MultExpression(mult=[
0.5,
models.FieldCondition(
key="doc_type",
match=models.MatchAny(any=["title", "heading"])
)
]),
models.MultExpression(mult=[
0.3,
models.FieldCondition(
key="authority_score",
range=models.Range(gte=0.8)
)
]),
])
)
)
이 수식은 기본 벡터 유사도 점수($score)를 가져와, 문서가 제목이나 헤딩이면 0.5점을 더하고 권위도 점수(authority_score)가 0.8 이상이면 0.3점을 추가합니다. 조건식은 참일 때 1.0, 거짓일 때 0.0으로 평가되므로, 결과적으로 score + 0.5 * is_title + 0.3 * is_authoritative 형태로 계산됩니다.
사용 가능한 표현식: $score, 페이로드 키, 상수값, sum, mult, div, abs, pow, sqrt, neg, log1p, sigmoid 및 중첩 조건절.
추천 및 탐색 API
Qdrant는 표준 유사도 검색을 넘어 두 가지 강력한 탐색형 API를 제공합니다.
**추천 API(Recommendation API)**는 긍정(positive) 및 부정(negative) 예시를 입력받아, 긍정 예시와는 유사하면서 부정 예시와는 거리가 먼 항목을 찾습니다. 두 가지 전략이 있습니다:
average_vector(기본값): 긍정 벡터들과 부정 벡터들을 하나의 쿼리 벡터로 평균화합니다. 수식:avg_positive + avg_positive - avg_negative. 일반 검색과 동일한 비용으로 매우 빠르게 동작합니다.best_score: 각 후보를 개별 예시들과 하나씩 대조하여 점수를 매긴 뒤, 후보별 최적 매칭 점수를 취합니다. 더 정교하지만 연산 비용이 높습니다.
**디스커버리 검색(Discovery Search)**은 컨텍스트 쌍(긍정, 부정)을 원샷(one-shot) 학습 세트로 활용합니다. 주어진 컨텍스트로부터 벡터 공간 내의 탐색 방향을 도출한 뒤, 해당 방향 축을 따라 검색을 진행합니다. 사용자가 “이것과 비슷한 건 더 많이, 저것과 비슷한 건 제외”와 같은 피드백을 제공하는 대화형 인터랙티브 탐색에 매우 유용합니다.
필터링: 쿼리 플래너의 내부 동작 메커니즘
벡터 검색과 페이로드 필터를 결합할 때, Qdrant의 쿼리 플래너(Query Planner)는 중요한 아키텍처적 결정을 내립니다. 즉, 벡터 인덱스를 먼저 검색한 뒤 결과를 필터링할 것인가(포스트 필터링, post-filter), 아니면 필터를 먼저 적용하여 조건을 만족하는 서브셋 내에서만 벡터 검색을 수행할 것인가(프리 필터링, pre-filter)를 결정합니다.
이 판단은 페이로드 인덱스 통계를 기반으로 한 **필터 카디널리티 추정(filter cardinality estimation)**에 의해 이루어집니다. 전체 포인트 중 1%만 매칭되는 필터라면 당연히 프리 필터링을 타야 합니다. 반면 99%가 매칭된다면 포스트 필터링이 훨씬 효율적입니다. 페이로드 인덱스는 바로 이러한 최적 경로 결정을 유도하는 카디널리티 통계를 제공합니다.
이것이 데이터 수집(ingestion) 이전에 페이로드 인덱스를 생성해야 하는 이유입니다. 인덱스가 없으면 Qdrant는 카디널리티를 정확히 추정할 수 없어 차선의 비효율적인 전략으로 폴백(fallback)하게 됩니다.
# 데이터 upsert 전에 페이로드 인덱스 먼저 생성
client.create_payload_index(
collection_name="documents",
field_name="region",
field_schema=models.KeywordIndexParams(
type=models.KeywordIndexType.KEYWORD,
is_tenant=True, # 테넌트별 벡터를 디스크 상에 물리적으로 인접 배치
),
)
여기서 is_tenant=True 플래그는 단순한 메타데이터 라벨이 아닙니다. Qdrant에게 동일한 테넌트의 벡터들을 디스크 상에 물리적으로 나란히(co-locate) 저장하도록 지시하여, 임의 탐색(random seek) 대신 연속 순차 읽기(sequential read)를 가능하게 만듭니다. 멀티테넌트 환경에서 이는 쿼리 처리량(throughput)을 5배에서 10배까지 끌어올리는 결정적 차이를 만듭니다.
양자화: 정밀도와 속도의 트레이드오프
Qdrant는 압축률과 재현율(recall) 간의 다양한 트레이드오프를 제공하는 네 가지 양자화 기법을 지원합니다:
| 기법 | 압축률 | 재현율 | 속도 | 최적의 사용처 |
|---|---|---|---|---|
| Scalar (스칼라) | 4배 | 높음 | 보통 | 범용 목적; 안정성과 검증성이 중요한 환경 |
| TurboQuant 4-bit | 8배 | 높음 | 빠름 | 최고의 재현율 대비 압축률 비율 |
| TurboQuant 2-bit | 16배 | 중간 | 매우 빠름 | 메모리가 극도로 제한된 대규모 컬렉션 |
| Binary (이진) | 16–32배 | 중간 | 가장 빠름 | 1,000차원 이상의 고차원 임베딩 |
| Product (PQ, 프로덕트) | 최대 64배 | 다소 낮음 | 보통 | 메모리 압축이 최우선 순위일 때 |
TurboQuant(v1.18+, Google 개발)는 압축을 적용하기 전에 벡터에 빠른 무작위 회전(fast random rotation)을 적용하는 혁신적인 기법입니다. 4비트 환경에서 스칼라 양자화 대비 2배의 압축률을 달성하면서도 동등한 수준의 재현율을 유지합니다. 2비트 이하 극단적 압축 환경에서도 바이너리 양자화와 동등한 속도를 내면서 재현율은 훨씬 우수합니다.
양자화는 컬렉션 전체에 적용할 수도 있고, 벡터별로 개별 적용할 수도 있습니다. always_ram 파라미터를 설정하면 원본 벡터가 디스크에 있더라도 양자화된 벡터는 항상 RAM에 상주하도록 유지할 수 있습니다. rescore 파라미터를 사용하면 양자화된 벡터로 빠르게 후보군을 추린 뒤, 원본 벡터를 사용해 최종 정밀 재랭킹을 수행할 수 있습니다.
Qdrant Edge: 벡터 검색을 위한 SQLite
Qdrant Edge는 생태계 전체에서 가장 과소평가된 기능 중 하나입니다. 별도의 서버 프로세스, 백그라운드 서비스, 네트워크 호출 없이 애플리케이션 프로세스 내부에서 직접 구동되는 임베디드(embedded) 벡터 검색 엔진입니다.
벡터 유사도 검색을 위한 SQLite라고 이해하시면 됩니다.
┌──────────────────────────────────────────────────────┐
│ EDGE DEVICE │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ APPLICATION PROCESS │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ QDRANT EDGE SHARD │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │
│ │ │ │ Vector Store │ │ Payload Store │ │ │ │
│ │ │ └──────────────┘ └──────────────────┘ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │
│ │ │ │ HNSW Index │ │ Payload Index │ │ │ │
│ │ │ └──────────────┘ └──────────────────┘ │ │ │
│ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │
│ │ │ │ BM25 Engine │ │ WAL │ │ │ │
│ │ │ └──────────────┘ └──────────────────┘ │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ FastEmbed (local models, no network) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ Optional: ──────── Sync ──────────► Qdrant Server │
└──────────────────────────────────────────────────────┘
Python 바인딩(qdrant-edge-py)과 Rust 크레이트(qdrant-edge)로 제공되는 Qdrant Edge는 다음과 같은 기능을 지원합니다:
- 모든 Qdrant 양자화 기법 지원 (Scalar, Product, Binary, TurboQuant)
- 스키마 에볼루션(Schema evolution) — 실행 중인 샤드에서 명명된 벡터를 동적으로 추가/제거 가능
- 완전한 페이로드 인덱싱 및 필터링
- 디바이스 내 키워드 검색을 위한 내장 BM25 임베더 (인터넷 연결 불필요)
- 중앙 Qdrant 서버와의 스냅샷 기반 동기화
로컬 쓰기 작업과 서버 동기화 데이터가 모두 필요한 엣지 디바이스에서는 **이중 샤드 동기화 패턴(dual-shard sync pattern)**이 권장 아키텍처입니다:
- 가변 엣지 샤드(Mutable Edge Shard) — 로컬 디바이스의 데이터 변경 사항을 처리
- 불변 엣지 샤드(Immutable Edge Shard) — 부분 스냅샷(partial snapshot)을 통해 서버 데이터를 미러링
- 병합 쿼리(Merged queries) — 두 샤드를 동시에 검색하고 결과를 결합
이를 통해 전체 지식 베이스와 동기화하면서 자체 로컬 인식 인덱스를 구축하는 로봇 시스템이나, 클라우드로부터 카탈로그 업데이트를 수신하면서 로컬에서 개인화 검색을 수행하는 키오스크 시스템 등을 구축할 수 있습니다.
FastEmbed를 활용한 온디바이스 임베딩
Qdrant Edge와 FastEmbed를 결합하면 외부 네트워크 의존성 없이 완전한 오프라인 환경에서 임베딩 생성부터 검색까지 완결되는 온디바이스 파이프라인을 구축할 수 있습니다.
프로비저닝 워크플로우:
- 디바이스에
fastembed및qdrant-edge-py설치 - 임베딩 모델을 로컬 캐시 디렉터리에 다운로드 (텍스트 및 이미지용 CLIP 기반 모델 등)
- 올바른 벡터 차원을 지정하여 엣지 샤드(Edge Shard) 생성
- 로컬에서 임베딩을 생성하고 포인트를 upsert
from fastembed import ImageEmbedding, TextEmbedding
# 최초 1회 다운로드 후 영구적 오프라인 사용
text_model = TextEmbedding(
model_name="Qdrant/clip-ViT-B-32-text",
cache_dir="./models",
local_files_only=True
)
image_model = ImageEmbedding(
model_name="Qdrant/clip-ViT-B-32-vision",
cache_dir="./models",
local_files_only=True
)
엣지의 BM25 임베더는 서버 사이드 BM25와 완벽히 호환됩니다. 동일한 토큰 ID와 동일한 채점 수식을 사용하므로, 서버 스냅샷으로부터 엣지 샤드를 초기화한 후 재인덱싱 없이도 로컬에서 생성한 BM25 벡터로 바로 질의할 수 있습니다.
인퍼런스 파이프라인: 별도의 임베딩 스택이 필요 없는 구조
Qdrant의 인퍼런스 API를 사용하면 상당수 배포 환경에서 별도의 외부 임베딩 서비스를 구축할 필요가 없어집니다. 네트워크 지연 시간이 가장 짧은 내부 방식부터 외부 프로바이더까지 네 가지 옵션을 제공합니다:
| 옵션 | 실행 위치 | 지연 시간 | 지원 모델 |
|---|---|---|---|
| Qdrant 클러스터 BM25 | Qdrant 클러스터 내부 | 가장 낮음 | BM25 희소 벡터 |
| Qdrant Cloud 인퍼런스 | Qdrant 매니지드 인프라 | 낮음 (쿼리 시 클러스터 내부 처리) | 지속 확대 중, 일부 무료 제공 |
| 외부 프로바이더 (Qdrant Cloud 연동) | OpenAI, Cohere, Jina, OpenRouter | 일반 API 레이턴시 | 전체 프로바이더 카탈로그 |
| 클라이언트 사이드 (FastEmbed) | 로컬 머신 | 로컬 GPU/CPU 기반 | FastEmbed 모델 카탈로그 |
핵심 포인트는 단일 요청 안에서 여러 인퍼런스 소스를 유연하게 조합할 수 있다는 점입니다. 한 번의 upsert 요청에서 이미지 임베딩은 Jina AI를 통해, 텍스트 임베딩은 Qdrant Cloud를 통해, BM25 임베딩은 로컬 클러스터를 통해 동시에 생성하는 작업을 단일 API 호출로 끝낼 수 있습니다.
client.upsert(
collection_name="documents",
points=[
models.PointStruct(
id=1,
vector={
"image": models.Document(
image="<url>",
model="jina-clip-v2"
),
"text": models.Document(
text="Document description",
model="all-minilm-l6-v2"
),
"bm25": models.Document(
text="Document description",
model="Qdrant/bm25"
),
},
)
],
)
스토리지 아키텍처: 세그먼트와 메모리 계층
Qdrant는 컬렉션 데이터를 자체적인 벡터 저장소, 페이로드 저장소, 인덱스 및 ID 매퍼를 갖춘 독립적인 단위인 **세그먼트(segment)**로 분할합니다.
두 가지 세그먼트 유형이 존재합니다:
- Appendable (추가 가능 세그먼트) — 전체 읽기/쓰기/삭제 지원 (가변 데이터용)
- Non-appendable (추가 불가 세그먼트) — 읽기/삭제 전용 (컴팩션(compaction) 이후 최적화된 불변 세그먼트)
벡터 스토리지는 메모리 맵 파일(mmap)을 사용하며, 두 가지 티어(tier)로 동작합니다:
| 티어 | 동작 방식 | 권장 환경 |
|---|---|---|
cached (기본값) |
시작 시 mmap을 디스크 캐시로 사전 로드(pre-load) | 벡터 전체를 수용할 RAM이 충분한 경우; 첫 번째 쿼리부터 빠른 응답 필요 시 |
cold |
사전 로드 없음; OS가 접근 시 페이지를 캐싱 | 고속 디스크를 장착한 대규모 컬렉션; 첫 번째 쿼리의 약간의 지연을 허용할 수 있는 경우 |
컬렉션 크기가 RAM 용량을 초과하는 대규모 환경에서는 HNSW 인덱스에 on_disk=true를 설정하여 그래프 구조 자체도 디스크에 저장하도록 구성할 수 있습니다.
프로덕션 운영 패턴
벌크 업로드 최적화
| 기법 | 기대 효과 |
|---|---|
| 요청당 64–256개 포인트 단위로 배치 처리 | 요청별 오버헤드 최소화 |
| 2–4개의 병렬 업로드 스레드 활용 | 쓰기 처리 용량(capacity) 극대화 |
| 컬렉션당 2–4개 샤드(shard) 할당 | 쓰기 워커 간 병렬 분산 처리 |
| 페이로드 인덱스를 데이터 수집 전에 사전 생성 | 데이터 인입 도중 HNSW 링크를 점진 빌드하여 사후 오버헤드 방지 |
대용량 데이터셋에서 벡터를 on_disk/cold 티어로 설정 |
대규모 수집 시 OOM(Out of Memory) 발생 방지 |
멀티테넌시: 단일 컬렉션, 다중 테넌트
# 1단계: 테넌트 인덱스 생성
client.create_payload_index(
collection_name="shared",
field_name="tenant_id",
field_schema=models.KeywordIndexParams(
type=models.KeywordIndexType.KEYWORD,
is_tenant=True,
),
)
# 2단계: 모든 쿼리에 테넌트 필터 포함
client.query_points(
collection_name="shared",
query=[0.1, 0.2, 0.3],
query_filter=models.Filter(
must=[models.FieldCondition(
key="tenant_id",
match=models.MatchValue(value="tenant_42"),
)]
),
)
is_tenant=true 옵션을 적용한 단일 컬렉션 아키텍처는 거의 모든 시나리오에서 테넌트별 개별 컬렉션을 분리하는 방식보다 우수한 성능을 보입니다. 디스크 상에 테넌트 데이터가 물리적으로 인접 배치되어 순차 디스크 읽기가 가능해지고 운영체제 페이지 캐시 활용률이 극대화되기 때문입니다.
초저지연을 위한 체크리스트
- 필터링 대상이 되는 모든 필드에 페이로드 인덱스 사전 구축
- 읽기 트래픽 분산을 위한 수평 복제본(horizontal replicas) 구성
- 느린 복제본 응답 지연을 완화하기 위한 지연 팬아웃(Delayed fan-out, v1.17+) 적용
- 메모리 제약 환경에서 재랭킹(rescoring)을 동반한 양자화(Quantization) 도입
- 대규모 컬렉션에 마트료시카(Matryoshka) 기반 다단계 검색 적용
- 조기 종료(early termination)를 유도하기 위한
score_threshold설정 - 인덱싱되지 않은 필드에 대한 쿼리 차단 (API 게이트웨이 레벨에서 거부)
핵심 요약
Qdrant는 단순한 HTTP API 래퍼가 씌워진 벡터 스토어가 아닙니다. 다음과 같은 기능을 아우르는 고성능 엔터프라이즈 검색 엔진입니다:
- 하이브리드 검색: RRF/DBSF 융합을 통해 밀집(dense), 희소(sparse), 후기 상호작용(late-interaction) 벡터를 결합
- 구조화 데이터 레이어: 타입 인덱스, 불리언 필터링, 지리 쿼리 및 전문 검색(Full-text search) 완벽 지원
- 커스텀 스코어링: Formula Query를 통해 유사도 랭킹에 비즈니스 로직을 직접 주입
- 엣지 배포: 온디바이스 BM25 및 FastEmbed가 내장된 임베디드 검색 엔진 제공
- 내장 인퍼런스: BM25, Qdrant Cloud 호스팅 모델 및 외부 API 프로바이더를 단일 파이프라인으로 통합
- 양자화: 4배 스칼라부터 64배 프로덕트(PQ)까지 지원하며, 구글의 TurboQuant를 통해 품질 손실 없이 극단적 압축 달성
만약 지금까지 Qdrant를 단순히 원시 벡터를 넘겨 client.search()만 호출하는 평범한 최근접 이웃 인덱스로 사용해 오셨다면, 데이터베이스가 가진 잠재 가치의 대부분을 방치하고 계셨던 것입니다. 지금 바로 하이브리드 검색을 도입해 보세요. 페이로드 필터링을 결합하고, 비즈니스 가중치 스코어 부스팅을 적용하며, 필요하다면 엣지 샤드를 배포해 보시기 바랍니다. 이를 통해 얻게 되는 검색 품질 향상은 단순히 임베딩 모델을 바꾸는 것과는 비교할 수 없을 만큼 압도적일 것입니다.
연구 노트: [[Qdrant Vector Database - Comprehensive User Manual Report]]
Saram Consulting