Qdrant
Qdrant 技術概要 — 高性能ベクトルデータベースの全容
目次
- はじめに — Qdrantとは何か
- アーキテクチャ概要
- インストールとセットアップ
- コレクション、ポイント、ベクトル、ペイロード
- 検索機能
- インデクシング戦略とパフォーマンスチューニング
- スパースベクトルとマルチベクトル対応
- スナップショットとバックアップ管理
- 分散デプロイメントとクラスタリング
- REST API と gRPC インターフェース
- クライアントライブラリ
- フレームワーク統合
- Qdrant Cloud マネージドサービス
- 実践的な RAG 実装例
- パフォーマンスベンチマークと他VectorDBとの比較
- 本番環境のベストプラクティス
1. はじめに — Qdrantとは何か
1.1 Qdrantの概要
Qdrant(クアドラント)は、Rust言語で開発された高性能なベクトル類似検索エンジンおよびベクトルデータベースである。2021年にAndrey Vasnetsovによって創設され、オープンソース(Apache 2.0ライセンス)で提供されている。名称はドイツ語の "Quadrant"(象限)に由来し、高次元空間におけるデータ探索を象徴している。
ベクトルデータベースは、テキスト、画像、音声などの非構造化データを高次元ベクトル(エンベディング)として格納し、類似性に基づく高速な検索を実現するデータベースシステムである。大規模言語モデル(LLM)の普及に伴い、RAG(Retrieval-Augmented Generation)パイプラインの中核コンポーネントとしてベクトルデータベースの重要性が急速に高まっている。
Qdrantは以下の特徴により、ベクトルデータベース市場において独自のポジションを確立している:
- Rust実装による高パフォーマンス: メモリ安全性と高速な処理を両立
- 豊富なフィルタリング機能: ペイロード(メタデータ)に基づく高度なフィルタリング
- 量子化サポート: スカラー量子化、プロダクト量子化、バイナリ量子化による省メモリ化
- 分散アーキテクチャ: 水平スケーリングと高可用性の実現
- マルチテナンシー: 単一コレクション内での効率的なテナント分離
- スパースベクトル対応: ハイブリッド検索の実現
- グループ検索: ドキュメント単位の検索結果集約
1.2 ベクトルデータベース市場における位置づけ
2024年から2026年にかけて、ベクトルデータベース市場は急速に成長している。主要なプレイヤーとしては以下が挙げられる:
| データベース | 開発言語 | ライセンス | 特徴 |
|---|---|---|---|
| Qdrant | Rust | Apache 2.0 | 高性能、豊富なフィルタリング |
| Pinecone | — | Proprietary | フルマネージド、簡易性重視 |
| Weaviate | Go | BSD-3 | GraphQL対応、モジュラー設計 |
| Milvus | Go/C++ | Apache 2.0 | 大規模データ対応 |
| ChromaDB | Python | Apache 2.0 | 軽量、開発者フレンドリー |
| pgvector | C | PostgreSQL | PostgreSQL拡張 |
Qdrantは、Rustによる実装がもたらすパフォーマンス優位性、そして充実したフィルタリング・検索機能により、特にエンタープライズ向けの本番ワークロードにおいて強い競争力を持っている。
1.3 ユースケース
Qdrantの主要なユースケースは多岐にわたる:
- RAG(Retrieval-Augmented Generation): LLMの回答精度を向上させるための知識ベース検索
- セマンティック検索: 意味的な類似性に基づく文書検索
- レコメンデーションシステム: ユーザーの嗜好ベクトルに基づく商品推薦
- 画像検索: 画像エンベディングを用いた類似画像検索
- 異常検知: 正常パターンからの逸脱を検出
- 重複検出: 類似ドキュメントやデータの重複を特定
- チャットボット/対話型AI: 文脈に応じた応答生成のための知識検索
2. アーキテクチャ概要
2.1 全体アーキテクチャ
Qdrantのアーキテクチャは、高スループットと低レイテンシを実現するために設計されている。以下の主要コンポーネントで構成される:
┌─────────────────────────────────────────────────┐
│ Client Layer │
│ (REST API / gRPC / Client SDKs) │
├─────────────────────────────────────────────────┤
│ Query Engine │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐│
│ │ Search │ │ Filter │ │ Aggregation ││
│ │ Planner │ │ Engine │ │ Engine ││
│ └──────────┘ └──────────┘ └──────────────┘│
├─────────────────────────────────────────────────┤
│ Index Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐│
│ │ HNSW │ │ Payload │ │ Sparse ││
│ │ Index │ │ Index │ │ Index ││
│ └──────────┘ └──────────┘ └──────────────┘│
├─────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐│
│ │ Vector │ │ Payload │ │ WAL ││
│ │ Storage │ │ Storage │ │ (Write-Ahead ││
│ │ │ │ │ │ Log) ││
│ └──────────┘ └──────────┘ └──────────────┘│
├─────────────────────────────────────────────────┤
│ Consensus Layer (Raft) │
│ (Distributed Mode / Cluster Management) │
└─────────────────────────────────────────────────┘
2.2 ストレージエンジン
Qdrantのストレージエンジンは、ベクトルデータとペイロードデータを効率的に管理する。主に2つのストレージモードを提供する:
In-Memory ストレージ
ベクトルデータをすべてRAMに格納するモードである。最高のパフォーマンスが得られるが、利用可能なメモリ量に制限される。
{
"vectors": {
"size": 768,
"distance": "Cosine",
"on_disk": false
}
}
On-Disk ストレージ(メモリマップ)
ベクトルデータをディスクに格納し、メモリマップ(mmap)を使用してアクセスする。大規模データセットに対してメモリ効率が良い。
{
"vectors": {
"size": 768,
"distance": "Cosine",
"on_disk": true
}
}
セグメントアーキテクチャ
Qdrantは内部的にデータを「セグメント」と呼ばれる単位で管理する。各セグメントは独立したインデックスとストレージを持ち、以下の利点がある:
- 並行書き込みと読み込み: セグメント単位でのロック制御により、読み取りをブロックせずに書き込みが可能
- バックグラウンドでのインデックス構築: 新しいセグメントのインデックスは非同期で構築される
- セグメントマージ: 小さなセグメントは自動的にマージされ、検索パフォーマンスが最適化される
WAL(Write-Ahead Log)メカニズムにより、書き込み操作の耐久性が保証される。すべての変更はまずWALに記録され、その後ストレージに反映される。
2.3 HNSW(Hierarchical Navigable Small World)インデックス
QdrantのANN(近似最近傍)検索のコアとなるのがHNSWインデックスである。HNSWは階層的なグラフ構造を構築し、高次元空間での近似最近傍検索を効率的に行う。
HNSWの仕組み
HNSWは複数のレイヤーで構成されるグラフ構造である:
Layer 3: A ──── B (最も疎なレイヤー)
│
Layer 2: A ──── B ──── C
│ │
Layer 1: A ──── B ──── C ──── D ──── E
│ │ │ │
Layer 0: A ─ B ─ C ─ D ─ E ─ F ─ G ─ H (最も密なレイヤー)
- 最上層:少数のノードで粗い検索を行う
- 下層に行くほど:ノード数が増え、より精密な検索が可能
- 検索時:最上層から開始し、各層で最も近いノードを辿りながら下層に進む
HNSWパラメータ
{
"hnsw_config": {
"m": 16,
"ef_construct": 100,
"full_scan_threshold": 10000,
"max_indexing_threads": 0,
"on_disk": false
}
}
| パラメータ | デフォルト値 | 説明 |
|---|---|---|
m | 16 | 各ノードの最大接続数。大きいほど精度が向上するがメモリ使用量が増加 |
ef_construct | 100 | インデックス構築時の探索幅。大きいほど精度が向上するが構築速度が低下 |
full_scan_threshold | 10000 | この数以下のベクトル数ではHNSWの代わりに全探索を使用 |
max_indexing_threads | 0 | インデクシングに使用するスレッド数(0はCPU数に自動設定) |
on_disk | false | HNSWグラフをディスクに保存するか |
2.4 量子化(Quantization)
Qdrantは3つの量子化手法をサポートしている。量子化により、ベクトルのメモリフットプリントを大幅に削減しつつ、検索精度への影響を最小限に抑えることができる。
スカラー量子化(Scalar Quantization)
32bit浮動小数点を8bit整数に変換する最もシンプルな量子化手法。メモリ使用量を約4分の1に削減する。
{
"quantization_config": {
"scalar": {
"type": "int8",
"quantile": 0.99,
"always_ram": true
}
}
}
プロダクト量子化(Product Quantization)
ベクトルを複数のサブベクトルに分割し、各サブベクトルを個別に量子化する。より高い圧縮率が得られるが、精度低下も大きくなる可能性がある。
{
"quantization_config": {
"product": {
"compression": "x16",
"always_ram": true
}
}
}
圧縮率のオプション:x4、x8、x16、x32、x64
バイナリ量子化(Binary Quantization)
ベクトルの各次元を1bitに変換する最も極端な量子化手法。メモリ使用量を32分の1に削減するが、精度低下が最も大きい。OpenAIのtext-embedding-3-largeなど、高次元エンベディングモデルとの組み合わせで有効な場合がある。
{
"quantization_config": {
"binary": {
"always_ram": true
}
}
}
量子化パフォーマンス比較
| 量子化方式 | メモリ削減率 | 検索速度向上 | 精度影響 |
|---|---|---|---|
| スカラー(int8) | ~75% | ~2x | 小 |
| プロダクト(x16) | ~94% | ~4x | 中 |
| バイナリ | ~97% | ~10x以上 | 大 |
量子化使用時のベストプラクティスとして、rescore パラメータを使用して元のベクトルでの再スコアリングを行うことで精度を回復できる:
{
"search_params": {
"quantization": {
"rescore": true,
"oversampling": 2.0
}
}
}
3. インストールとセットアップ
3.1 Docker によるインストール
最も簡単にQdrantを起動する方法はDockerを使用することである。
基本的な起動
docker pull qdrant/qdrant:latest
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage:z \
qdrant/qdrant
- ポート6333: REST API
- ポート6334: gRPC API
Docker Compose による設定
version: '3.8'
services:
qdrant:
image: qdrant/qdrant:latest
restart: always
container_name: qdrant
ports:
- "6333:6333"
- "6334:6334"
volumes:
- ./qdrant_storage:/qdrant/storage
- ./qdrant_config:/qdrant/config
environment:
- QDRANT__SERVICE__GRPC_PORT=6334
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__LOG_LEVEL=INFO
カスタム設定ファイル
config/config.yaml を作成し、詳細な設定を行う:
log_level: INFO
storage:
storage_path: ./storage
snapshots_path: ./snapshots
# WAL設定
wal:
wal_capacity_mb: 32
wal_segments_ahead: 0
# パフォーマンス設定
performance:
max_search_threads: 0 # 0 = auto
max_optimization_threads: 1
# オプティマイザ設定
optimizers:
deleted_threshold: 0.2
vacuum_min_vector_number: 1000
default_segment_number: 0
max_segment_size_kb: 0
memmap_threshold_kb: 0
indexing_threshold_kb: 20000
flush_interval_sec: 5
max_optimization_threads: 1
# HNSWデフォルト設定
hnsw_index:
m: 16
ef_construct: 100
full_scan_threshold: 10000
max_indexing_threads: 0
on_disk: false
service:
max_request_size_mb: 256
host: 0.0.0.0
http_port: 6333
grpc_port: 6334
enable_cors: true
# APIキー認証
api_key: "your-secret-api-key"
read_only_api_key: "your-read-only-key"
# TLS設定
# enable_tls: true
# tls:
# cert: ./tls/cert.pem
# key: ./tls/key.pem
3.2 バイナリによるインストール
Linux/macOS
# ダウンロードとインストール
curl -sSL https://github.com/qdrant/qdrant/releases/latest/download/qdrant-$(uname -m)-unknown-linux-gnu.tar.gz | tar xz
# 起動
./qdrant --config-path config/config.yaml
ソースからのビルド
# Rust のインストール(未インストールの場合)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# リポジトリのクローンとビルド
git clone https://github.com/qdrant/qdrant.git
cd qdrant
cargo build --release
# 起動
./target/release/qdrant --config-path config/config.yaml
3.3 Qdrant Cloud
Qdrant Cloudは、フルマネージドのQdrantサービスである。インフラストラクチャの管理を不要にし、即座にベクトル検索機能を利用できる。
from qdrant_client import QdrantClient
# Qdrant Cloud への接続
client = QdrantClient(
url="https://your-cluster-id.us-east4-0.gcp.cloud.qdrant.io",
api_key="your-api-key",
)
詳細はセクション13で説明する。
3.4 組み込みモード(Embedded Mode)
Qdrantはインメモリモードで直接Pythonプロセス内に組み込むことができる。テストやプロトタイピングに最適である。
from qdrant_client import QdrantClient
# インメモリモード(データは揮発性)
client = QdrantClient(":memory:")
# ディスク永続化モード
client = QdrantClient(path="./local_qdrant_storage")
組み込みモードは、REST/gRPC サーバーを起動せずにQdrantの全機能を利用できるため、以下のシナリオで特に有用である:
- 単体テストとCI/CDパイプライン
- ローカル開発とプロトタイピング
- 小規模データセットでのPoCプロジェクト
- エッジデバイスでの組み込み利用
4. コレクション、ポイント、ベクトル、ペイロード
4.1 コレクション(Collections)
コレクションはQdrantにおけるデータの最上位の組織単位であり、RDBMSにおけるテーブルに相当する。各コレクションはベクトルの設定(次元数、距離メトリクス)を定義し、ポイント(データレコード)を格納する。
コレクションの作成
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
client = QdrantClient("localhost", port=6333)
# 基本的なコレクション作成
client.create_collection(
collection_name="my_collection",
vectors_config=VectorParams(
size=768,
distance=Distance.COSINE
),
)
距離メトリクス
| メトリクス | 説明 | 用途 |
|---|---|---|
Cosine | コサイン類似度 | テキストエンベディング(最も一般的) |
Euclid | ユークリッド距離 | 画像特徴量、地理データ |
Dot | 内積 | 正規化済みベクトル、推薦システム |
Manhattan | マンハッタン距離 | 特定の距離計算要件 |
名前付きベクトル(Named Vectors)
一つのコレクション内で複数の種類のベクトルを格納できる:
from qdrant_client.models import VectorParams, Distance
client.create_collection(
collection_name="multimodal_collection",
vectors_config={
"text": VectorParams(size=768, distance=Distance.COSINE),
"image": VectorParams(size=512, distance=Distance.EUCLID),
"text_sparse": VectorParams(
size=30000,
distance=Distance.DOT,
multivector_config=None,
),
},
)
コレクション設定のカスタマイズ
from qdrant_client.models import (
VectorParams, Distance, HnswConfigDiff,
OptimizersConfigDiff, WalConfigDiff
)
client.create_collection(
collection_name="optimized_collection",
vectors_config=VectorParams(
size=768,
distance=Distance.COSINE,
on_disk=True,
),
hnsw_config=HnswConfigDiff(
m=32,
ef_construct=200,
full_scan_threshold=20000,
),
optimizers_config=OptimizersConfigDiff(
indexing_threshold=50000,
memmap_threshold=100000,
),
wal_config=WalConfigDiff(
wal_capacity_mb=64,
),
shard_number=3,
replication_factor=2,
)
4.2 ポイント(Points)
ポイントはQdrantにおけるデータの基本単位であり、RDBMSの行に相当する。各ポイントは以下の要素で構成される:
- ID: 一意の識別子(符号なし整数またはUUID)
- ベクトル: 数値配列(エンベディング)
- ペイロード: メタデータ(JSON形式)
ポイントの挿入(Upsert)
from qdrant_client.models import PointStruct
# 単一ポイントの挿入
client.upsert(
collection_name="my_collection",
points=[
PointStruct(
id=1,
vector=[0.1, 0.2, 0.3, ...], # 768次元ベクトル
payload={
"title": "Qdrant入門",
"category": "技術",
"author": "山田太郎",
"created_at": "2025-01-15T10:30:00Z",
"tags": ["vector-db", "search", "AI"],
"price": 2500,
}
),
PointStruct(
id=2,
vector=[0.4, 0.5, 0.6, ...],
payload={
"title": "機械学習実践ガイド",
"category": "技術",
"author": "佐藤花子",
"created_at": "2025-02-20T14:00:00Z",
"tags": ["ML", "deep-learning"],
"price": 3200,
}
),
]
)
バッチ挿入
大量データの挿入にはバッチ操作が効率的である:
import numpy as np
# 10000個のポイントをバッチで挿入
batch_size = 100
vectors = np.random.rand(10000, 768).tolist()
for i in range(0, len(vectors), batch_size):
batch = vectors[i:i+batch_size]
points = [
PointStruct(
id=i + j,
vector=vec,
payload={"batch_id": i // batch_size}
)
for j, vec in enumerate(batch)
]
client.upsert(
collection_name="my_collection",
points=points
)
UUID IDの使用
import uuid
point = PointStruct(
id=str(uuid.uuid4()), # "550e8400-e29b-41d4-a716-446655440000"
vector=[0.1, 0.2, ...],
payload={"source": "document_1"}
)
4.3 ベクトル(Vectors)
ベクトルは浮動小数点数の配列であり、データのセマンティックな表現である。Qdrantは以下のベクトルタイプをサポートする:
密ベクトル(Dense Vectors)
最も一般的なベクトル形式。テキストエンベディングモデル(OpenAI、Cohere、sentence-transformersなど)の出力がこれに該当する。
スパースベクトル(Sparse Vectors)
BM25やSPLADEなどのモデルが生成する、大部分の値がゼロのベクトル。キーワードベースの検索に使用される。詳細はセクション7で説明する。
マルチベクトル(Multi-Vectors)
ColBERTスタイルの検索のために、1つのポイントに複数のベクトルを格納する。トークンレベルのインタラクション検索が可能になる。
4.4 ペイロード(Payloads)
ペイロードはポイントに付随するJSON形式のメタデータである。フィルタリング、ソート、グルーピングなどに使用される。
サポートされるデータ型
| データ型 | 説明 | 例 |
|---|---|---|
keyword | 完全一致の文字列 | "status": "active" |
integer | 整数値 | "count": 42 |
float | 浮動小数点数 | "score": 0.95 |
bool | 真偽値 | "is_active": true |
text | 全文検索用テキスト | "description": "..." |
geo | 地理座標 | {"lat": 35.68, "lon": 139.77} |
datetime | 日時 | "2025-01-15T10:30:00Z" |
uuid | UUID | "id": "550e8400-..." |
ペイロードインデックス
フィルタリングの高速化のため、ペイロードフィールドにインデックスを作成できる:
from qdrant_client.models import PayloadSchemaType
# キーワードインデックス
client.create_payload_index(
collection_name="my_collection",
field_name="category",
field_schema=PayloadSchemaType.KEYWORD,
)
# 整数インデックス
client.create_payload_index(
collection_name="my_collection",
field_name="price",
field_schema=PayloadSchemaType.INTEGER,
)
# テキストインデックス(全文検索用)
from qdrant_client.models import TextIndexParams, TokenizerType
client.create_payload_index(
collection_name="my_collection",
field_name="description",
field_schema=TextIndexParams(
type="text",
tokenizer=TokenizerType.WORD,
min_token_len=2,
max_token_len=20,
lowercase=True,
),
)
# 地理インデックス
client.create_payload_index(
collection_name="my_collection",
field_name="location",
field_schema=PayloadSchemaType.GEO,
)
# 日時インデックス
client.create_payload_index(
collection_name="my_collection",
field_name="created_at",
field_schema=PayloadSchemaType.DATETIME,
)
5. 検索機能
Qdrantは多彩な検索機能を提供しており、単純なベクトル類似検索から、フィルタリングを組み合わせたハイブリッド検索まで対応している。
5.1 基本的な類似検索
from qdrant_client.models import Filter, FieldCondition, MatchValue
# 最もシンプルな検索
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, 0.3, ...], # クエリベクトル
limit=10,
)
for point in results.points:
print(f"ID: {point.id}, Score: {point.score}")
print(f"Payload: {point.payload}")
検索パラメータの調整
from qdrant_client.models import SearchParams
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, 0.3, ...],
search_params=SearchParams(
hnsw_ef=128, # 検索精度(デフォルトは ef_construct の値)
exact=False, # True にすると完全探索(遅いが正確)
),
limit=10,
with_payload=True,
with_vectors=False,
score_threshold=0.7, # スコア閾値
)
5.2 フィルタリング検索
Qdrantの最大の強みの一つが、ベクトル検索とペイロードフィルタリングを組み合わせた検索である。フィルタはインデックスを使用して効率的に処理される。
基本的なフィルタ
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range
# 単一条件フィルタ
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
query_filter=Filter(
must=[
FieldCondition(
key="category",
match=MatchValue(value="技術"),
)
]
),
limit=10,
)
複合フィルタ
from qdrant_client.models import (
Filter, FieldCondition, MatchValue, MatchAny,
Range, DatetimeRange, GeoRadius, GeoPoint,
HasIdCondition, IsEmptyCondition, IsNullCondition
)
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
query_filter=Filter(
must=[
# カテゴリが「技術」
FieldCondition(
key="category",
match=MatchValue(value="技術"),
),
# 価格が1000以上5000以下
FieldCondition(
key="price",
range=Range(gte=1000, lte=5000),
),
],
should=[
# タグに「AI」または「ML」を含む(OR条件)
FieldCondition(
key="tags",
match=MatchAny(any=["AI", "ML"]),
),
],
must_not=[
# ステータスが「draft」でない
FieldCondition(
key="status",
match=MatchValue(value="draft"),
),
],
),
limit=10,
)
フィルタ条件の種類
| 条件 | 説明 | 使用例 |
|---|---|---|
must | すべての条件を満たす(AND) | カテゴリかつ価格範囲 |
should | いずれかの条件を満たす(OR) | 複数タグのいずれか |
must_not | 条件を満たさない(NOT) | 特定ステータスを除外 |
min_should | should条件のうち最低N個を満たす | 2つ以上のタグに一致 |
フィルタオペレーション一覧
# 完全一致
FieldCondition(key="status", match=MatchValue(value="active"))
# 複数値のいずれかに一致
FieldCondition(key="category", match=MatchAny(any=["tech", "science"]))
# 複数値のいずれにも不一致
FieldCondition(key="category", match=MatchExcept(except_=["spam", "test"]))
# 数値範囲
FieldCondition(key="price", range=Range(gte=100, lte=500))
# 日時範囲
FieldCondition(
key="created_at",
range=DatetimeRange(
gte="2025-01-01T00:00:00Z",
lte="2025-12-31T23:59:59Z",
)
)
# 地理的範囲(半径検索)
FieldCondition(
key="location",
geo_radius=GeoRadius(
center=GeoPoint(lat=35.6812, lon=139.7671),
radius=5000, # メートル
)
)
# 地理的範囲(バウンディングボックス)
FieldCondition(
key="location",
geo_bounding_box=GeoBoundingBox(
top_left=GeoPoint(lat=35.8, lon=139.5),
bottom_right=GeoPoint(lat=35.5, lon=139.9),
)
)
# テキスト全文検索
FieldCondition(
key="description",
match=MatchText(text="ベクトルデータベース"),
)
# フィールドの存在チェック
IsEmptyCondition(is_empty=PayloadField(key="optional_field"))
IsNullCondition(is_null=PayloadField(key="nullable_field"))
# IDフィルタ
HasIdCondition(has_id=[1, 2, 3, 4, 5])
# ネストフィルタ
FieldCondition(
key="metadata.nested.field",
match=MatchValue(value="nested_value"),
)
5.3 ハイブリッド検索
密ベクトルとスパースベクトルを組み合わせたハイブリッド検索は、セマンティック理解とキーワードマッチングの両方を活用する強力な手法である。
from qdrant_client.models import (
SparseVector, NamedSparseVector,
Prefetch, FusionQuery, Fusion
)
# ハイブリッド検索(密 + スパース)
results = client.query_points(
collection_name="hybrid_collection",
prefetch=[
Prefetch(
query=[0.1, 0.2, ...], # 密ベクトルクエリ
using="text_dense",
limit=50,
),
Prefetch(
query=SparseVector(
indices=[1, 42, 100, 5000],
values=[0.5, 0.3, 0.8, 0.1],
),
using="text_sparse",
limit=50,
),
],
query=FusionQuery(fusion=Fusion.RRF), # Reciprocal Rank Fusion
limit=10,
)
5.4 レコメンデーション検索
既存のポイントIDを正例・負例として使用し、類似するポイントを検索する:
results = client.query_points(
collection_name="my_collection",
query=RecommendQuery(
recommend=RecommendInput(
positive=[1, 42, 100], # 正例(これに類似)
negative=[5, 20], # 負例(これとは異なる)
strategy="average_vector", # or "best_score"
)
),
limit=10,
)
5.5 ディスカバリー検索
コンテキストペアを使用して、検索空間を制約しながら探索する高度な検索手法:
results = client.query_points(
collection_name="my_collection",
query=DiscoverQuery(
discover=DiscoverInput(
target=42, # ターゲットポイントID
context=[
ContextPair(positive=1, negative=2),
ContextPair(positive=3, negative=4),
],
)
),
limit=10,
)
5.6 スクロール(Scroll)とページネーション
大量のデータを順次取得する場合はスクロールAPIを使用する:
# オフセットベースのスクロール
offset = None
all_points = []
while True:
result = client.scroll(
collection_name="my_collection",
scroll_filter=Filter(
must=[
FieldCondition(
key="category",
match=MatchValue(value="技術"),
)
]
),
limit=100,
offset=offset,
with_payload=True,
with_vectors=False,
)
points, next_offset = result
all_points.extend(points)
if next_offset is None:
break
offset = next_offset
print(f"Total points: {len(all_points)}")
5.7 グループ検索(Search Groups)
同一ドキュメントに属するチャンクをグループ化して検索結果を返す:
results = client.query_points_groups(
collection_name="my_collection",
query=[0.1, 0.2, ...],
group_by="document_id",
group_size=3, # 各グループから最大3件
limit=5, # グループ数の上限
with_payload=True,
)
for group in results.groups:
print(f"Document: {group.id}")
for hit in group.hits:
print(f" Score: {hit.score}, Payload: {hit.payload}")
5.8 カウントAPI
条件に一致するポイント数を取得する:
count = client.count(
collection_name="my_collection",
count_filter=Filter(
must=[
FieldCondition(
key="category",
match=MatchValue(value="技術"),
)
]
),
exact=True, # 正確なカウント(遅い場合がある)
)
print(f"Count: {count.count}")
6. インデクシング戦略とパフォーマンスチューニング
6.1 インデクシング概要
Qdrantでは、ベクトルインデックスとペイロードインデックスの2種類のインデックスが存在する。適切なインデックス戦略はパフォーマンスに大きな影響を与える。
6.2 HNSWインデックスのチューニング
m パラメータ
グラフの接続密度を制御する。値が大きいほど検索精度が向上するが、メモリ使用量とインデックス構築時間が増加する。
| m の値 | メモリ使用量 | 検索精度 | 構築速度 | 推奨シナリオ |
|---|---|---|---|---|
| 8 | 低 | 低〜中 | 高速 | メモリ制限環境 |
| 16 | 中(デフォルト) | 中〜高 | 中速 | 一般的な用途 |
| 32 | 高 | 高 | 低速 | 高精度要求 |
| 64 | 非常に高 | 非常に高 | 非常に低速 | 最高精度要求 |
ef_construct パラメータ
インデックス構築時の探索幅。構築品質に影響する。
# 高精度インデックスの設定
client.update_collection(
collection_name="my_collection",
hnsw_config=HnswConfigDiff(
m=32,
ef_construct=200,
),
)
hnsw_ef(検索時パラメータ)
検索時の探索幅。リクエストごとに指定でき、精度と速度のトレードオフを制御する:
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
search_params=SearchParams(
hnsw_ef=256, # デフォルトより大きくすると精度向上
),
limit=10,
)
6.3 オプティマイザ設定
Qdrantのオプティマイザは、バックグラウンドでセグメントの最適化を行う:
from qdrant_client.models import OptimizersConfigDiff
client.update_collection(
collection_name="my_collection",
optimizer_config=OptimizersConfigDiff(
# 削除率がこの閾値を超えるとバキュームが実行される
deleted_threshold=0.2,
# バキュームを実行する最小ベクトル数
vacuum_min_vector_number=1000,
# セグメント数の目標(0 = 自動)
default_segment_number=0,
# セグメントの最大サイズ(KB)
max_segment_size_kb=0,
# メモリマップに切り替える閾値(KB)
memmap_threshold_kb=0,
# HNSWインデックスを構築する閾値(KB)
indexing_threshold_kb=20000,
# ディスクへのフラッシュ間隔(秒)
flush_interval_sec=5,
# 最適化に使用するスレッド数
max_optimization_threads=1,
),
)
6.4 パフォーマンスチューニングのベストプラクティス
大量データの一括インポート
# 1. オプティマイザを一時的に無効化
client.update_collection(
collection_name="my_collection",
optimizer_config=OptimizersConfigDiff(
indexing_threshold=0, # インデクシングを無効化
),
)
# 2. データの一括挿入
for batch in data_batches:
client.upsert(
collection_name="my_collection",
points=batch,
)
# 3. オプティマイザを再有効化
client.update_collection(
collection_name="my_collection",
optimizer_config=OptimizersConfigDiff(
indexing_threshold=20000, # デフォルトに戻す
),
)
# 4. インデクシング完了を待機
while True:
info = client.get_collection("my_collection")
if info.optimizer_status == "ok":
break
time.sleep(1)
メモリとディスクの最適化
# 大規模データセット向け:ベクトルとHNSWインデックスの両方をディスクに
client.create_collection(
collection_name="large_collection",
vectors_config=VectorParams(
size=768,
distance=Distance.COSINE,
on_disk=True, # ベクトルをディスクに
),
hnsw_config=HnswConfigDiff(
on_disk=True, # HNSWグラフをディスクに
),
# 量子化でメモリ内検索の高速化
quantization_config=ScalarQuantization(
scalar=ScalarQuantizationConfig(
type=ScalarType.INT8,
always_ram=True, # 量子化ベクトルはRAMに保持
),
),
)
パフォーマンスプロファイル
| シナリオ | ベクトルストレージ | HNSW | 量子化 | 特徴 |
|---|---|---|---|---|
| 最高速度 | RAM | RAM | なし | 最大メモリ使用量 |
| バランス | RAM | RAM | スカラー | 良好な速度と省メモリ |
| 大規模データ | ディスク | RAM | スカラー(RAM) | 多数のベクトルに対応 |
| 最省メモリ | ディスク | ディスク | プロダクト | 最低速度 |
6.5 マルチテナンシー
単一コレクション内でテナントを分離するアプローチ:
# テナントIDでペイロードインデックスを作成
client.create_payload_index(
collection_name="multi_tenant",
field_name="tenant_id",
field_schema=PayloadSchemaType.KEYWORD,
)
# テナント別の検索
results = client.query_points(
collection_name="multi_tenant",
query=[0.1, 0.2, ...],
query_filter=Filter(
must=[
FieldCondition(
key="tenant_id",
match=MatchValue(value="tenant_001"),
)
]
),
limit=10,
)
マルチテナンシーにおけるベストプラクティス:
- ペイロードインデックスの作成:テナントIDフィールドにkeywordインデックスを必ず作成する
- シャーディング最適化:テナントごとのデータ分布を考慮してシャード数を設定する
- コレクション分離 vs 共有コレクション:テナント数が少ない場合はコレクションを分ける、多数のテナントがいる場合は共有コレクションを使用する
7. スパースベクトルとマルチベクトル対応
7.1 スパースベクトル
スパースベクトルは、キーワードベースの検索をベクトル形式で表現したものである。BM25やSPLADEなどのモデルにより生成され、ほとんどの次元がゼロであるベクトルを効率的に格納する。
スパースベクトル対応コレクションの作成
from qdrant_client.models import (
SparseVectorParams, SparseIndexParams
)
client.create_collection(
collection_name="hybrid_collection",
vectors_config={
"dense": VectorParams(size=768, distance=Distance.COSINE),
},
sparse_vectors_config={
"sparse": SparseVectorParams(
index=SparseIndexParams(
on_disk=False,
),
),
},
)
スパースベクトルの挿入
from qdrant_client.models import SparseVector, PointStruct
client.upsert(
collection_name="hybrid_collection",
points=[
PointStruct(
id=1,
vector={
"dense": [0.1, 0.2, ...], # 密ベクトル
"sparse": SparseVector(
indices=[10, 50, 100, 372, 5000],
values=[0.5, 0.3, 0.8, 0.1, 0.9],
),
},
payload={"text": "Qdrant is a vector database"},
),
],
)
スパースベクトルの検索
results = client.query_points(
collection_name="hybrid_collection",
query=SparseVector(
indices=[10, 50, 372],
values=[0.5, 0.3, 0.8],
),
using="sparse",
limit=10,
)
SPLADE を使用したスパースベクトル生成
from transformers import AutoTokenizer, AutoModelForMaskedLM
import torch
# SPLADEモデルのロード
tokenizer = AutoTokenizer.from_pretrained("naver/splade-cocondenser-ensembledistil")
model = AutoModelForMaskedLM.from_pretrained("naver/splade-cocondenser-ensembledistil")
def encode_sparse(text):
tokens = tokenizer(text, return_tensors="pt", padding=True, truncation=True)
with torch.no_grad():
output = model(**tokens)
# スパース表現を取得
logits = output.logits
relu_log = torch.log1p(torch.relu(logits))
weighted = relu_log.max(dim=1).values.squeeze()
# 非ゼロインデックスと値を抽出
indices = weighted.nonzero().squeeze().tolist()
values = weighted[indices].tolist()
if isinstance(indices, int):
indices = [indices]
values = [values]
return SparseVector(indices=indices, values=values)
# 使用例
sparse_query = encode_sparse("vector database performance")
7.2 マルチベクトル対応
ColBERTスタイルの遅延インタラクション検索のため、マルチベクトルがサポートされている。
マルチベクトルコレクションの作成
from qdrant_client.models import MultiVectorConfig, MultiVectorComparator
client.create_collection(
collection_name="colbert_collection",
vectors_config={
"colbert": VectorParams(
size=128,
distance=Distance.COSINE,
multivector_config=MultiVectorConfig(
comparator=MultiVectorComparator.MAX_SIM,
),
),
},
)
マルチベクトルの挿入と検索
# マルチベクトルの挿入(各トークンのベクトルを格納)
client.upsert(
collection_name="colbert_collection",
points=[
PointStruct(
id=1,
vector={
"colbert": [
[0.1, 0.2, ...], # トークン1のベクトル
[0.3, 0.4, ...], # トークン2のベクトル
[0.5, 0.6, ...], # トークン3のベクトル
],
},
payload={"text": "example document"},
),
],
)
# マルチベクトル検索
results = client.query_points(
collection_name="colbert_collection",
query=[
[0.1, 0.2, ...], # クエリトークン1
[0.3, 0.4, ...], # クエリトークン2
],
using="colbert",
limit=10,
)
7.3 ハイブリッド検索の実装パターン
密ベクトルとスパースベクトルを組み合わせたハイブリッド検索は、以下のパターンで実装できる:
パターン1: RRF(Reciprocal Rank Fusion)
from qdrant_client.models import Prefetch, FusionQuery, Fusion
results = client.query_points(
collection_name="hybrid_collection",
prefetch=[
# 密ベクトル検索
Prefetch(
query=[0.1, 0.2, ...],
using="dense",
limit=50,
),
# スパースベクトル検索
Prefetch(
query=SparseVector(
indices=[10, 50, 100],
values=[0.5, 0.3, 0.8],
),
using="sparse",
limit=50,
),
],
query=FusionQuery(fusion=Fusion.RRF),
limit=10,
)
パターン2: Distribution-Based Score Fusion (DBSF)
results = client.query_points(
collection_name="hybrid_collection",
prefetch=[
Prefetch(
query=[0.1, 0.2, ...],
using="dense",
limit=50,
),
Prefetch(
query=SparseVector(
indices=[10, 50, 100],
values=[0.5, 0.3, 0.8],
),
using="sparse",
limit=50,
),
],
query=FusionQuery(fusion=Fusion.DBSF),
limit=10,
)
パターン3: リスコアリングによるリランキング
# Stage 1: 各ベクトル空間で候補を取得
# Stage 2: クロスエンコーダでリランキング
results = client.query_points(
collection_name="hybrid_collection",
prefetch=[
Prefetch(
query=[0.1, 0.2, ...],
using="dense",
limit=100,
),
],
# マルチベクトルでリスコアリング
query=[
[0.1, 0.2, ...],
[0.3, 0.4, ...],
],
using="colbert",
limit=10,
)
8. スナップショットとバックアップ管理
8.1 スナップショット機能
Qdrantはコレクション単位およびサーバー全体のスナップショットを作成できる。スナップショットは.snapshotファイルとして保存される。
コレクションスナップショットの作成
# スナップショットの作成
snapshot_info = client.create_snapshot(
collection_name="my_collection"
)
print(f"Snapshot created: {snapshot_info.name}")
REST API経由:
# スナップショットの作成
curl -X POST "http://localhost:6333/collections/my_collection/snapshots"
# スナップショット一覧の取得
curl "http://localhost:6333/collections/my_collection/snapshots"
# スナップショットのダウンロード
curl -o snapshot.tar \
"http://localhost:6333/collections/my_collection/snapshots/snapshot_name.snapshot"
サーバー全体のスナップショット
# 全コレクションのスナップショット作成
curl -X POST "http://localhost:6333/snapshots"
# スナップショット一覧
curl "http://localhost:6333/snapshots"
スナップショットからの復元
# スナップショットからコレクションを復元
curl -X PUT "http://localhost:6333/collections/my_collection/snapshots/recover" \
-H "Content-Type: application/json" \
-d '{
"location": "http://backup-server/snapshots/my_collection_snapshot.snapshot"
}'
# Pythonクライアントでの復元
client.recover_snapshot(
collection_name="my_collection",
location="file:///path/to/snapshot.snapshot",
)
8.2 バックアップ戦略
定期バックアップスクリプト
#!/bin/bash
# backup_qdrant.sh
QDRANT_URL="http://localhost:6333"
BACKUP_DIR="/backups/qdrant"
DATE=$(date +%Y%m%d_%H%M%S)
# ディレクトリ作成
mkdir -p "${BACKUP_DIR}/${DATE}"
# 全コレクション名の取得
COLLECTIONS=$(curl -s "${QDRANT_URL}/collections" | jq -r '.result.collections[].name')
# 各コレクションのスナップショットを作成・ダウンロード
for collection in $COLLECTIONS; do
echo "Backing up collection: ${collection}"
# スナップショット作成
SNAPSHOT=$(curl -s -X POST \
"${QDRANT_URL}/collections/${collection}/snapshots" | \
jq -r '.result.name')
# スナップショットダウンロード
curl -s -o "${BACKUP_DIR}/${DATE}/${collection}.snapshot" \
"${QDRANT_URL}/collections/${collection}/snapshots/${SNAPSHOT}"
echo " Saved: ${BACKUP_DIR}/${DATE}/${collection}.snapshot"
done
# 古いバックアップの削除(30日以上前)
find "${BACKUP_DIR}" -type d -mtime +30 -exec rm -rf {} +
echo "Backup completed: ${DATE}"
S3へのバックアップ
#!/bin/bash
# スナップショットをS3にアップロード
aws s3 sync "/backups/qdrant/" "s3://my-bucket/qdrant-backups/" \
--storage-class STANDARD_IA
8.3 ストレージディレクトリのバックアップ
スナップショット以外に、ストレージディレクトリ全体をバックアップする方法もある:
# Qdrantを停止してからバックアップ
docker stop qdrant
tar czf qdrant_storage_backup.tar.gz ./qdrant_storage/
docker start qdrant
注意:稼働中のQdrantのストレージディレクトリを直接コピーすることは推奨されない。データの整合性が保証されないためである。
9. 分散デプロイメントとクラスタリング
9.1 分散アーキテクチャ概要
Qdrantは水平スケーリングと高可用性のための分散デプロイメントをサポートする。クラスターモードでは、Raftコンセンサスアルゴリズムを使用してメタデータの一貫性を維持する。
┌─────────────────────────────────────────────────────┐
│ Load Balancer │
├──────────────┬──────────────┬──────────────┬────────┤
│ Node 1 │ Node 2 │ Node 3 │ ... │
│ ┌──────────┐ │ ┌──────────┐ │ ┌──────────┐ │ │
│ │ Shard 1 │ │ │ Shard 1 │ │ │ Shard 2 │ │ │
│ │ (primary)│ │ │ (replica)│ │ │ (primary)│ │ │
│ ├──────────┤ │ ├──────────┤ │ ├──────────┤ │ │
│ │ Shard 3 │ │ │ Shard 2 │ │ │ Shard 3 │ │ │
│ │ (replica)│ │ │ (replica)│ │ │ (replica)│ │ │
│ └──────────┘ │ └──────────┘ │ └──────────┘ │ │
│ Raft │ Raft │ Raft │ │
└──────────────┴──────────────┴──────────────┴────────┘
9.2 クラスターの構築
Docker Compose による3ノードクラスター
version: '3.8'
services:
qdrant-node1:
image: qdrant/qdrant:latest
container_name: qdrant-node1
ports:
- "6333:6333"
- "6334:6334"
- "6335:6335"
volumes:
- ./node1_storage:/qdrant/storage
environment:
- QDRANT__CLUSTER__ENABLED=true
- QDRANT__CLUSTER__P2P__PORT=6335
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
qdrant-node2:
image: qdrant/qdrant:latest
container_name: qdrant-node2
ports:
- "6343:6333"
- "6344:6334"
- "6345:6335"
volumes:
- ./node2_storage:/qdrant/storage
environment:
- QDRANT__CLUSTER__ENABLED=true
- QDRANT__CLUSTER__P2P__PORT=6335
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
command: ./qdrant --bootstrap "http://qdrant-node1:6335"
qdrant-node3:
image: qdrant/qdrant:latest
container_name: qdrant-node3
ports:
- "6353:6333"
- "6354:6334"
- "6355:6335"
volumes:
- ./node3_storage:/qdrant/storage
environment:
- QDRANT__CLUSTER__ENABLED=true
- QDRANT__CLUSTER__P2P__PORT=6335
- QDRANT__SERVICE__HTTP_PORT=6333
- QDRANT__SERVICE__GRPC_PORT=6334
command: ./qdrant --bootstrap "http://qdrant-node1:6335"
9.3 シャーディングとレプリケーション
シャーディング
コレクション作成時にシャード数を指定する:
client.create_collection(
collection_name="distributed_collection",
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
shard_number=6, # シャード数
replication_factor=2, # レプリケーション数
write_consistency_factor=1, # 書き込み一貫性(最小応答ノード数)
)
カスタムシャーディング
特定のシャードにデータを割り当てる:
# シャードキーの作成
client.create_shard_key(
collection_name="my_collection",
shard_key="region_us",
)
# シャードキーを指定してデータを挿入
client.upsert(
collection_name="my_collection",
points=[...],
shard_key_selector="region_us",
)
# シャードキーを指定して検索
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
shard_key_selector="region_us",
limit=10,
)
9.4 レプリケーション
レプリケーションにより、ノード障害時のデータ可用性を確保する:
# レプリケーションファクターの更新
client.update_collection(
collection_name="my_collection",
replication_factor=3,
)
読み取り一貫性レベル
from qdrant_client.models import ReadConsistency, ReadConsistencyType
# 全レプリカからの読み取り(最高一貫性)
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
consistency=ReadConsistency(
type=ReadConsistencyType.ALL,
),
limit=10,
)
# 過半数からの読み取り
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
consistency=ReadConsistency(
type=ReadConsistencyType.MAJORITY,
),
limit=10,
)
# 任意の1ノードからの読み取り(最高速度)
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
consistency=ReadConsistency(
type=ReadConsistencyType.QUORUM,
),
limit=10,
)
9.5 クラスター管理
# クラスター状態の確認
curl "http://localhost:6333/cluster"
# ピア情報の取得
curl "http://localhost:6333/cluster/peer"
# コレクションのシャード情報
curl "http://localhost:6333/collections/my_collection/cluster"
9.6 Kubernetes でのデプロイ
Helm chartを使用したKubernetesへのデプロイ:
# Qdrant Helm リポジトリの追加
helm repo add qdrant https://qdrant.github.io/qdrant-helm
helm repo update
# デプロイ
helm install qdrant qdrant/qdrant \
--set replicaCount=3 \
--set persistence.size=50Gi \
--set resources.requests.memory=4Gi \
--set resources.requests.cpu=2 \
--set resources.limits.memory=8Gi \
--set resources.limits.cpu=4
values.yaml の例
replicaCount: 3
image:
repository: qdrant/qdrant
tag: latest
pullPolicy: IfNotPresent
persistence:
enabled: true
size: 100Gi
storageClass: "gp3"
resources:
requests:
memory: "4Gi"
cpu: "2"
limits:
memory: "8Gi"
cpu: "4"
service:
type: ClusterIP
ports:
http: 6333
grpc: 6334
p2p: 6335
config:
cluster:
enabled: true
storage:
optimizers:
default_segment_number: 2
hnsw_index:
m: 16
ef_construct: 100
apiKey: "" # 本番環境では必ず設定
tolerations: []
affinity: {}
nodeSelector: {}
10. REST API と gRPC インターフェース
10.1 REST API
QdrantはOpenAPI仕様に準拠したREST APIを提供する。デフォルトではポート6333で提供される。
主要エンドポイント
| カテゴリ | メソッド | エンドポイント | 説明 |
|---|---|---|---|
| コレクション | GET | /collections | コレクション一覧 |
| コレクション | PUT | /collections/{name} | コレクション作成 |
| コレクション | DELETE | /collections/{name} | コレクション削除 |
| コレクション | PATCH | /collections/{name} | コレクション更新 |
| コレクション | GET | /collections/{name} | コレクション情報 |
| ポイント | PUT | /collections/{name}/points | ポイントupsert |
| ポイント | POST | /collections/{name}/points/search | ベクトル検索 |
| ポイント | POST | /collections/{name}/points/query | クエリ検索 |
| ポイント | POST | /collections/{name}/points/scroll | スクロール |
| ポイント | POST | /collections/{name}/points/count | カウント |
| ポイント | POST | /collections/{name}/points/delete | ポイント削除 |
| スナップショット | POST | /collections/{name}/snapshots | スナップショット作成 |
| クラスター | GET | /cluster | クラスター情報 |
| ヘルス | GET | /healthz | ヘルスチェック |
REST API の使用例
# コレクション作成
curl -X PUT "http://localhost:6333/collections/test" \
-H "Content-Type: application/json" \
-d '{
"vectors": {
"size": 768,
"distance": "Cosine"
}
}'
# ポイントの挿入
curl -X PUT "http://localhost:6333/collections/test/points" \
-H "Content-Type: application/json" \
-d '{
"points": [
{
"id": 1,
"vector": [0.1, 0.2, 0.3],
"payload": {"title": "Test document"}
}
]
}'
# 検索
curl -X POST "http://localhost:6333/collections/test/points/query" \
-H "Content-Type: application/json" \
-d '{
"query": [0.1, 0.2, 0.3],
"limit": 10,
"with_payload": true,
"filter": {
"must": [
{
"key": "category",
"match": {"value": "tech"}
}
]
}
}'
# コレクション情報の取得
curl "http://localhost:6333/collections/test"
# ヘルスチェック
curl "http://localhost:6333/healthz"
認証
APIキーを使用した認証:
# APIキー付きリクエスト
curl -H "api-key: your-secret-api-key" \
"http://localhost:6333/collections"
10.2 gRPC インターフェース
gRPCインターフェースはポート6334で提供される。REST APIと比較して、より高いスループットと低レイテンシを実現する。
gRPC の利点
- バイナリプロトコル: プロトコルバッファによるコンパクトなシリアライゼーション
- HTTP/2: 多重化による効率的な接続管理
- ストリーミング: 大量データの効率的な転送
- 型安全性: スキーマ定義による厳密な型チェック
Python gRPC クライアント
from qdrant_client import QdrantClient
# gRPC接続
client = QdrantClient(
host="localhost",
port=6334, # gRPCポート
grpc_port=6334,
prefer_grpc=True, # gRPCを優先
)
# 以降のAPIコールはgRPCで実行される
results = client.query_points(
collection_name="my_collection",
query=[0.1, 0.2, ...],
limit=10,
)
REST vs gRPC パフォーマンス比較
| 指標 | REST API | gRPC |
|---|---|---|
| レイテンシ | 基準 | ~20-30%低い |
| スループット | 基準 | ~40-50%高い |
| ペイロードサイズ | 大きい(JSON) | 小さい(ProtoBuf) |
| デバッグ容易性 | 高い | 低い |
| ブラウザ互換性 | 高い | 低い |
10.3 Web UI ダッシュボード
Qdrantはビルトインのウェブダッシュボードを提供する。ブラウザで http://localhost:6333/dashboard にアクセスすることで利用可能である。
主な機能:
- コレクションの管理(作成・削除・設定変更)
- データの閲覧と検索
- クラスター状態の監視
- API コンソール(REST APIの対話的実行)
11. クライアントライブラリ
11.1 Python クライアント
最も機能が充実した公式クライアントライブラリである。
pip install qdrant-client
基本操作
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance, VectorParams, PointStruct,
Filter, FieldCondition, MatchValue
)
# 接続
client = QdrantClient("localhost", port=6333)
# コレクション作成
client.create_collection(
collection_name="demo",
vectors_config=VectorParams(size=4, distance=Distance.COSINE),
)
# データ挿入
client.upsert(
collection_name="demo",
points=[
PointStruct(id=1, vector=[0.1, 0.2, 0.3, 0.4], payload={"city": "Tokyo"}),
PointStruct(id=2, vector=[0.5, 0.6, 0.7, 0.8], payload={"city": "Osaka"}),
],
)
# 検索
results = client.query_points(
collection_name="demo",
query=[0.1, 0.2, 0.3, 0.4],
limit=5,
)
非同期クライアント
from qdrant_client import AsyncQdrantClient
async_client = AsyncQdrantClient("localhost", port=6333)
# 非同期操作
results = await async_client.query_points(
collection_name="demo",
query=[0.1, 0.2, 0.3, 0.4],
limit=5,
)
FastEmbed 統合
Qdrant Pythonクライアントには、軽量なエンベディング生成ライブラリ FastEmbed が統合されている:
from qdrant_client import QdrantClient
client = QdrantClient(":memory:")
# FastEmbedを使用したドキュメント追加
client.add(
collection_name="demo",
documents=[
"Qdrant is a vector database",
"It supports similarity search",
"Written in Rust for performance",
],
metadata=[
{"source": "doc1"},
{"source": "doc2"},
{"source": "doc3"},
],
)
# テキストで直接検索
results = client.query(
collection_name="demo",
query_text="fast vector search engine",
limit=3,
)
11.2 Rust クライアント
# Cargo.toml
[dependencies]
qdrant-client = "1.10"
tokio = { version = "1", features = ["full"] }
use qdrant_client::prelude::*;
use qdrant_client::qdrant::vectors_config::Config;
use qdrant_client::qdrant::{
CreateCollection, SearchPoints, VectorParams, VectorsConfig,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = QdrantClient::from_url("http://localhost:6334").build()?;
// コレクション作成
client
.create_collection(&CreateCollection {
collection_name: "demo".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
..Default::default()
})
.await?;
// 検索
let search_result = client
.search_points(&SearchPoints {
collection_name: "demo".to_string(),
vector: vec![0.1, 0.2, 0.3],
limit: 10,
with_payload: Some(true.into()),
..Default::default()
})
.await?;
Ok(())
}
11.3 JavaScript/TypeScript クライアント
npm install @qdrant/js-client-rest
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
// コレクション作成
await client.createCollection("demo", {
vectors: {
size: 768,
distance: "Cosine",
},
});
// ポイント挿入
await client.upsert("demo", {
points: [
{
id: 1,
vector: [0.1, 0.2, 0.3],
payload: { title: "Test" },
},
],
});
// 検索
const results = await client.query("demo", {
query: [0.1, 0.2, 0.3],
limit: 10,
with_payload: true,
});
11.4 Go クライアント
go get github.com/qdrant/go-client
package main
import (
"context"
"log"
pb "github.com/qdrant/go-client/qdrant"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:6334", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
collectionsClient := pb.NewCollectionsClient(conn)
pointsClient := pb.NewPointsClient(conn)
// コレクション作成
_, err = collectionsClient.Create(context.Background(), &pb.CreateCollection{
CollectionName: "demo",
VectorsConfig: &pb.VectorsConfig{
Config: &pb.VectorsConfig_Params{
Params: &pb.VectorParams{
Size: 768,
Distance: pb.Distance_Cosine,
},
},
},
})
// 検索
searchResult, err := pointsClient.Search(context.Background(), &pb.SearchPoints{
CollectionName: "demo",
Vector: []float32{0.1, 0.2, 0.3},
Limit: 10,
WithPayload: &pb.WithPayloadSelector{SelectorOptions: &pb.WithPayloadSelector_Enable{Enable: true}},
})
}
11.5 その他のクライアント
- Java:
io.qdrant:client- Maven/Gradle で利用可能 - C#/.NET:
Qdrant.Client- NuGetパッケージ - Ruby: コミュニティ製クライアント
12. フレームワーク統合
12.1 LangChain との統合
LangChainはLLMアプリケーション開発の最も人気のあるフレームワークの一つであり、Qdrantとの統合が公式にサポートされている。
pip install langchain langchain-qdrant langchain-openai
基本的な使用法
from langchain_openai import OpenAIEmbeddings
from langchain_qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
# エンベディングモデル
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# Qdrant接続
client = QdrantClient("localhost", port=6333)
# VectorStoreの作成
vector_store = QdrantVectorStore(
client=client,
collection_name="langchain_collection",
embedding=embeddings,
)
# ドキュメントの追加
from langchain_core.documents import Document
docs = [
Document(
page_content="Qdrantは高性能なベクトルデータベースです。",
metadata={"source": "doc1", "category": "tech"},
),
Document(
page_content="LangChainはLLMアプリケーション開発フレームワークです。",
metadata={"source": "doc2", "category": "framework"},
),
]
vector_store.add_documents(docs)
# 類似検索
results = vector_store.similarity_search(
"ベクトル検索エンジン",
k=5,
)
# フィルタ付き検索
from qdrant_client.models import Filter, FieldCondition, MatchValue
results = vector_store.similarity_search(
"ベクトル検索",
k=5,
filter=Filter(
must=[
FieldCondition(key="metadata.category", match=MatchValue(value="tech"))
]
),
)
# スコア付き検索
results_with_scores = vector_store.similarity_search_with_score(
"ベクトルデータベース",
k=5,
)
for doc, score in results_with_scores:
print(f"Score: {score:.4f}, Content: {doc.page_content}")
LangChainでのRAGチェーン
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# Retriever の作成
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 5},
)
# プロンプトテンプレート
prompt = ChatPromptTemplate.from_template("""
以下のコンテキストに基づいて質問に回答してください。
コンテキストに情報がない場合は「分かりません」と回答してください。
コンテキスト: {context}
質問: {question}
回答:
""")
# LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# RAGチェーン
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 実行
answer = rag_chain.invoke("Qdrantとは何ですか?")
print(answer)
12.2 LlamaIndex との統合
pip install llama-index llama-index-vector-stores-qdrant
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
from qdrant_client import QdrantClient
# 設定
Settings.llm = OpenAI(model="gpt-4o", temperature=0)
Settings.embed_model = OpenAIEmbedding(model_name="text-embedding-3-small")
# Qdrantクライアント
client = QdrantClient("localhost", port=6333)
# QdrantVectorStore
vector_store = QdrantVectorStore(
client=client,
collection_name="llamaindex_collection",
)
# ドキュメントの読み込み
documents = SimpleDirectoryReader("./data").load_data()
# インデックスの構築
from llama_index.core import StorageContext
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(
documents,
storage_context=storage_context,
)
# クエリエンジンの作成
query_engine = index.as_query_engine(
similarity_top_k=5,
)
# 質問応答
response = query_engine.query("Qdrantの主な特徴は何ですか?")
print(response)
LlamaIndex でのフィルタ付き検索
from llama_index.core.vector_stores import (
MetadataFilter,
MetadataFilters,
FilterOperator,
)
# フィルタの定義
filters = MetadataFilters(
filters=[
MetadataFilter(
key="category",
value="technical",
operator=FilterOperator.EQ,
),
MetadataFilter(
key="date",
value="2025-01-01",
operator=FilterOperator.GTE,
),
],
)
# フィルタ付きクエリ
query_engine = index.as_query_engine(
similarity_top_k=5,
filters=filters,
)
12.3 Haystack との統合
from haystack_integrations.document_stores.qdrant import QdrantDocumentStore
from haystack_integrations.components.retrievers.qdrant import QdrantEmbeddingRetriever
# ドキュメントストアの作成
document_store = QdrantDocumentStore(
url="http://localhost:6333",
index="haystack_collection",
embedding_dim=768,
similarity="cosine",
)
# Retriever
retriever = QdrantEmbeddingRetriever(
document_store=document_store,
top_k=10,
)
12.4 Semantic Kernel との統合
import semantic_kernel as sk
from semantic_kernel.connectors.memory.qdrant import QdrantMemoryStore
# Qdrant メモリストアの設定
memory_store = QdrantMemoryStore(
url="http://localhost:6333",
vector_size=768,
)
12.5 直接的なエンベディングパイプライン
フレームワークを使用せずに、エンベディングモデルとQdrantを直接組み合わせるパターン:
from sentence_transformers import SentenceTransformer
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
# エンベディングモデルのロード
model = SentenceTransformer("intfloat/multilingual-e5-large")
# Qdrantクライアント
client = QdrantClient("localhost", port=6333)
# コレクション作成
client.create_collection(
collection_name="direct_pipeline",
vectors_config=VectorParams(
size=model.get_sentence_embedding_dimension(),
distance=Distance.COSINE,
),
)
# ドキュメントのエンベディングと挿入
documents = [
"Qdrantは高速なベクトルデータベースです",
"Rustで書かれた検索エンジン",
"RAGパイプラインに最適なストレージ",
]
embeddings = model.encode(documents)
client.upsert(
collection_name="direct_pipeline",
points=[
PointStruct(
id=i,
vector=embedding.tolist(),
payload={"text": doc},
)
for i, (doc, embedding) in enumerate(zip(documents, embeddings))
],
)
# 検索
query = "ベクトル検索のためのデータベース"
query_embedding = model.encode(query)
results = client.query_points(
collection_name="direct_pipeline",
query=query_embedding.tolist(),
limit=5,
)
13. Qdrant Cloud マネージドサービス
13.1 Qdrant Cloud 概要
Qdrant Cloudは、Qdrant社が提供するフルマネージドのベクトルデータベースサービスである。AWS、GCP、Azureの各クラウドプロバイダー上でホストされ、インフラストラクチャの管理負担を排除する。
主な特徴:
- フルマネージド: アップグレード、バックアップ、スケーリングの自動化
- マルチクラウド: AWS、GCP、Azure対応
- 水平スケーリング: ノードの追加によるシームレスな拡張
- ゼロダウンタイムアップグレード: ローリングアップデートによる無停止更新
- SOC 2 Type II 認証: エンタープライズレベルのセキュリティ
13.2 料金体系
Qdrant Cloudは以下のティアを提供する:
| ティア | 特徴 | 用途 |
|---|---|---|
| Free | 1GBメモリ、1ノード | 開発・テスト |
| Standard | カスタムサイズ、水平スケーリング | プロダクション |
| Enterprise | 専用インフラ、SLA保証 | 大規模エンタープライズ |
料金はノード構成(CPU、RAM、ディスク)に基づく時間課金である。
13.3 Qdrant Cloud の使用
from qdrant_client import QdrantClient
# Qdrant Cloudへの接続
client = QdrantClient(
url="https://your-cluster-abc123.us-east4-0.gcp.cloud.qdrant.io:6333",
api_key="your-api-key-here",
)
# 以降のAPI操作はローカルと同一
client.create_collection(
collection_name="cloud_collection",
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
)
13.4 ハイブリッドクラウド
Qdrant Hybrid Cloudは、自社のインフラストラクチャ上にQdrant Cloudのマネージメントレイヤーを展開するオプションである。データの所在地要件が厳しい環境やオンプレミス環境において、マネージドサービスの利便性を享受できる。
13.5 プライベートリージョン
特定のクラウドリージョンに専用のQdrantインフラストラクチャを展開できる。データレジデンシー要件への対応や、アプリケーションとの低レイテンシ接続に有用である。
14. 実践的な RAG 実装例
14.1 基本的なRAGパイプライン
以下は、PDFドキュメントを基にした質問応答システムの完全な実装例である。
"""
RAG Pipeline with Qdrant - Complete Implementation
"""
import os
from typing import List
from dataclasses import dataclass
from qdrant_client import QdrantClient
from qdrant_client.models import (
Distance, VectorParams, PointStruct,
Filter, FieldCondition, MatchValue,
SparseVectorParams, SparseIndexParams,
SparseVector, Prefetch, FusionQuery, Fusion,
)
from openai import OpenAI
import tiktoken
@dataclass
class Chunk:
"""テキストチャンクを表すデータクラス"""
text: str
metadata: dict
chunk_id: int
class QdrantRAGPipeline:
"""Qdrantを使用したRAGパイプライン"""
def __init__(
self,
qdrant_url: str = "localhost",
qdrant_port: int = 6333,
collection_name: str = "rag_documents",
embedding_model: str = "text-embedding-3-small",
llm_model: str = "gpt-4o",
chunk_size: int = 500,
chunk_overlap: int = 50,
):
self.qdrant = QdrantClient(qdrant_url, port=qdrant_port)
self.openai = OpenAI()
self.collection_name = collection_name
self.embedding_model = embedding_model
self.llm_model = llm_model
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
self.tokenizer = tiktoken.encoding_for_model(embedding_model)
def create_collection(self):
"""コレクションの作成"""
self.qdrant.recreate_collection(
collection_name=self.collection_name,
vectors_config=VectorParams(
size=1536, # text-embedding-3-small
distance=Distance.COSINE,
),
)
# ペイロードインデックスの作成
self.qdrant.create_payload_index(
collection_name=self.collection_name,
field_name="source",
field_schema="keyword",
)
def chunk_text(self, text: str, source: str) -> List[Chunk]:
"""テキストをチャンクに分割"""
tokens = self.tokenizer.encode(text)
chunks = []
chunk_id = 0
for i in range(0, len(tokens), self.chunk_size - self.chunk_overlap):
chunk_tokens = tokens[i:i + self.chunk_size]
chunk_text = self.tokenizer.decode(chunk_tokens)
chunks.append(Chunk(
text=chunk_text,
metadata={"source": source, "chunk_index": chunk_id},
chunk_id=chunk_id,
))
chunk_id += 1
return chunks
def embed_texts(self, texts: List[str]) -> List[List[float]]:
"""テキストのエンベディング生成"""
response = self.openai.embeddings.create(
input=texts,
model=self.embedding_model,
)
return [item.embedding for item in response.data]
def ingest_documents(self, documents: List[dict]):
"""ドキュメントの取り込み"""
all_chunks = []
for doc in documents:
chunks = self.chunk_text(doc["text"], doc["source"])
all_chunks.extend(chunks)
# バッチでエンベディング生成と挿入
batch_size = 100
for i in range(0, len(all_chunks), batch_size):
batch = all_chunks[i:i + batch_size]
texts = [c.text for c in batch]
embeddings = self.embed_texts(texts)
points = [
PointStruct(
id=i + j,
vector=embedding,
payload={
"text": chunk.text,
**chunk.metadata,
},
)
for j, (chunk, embedding) in enumerate(zip(batch, embeddings))
]
self.qdrant.upsert(
collection_name=self.collection_name,
points=points,
)
print(f"Ingested {len(all_chunks)} chunks from {len(documents)} documents")
def retrieve(
self,
query: str,
top_k: int = 5,
source_filter: str = None,
score_threshold: float = None,
) -> List[dict]:
"""関連ドキュメントの検索"""
query_embedding = self.embed_texts([query])[0]
query_filter = None
if source_filter:
query_filter = Filter(
must=[
FieldCondition(
key="source",
match=MatchValue(value=source_filter),
)
]
)
results = self.qdrant.query_points(
collection_name=self.collection_name,
query=query_embedding,
query_filter=query_filter,
limit=top_k,
score_threshold=score_threshold,
)
return [
{
"text": point.payload["text"],
"score": point.score,
"source": point.payload.get("source", "unknown"),
}
for point in results.points
]
def generate_answer(self, query: str, contexts: List[dict]) -> str:
"""コンテキストに基づく回答生成"""
context_text = "\n\n---\n\n".join([
f"[Source: {ctx['source']}, Relevance: {ctx['score']:.4f}]\n{ctx['text']}"
for ctx in contexts
])
response = self.openai.chat.completions.create(
model=self.llm_model,
messages=[
{
"role": "system",
"content": (
"あなたは質問応答アシスタントです。"
"提供されたコンテキストに基づいて正確に回答してください。"
"コンテキストに情報がない場合は、その旨を明記してください。"
),
},
{
"role": "user",
"content": f"コンテキスト:\n{context_text}\n\n質問: {query}",
},
],
temperature=0,
max_tokens=1000,
)
return response.choices[0].message.content
def ask(self, query: str, top_k: int = 5, source_filter: str = None) -> dict:
"""エンドツーエンドのRAGクエリ"""
# 1. 検索
contexts = self.retrieve(query, top_k=top_k, source_filter=source_filter)
if not contexts:
return {
"answer": "関連するドキュメントが見つかりませんでした。",
"contexts": [],
}
# 2. 生成
answer = self.generate_answer(query, contexts)
return {
"answer": answer,
"contexts": contexts,
}
# 使用例
if __name__ == "__main__":
pipeline = QdrantRAGPipeline()
pipeline.create_collection()
# ドキュメントの取り込み
documents = [
{
"text": "Qdrantは、Rustで書かれた高性能なベクトル類似検索エンジンです。...",
"source": "qdrant_docs",
},
{
"text": "RAGは、大規模言語モデルの回答精度を向上させる手法です。...",
"source": "rag_guide",
},
]
pipeline.ingest_documents(documents)
# 質問
result = pipeline.ask("Qdrantの主な特徴は何ですか?")
print(f"Answer: {result['answer']}")
print(f"Sources: {[c['source'] for c in result['contexts']]}")
14.2 ハイブリッドRAGパイプライン
密ベクトルとスパースベクトルを組み合わせたハイブリッドRAGの実装例:
class HybridRAGPipeline:
"""ハイブリッド検索を使用したRAGパイプライン"""
def __init__(self, qdrant_url="localhost", qdrant_port=6333):
self.qdrant = QdrantClient(qdrant_url, port=qdrant_port)
self.openai = OpenAI()
self.collection_name = "hybrid_rag"
def create_collection(self):
"""ハイブリッドコレクションの作成"""
self.qdrant.create_collection(
collection_name=self.collection_name,
vectors_config={
"dense": VectorParams(
size=1536,
distance=Distance.COSINE,
),
},
sparse_vectors_config={
"sparse": SparseVectorParams(
index=SparseIndexParams(on_disk=False),
),
},
)
def compute_sparse_vector(self, text: str) -> SparseVector:
"""簡易的なスパースベクトル生成(BM25的アプローチ)"""
from collections import Counter
import math
words = text.lower().split()
word_counts = Counter(words)
total_words = len(words)
indices = []
values = []
for word, count in word_counts.items():
word_hash = hash(word) % 30000
tf = count / total_words
indices.append(abs(word_hash))
values.append(float(tf))
return SparseVector(indices=indices, values=values)
def hybrid_search(self, query: str, top_k: int = 5) -> List[dict]:
"""ハイブリッド検索"""
# 密ベクトル生成
dense_embedding = self.openai.embeddings.create(
input=[query],
model="text-embedding-3-small",
).data[0].embedding
# スパースベクトル生成
sparse_vector = self.compute_sparse_vector(query)
# ハイブリッド検索(RRF融合)
results = self.qdrant.query_points(
collection_name=self.collection_name,
prefetch=[
Prefetch(
query=dense_embedding,
using="dense",
limit=50,
),
Prefetch(
query=sparse_vector,
using="sparse",
limit=50,
),
],
query=FusionQuery(fusion=Fusion.RRF),
limit=top_k,
)
return [
{"text": p.payload["text"], "score": p.score}
for p in results.points
]
14.3 マルチモーダルRAG
画像とテキストを組み合わせたマルチモーダルRAGの例:
from qdrant_client.models import PointStruct, VectorParams, Distance
class MultiModalRAG:
"""マルチモーダルRAGパイプライン"""
def __init__(self):
self.qdrant = QdrantClient("localhost", port=6333)
self.collection_name = "multimodal_rag"
def create_collection(self):
self.qdrant.create_collection(
collection_name=self.collection_name,
vectors_config={
"text": VectorParams(size=1536, distance=Distance.COSINE),
"image": VectorParams(size=512, distance=Distance.COSINE),
},
)
def search_by_text(self, query_embedding, top_k=5):
"""テキストベクトルで検索"""
return self.qdrant.query_points(
collection_name=self.collection_name,
query=query_embedding,
using="text",
limit=top_k,
)
def search_by_image(self, image_embedding, top_k=5):
"""画像ベクトルで検索"""
return self.qdrant.query_points(
collection_name=self.collection_name,
query=image_embedding,
using="image",
limit=top_k,
)
def cross_modal_search(self, text_emb, image_emb, top_k=5):
"""クロスモーダル検索(テキスト+画像)"""
return self.qdrant.query_points(
collection_name=self.collection_name,
prefetch=[
Prefetch(query=text_emb, using="text", limit=50),
Prefetch(query=image_emb, using="image", limit=50),
],
query=FusionQuery(fusion=Fusion.RRF),
limit=top_k,
)
15. パフォーマンスベンチマークと比較
15.1 ベンチマーク概要
ベクトルデータベースのパフォーマンスは、以下の指標で評価される:
- 検索レイテンシ(p50/p95/p99): クエリの応答時間
- スループット(QPS): 1秒あたりの処理クエリ数
- リコール率: 真の最近傍のうち正しく返された割合
- インデックス構築時間: データの取り込みにかかる時間
- メモリ使用量: データ格納に必要なメモリ量
15.2 Qdrant のパフォーマンス特性
Qdrantはann-benchmarks.comやVectorDBBenchなどの外部ベンチマークで一貫して優れた結果を示している。
典型的なパフォーマンス数値(参考値)
テスト条件:100万ベクトル、768次元、コサイン距離、8GB RAM、4 vCPU
| 指標 | 値 |
|---|---|
| 検索レイテンシ(p50) | ~2-5ms |
| 検索レイテンシ(p99) | ~10-20ms |
| スループット(QPS) | ~500-1500 |
| リコール率(@10) | >0.95 |
| メモリ使用量 | ~4-6GB |
| インデックス構築時間 | ~5-15分 |
注意:実際のパフォーマンスはハードウェア、設定、データ特性に大きく依存する。
15.3 他のベクトルデータベースとの比較
Qdrant vs Pinecone
| 項目 | Qdrant | Pinecone |
|---|---|---|
| ライセンス | Apache 2.0 (OSS) | Proprietary |
| デプロイメント | Self-hosted / Cloud | Cloud only |
| 言語 | Rust | 非公開 |
| フィルタリング | 非常に豊富 | 基本的 |
| スパースベクトル | ネイティブ対応 | 対応 |
| マルチベクトル | 対応 | 非対応 |
| 量子化 | 3種類 | サーバーサイド最適化 |
| 価格 | Self-hosted: 無料 | 従量課金 |
| メタデータフィルタ | ネスト対応、地理検索 | フラット構造 |
| 運用負担 | Self-hosted: 高い | 低い(フルマネージド) |
選択指針: カスタマイズ性とコスト効率を重視するならQdrant、運用負担の最小化を重視するならPinecone。
Qdrant vs Weaviate
| 項目 | Qdrant | Weaviate |
|---|---|---|
| 言語 | Rust | Go |
| API | REST + gRPC | REST + GraphQL |
| 検索 | ベクトル + フィルタ | ベクトル + BM25 + ハイブリッド |
| モジュール | なし(コア機能) | 多数(vectorizer、rerankerなど) |
| マルチテナンシー | ペイロードフィルタ | ネイティブ対応 |
| パフォーマンス | 一般に高速 | 良好 |
| メモリ効率 | 量子化で高効率 | HNSW + PQ |
| コミュニティ | 急成長中 | 大規模 |
選択指針: 純粋な検索パフォーマンスと柔軟なフィルタリングならQdrant、GraphQL対応や組み込みモジュールの豊富さならWeaviate。
Qdrant vs Milvus
| 項目 | Qdrant | Milvus |
|---|---|---|
| 言語 | Rust | Go + C++ |
| アーキテクチャ | モノリシック/分散 | マイクロサービス |
| スケーラビリティ | 水平スケーリング | 大規模分散(10億+ベクトル) |
| 依存関係 | なし | etcd, MinIO, Pulsar |
| デプロイ複雑性 | 低い | 高い |
| GPU対応 | なし | 対応(Knowhere) |
| ストリーミング挿入 | 対応 | 対応 |
| コレクション管理 | シンプル | Schema定義が必要 |
選択指針: シンプルなデプロイと運用ならQdrant、超大規模データセット(10億以上のベクトル)やGPUアクセラレーションが必要ならMilvus。
Qdrant vs ChromaDB
| 項目 | Qdrant | ChromaDB |
|---|---|---|
| 言語 | Rust | Python |
| パフォーマンス | 高い | 中程度 |
| スケーラビリティ | 分散対応 | 限定的 |
| フィルタリング | 非常に豊富 | 基本的 |
| プロダクション対応 | はい | 発展中 |
| 学習コスト | 中程度 | 低い |
| 組み込みモード | 対応 | 対応 |
| エンベディング統合 | FastEmbed | 内蔵 |
選択指針: プロダクション環境でのスケーラビリティとパフォーマンスならQdrant、プロトタイピングの速さとシンプルさならChromaDB。
15.4 ベンチマーク実施のための参考コード
"""
簡易ベンチマークスクリプト
"""
import time
import numpy as np
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct, SearchParams
def benchmark_qdrant(
num_vectors=100000,
vector_dim=768,
num_queries=1000,
top_k=10,
):
client = QdrantClient("localhost", port=6333)
collection_name = "benchmark"
# コレクション作成
client.recreate_collection(
collection_name=collection_name,
vectors_config=VectorParams(size=vector_dim, distance=Distance.COSINE),
)
# データ挿入のベンチマーク
print(f"Inserting {num_vectors} vectors...")
vectors = np.random.rand(num_vectors, vector_dim).astype(np.float32)
start_time = time.time()
batch_size = 1000
for i in range(0, num_vectors, batch_size):
batch = vectors[i:i+batch_size]
points = [
PointStruct(
id=i+j,
vector=vec.tolist(),
payload={"index": i+j, "category": f"cat_{(i+j) % 10}"},
)
for j, vec in enumerate(batch)
]
client.upsert(collection_name=collection_name, points=points)
insert_time = time.time() - start_time
print(f"Insert time: {insert_time:.2f}s ({num_vectors/insert_time:.0f} vectors/s)")
# インデックス構築待機
while True:
info = client.get_collection(collection_name)
if info.optimizer_status == "ok":
break
time.sleep(0.5)
# 検索のベンチマーク
query_vectors = np.random.rand(num_queries, vector_dim).astype(np.float32)
latencies = []
print(f"Running {num_queries} queries...")
for qvec in query_vectors:
start = time.time()
client.query_points(
collection_name=collection_name,
query=qvec.tolist(),
limit=top_k,
)
latencies.append((time.time() - start) * 1000) # ms
latencies.sort()
print(f"Search Latency:")
print(f" p50: {latencies[len(latencies)//2]:.2f}ms")
print(f" p95: {latencies[int(len(latencies)*0.95)]:.2f}ms")
print(f" p99: {latencies[int(len(latencies)*0.99)]:.2f}ms")
print(f" QPS: {num_queries/sum(l/1000 for l in latencies):.0f}")
# クリーンアップ
client.delete_collection(collection_name)
if __name__ == "__main__":
benchmark_qdrant()
16. 本番環境のベストプラクティス
16.1 セキュリティ
APIキーの設定
# config.yaml
service:
api_key: "${QDRANT_API_KEY}"
read_only_api_key: "${QDRANT_READ_ONLY_API_KEY}"
TLSの有効化
service:
enable_tls: true
tls:
cert: /path/to/cert.pem
key: /path/to/key.pem
ca_cert: /path/to/ca.pem
ネットワークセキュリティ
- QdrantをプライベートVPC/サブネットに配置する
- ロードバランサーを前段に配置する
- ファイアウォールルールでアクセスを制限する
- APIキーは環境変数またはシークレットマネージャで管理する
16.2 監視とオブザーバビリティ
Prometheus メトリクス
Qdrantは /metrics エンドポイントでPrometheusメトリクスを公開する:
curl "http://localhost:6333/metrics"
主要メトリクス:
qdrant_search_duration_seconds: 検索レイテンシqdrant_upsert_duration_seconds: 挿入レイテンシqdrant_collection_points_count: ポイント数qdrant_collection_segments_count: セグメント数qdrant_grpc_requests_total: gRPCリクエスト数
Grafanaダッシュボード
# docker-compose.yml(監視スタック)
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
# prometheus.yml
scrape_configs:
- job_name: 'qdrant'
scrape_interval: 15s
static_configs:
- targets: ['qdrant:6333']
metrics_path: /metrics
16.3 容量計画
メモリ見積もり
必要メモリ = ベクトル数 × (ベクトル次元 × 4 bytes + HNSWオーバーヘッド + ペイロードサイズ)
概算式:
- ベクトルストレージ:
N × D × 4 bytes(float32の場合) - HNSWインデックス:
N × M × 2 × 8 bytes(M = 接続数) - ペイロード: 可変(平均ペイロードサイズ × N)
例:100万ベクトル、768次元、M=16の場合:
- ベクトル: 1M × 768 × 4 = ~3 GB
- HNSW: 1M × 16 × 2 × 8 = ~256 MB
- 合計: ~3.3 GB(ペイロード除く)
量子化使用時:
- スカラー量子化: ~0.8 GB + HNSW ~256 MB = ~1 GB
- バイナリ量子化: ~0.1 GB + HNSW ~256 MB = ~0.4 GB
16.4 データインジェスト最適化
# 推奨されるデータインジェストパターン
from concurrent.futures import ThreadPoolExecutor
def optimized_ingest(client, collection_name, data, batch_size=256, workers=4):
"""最適化されたデータインジェスト"""
# 1. インデクシングを一時停止
client.update_collection(
collection_name=collection_name,
optimizer_config=OptimizersConfigDiff(indexing_threshold=0),
)
# 2. 並行バッチ挿入
def insert_batch(batch):
client.upsert(collection_name=collection_name, points=batch)
batches = [data[i:i+batch_size] for i in range(0, len(data), batch_size)]
with ThreadPoolExecutor(max_workers=workers) as executor:
executor.map(insert_batch, batches)
# 3. インデクシングを再開
client.update_collection(
collection_name=collection_name,
optimizer_config=OptimizersConfigDiff(indexing_threshold=20000),
)
# 4. 完了待機
while True:
info = client.get_collection(collection_name)
if info.optimizer_status == "ok":
break
time.sleep(1)
16.5 高可用性構成
# 本番環境向けDocker Compose
version: '3.8'
services:
qdrant-1:
image: qdrant/qdrant:latest
restart: always
deploy:
resources:
limits:
memory: 8G
cpus: '4'
reservations:
memory: 4G
cpus: '2'
volumes:
- qdrant_data_1:/qdrant/storage
environment:
- QDRANT__CLUSTER__ENABLED=true
- QDRANT__CLUSTER__P2P__PORT=6335
- QDRANT__SERVICE__API_KEY=${QDRANT_API_KEY}
- QDRANT__SERVICE__ENABLE_TLS=true
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6333/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# qdrant-2, qdrant-3 も同様に設定
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./certs:/etc/nginx/certs
depends_on:
- qdrant-1
- qdrant-2
- qdrant-3
volumes:
qdrant_data_1:
qdrant_data_2:
qdrant_data_3:
16.6 本番チェックリスト
運用環境へのデプロイ前に確認すべき項目:
- APIキー認証の設定
- TLS/SSLの有効化
- ネットワークのアクセス制御(ファイアウォール、セキュリティグループ)
- 適切なリソース割り当て(CPU、メモリ、ディスク)
- 永続ストレージのマウント
- バックアップ・スナップショットの自動化
- 監視・アラートの設定(Prometheus + Grafana)
- ヘルスチェックの設定
- レプリケーションの設定(高可用性)
- 量子化の検討(メモリ最適化)
- ペイロードインデックスの作成(フィルタリング性能)
- HNSWパラメータの調整
- ログレベルの設定
- 障害復旧手順の文書化
- ロードテストの実施
まとめ
Qdrantは、Rustの高性能性を活かした堅牢なベクトルデータベースとして、RAGパイプラインから推薦システムまで幅広いユースケースに対応している。豊富なフィルタリング機能、柔軟な量子化オプション、分散アーキテクチャのサポートにより、プロトタイプから本番環境まで一貫して利用できる。
特にLLMアプリケーションの普及に伴い、ベクトルデータベースの重要性は今後もさらに高まることが予想される。Qdrantは活発なコミュニティと継続的な機能改善により、この成長する市場において有力な選択肢であり続けるだろう。
本記事で解説した各機能と設定パラメータを理解し、自身のワークロードに最適な構成を選択することで、Qdrantの性能を最大限に引き出すことが可能である。
本記事は2026年4月時点の情報に基づいています。最新の情報はQdrant公式ドキュメントを参照してください。