Qdrant

Qdrant 技術概要 — 高性能ベクトルデータベースの全容

目次

  1. はじめに — Qdrantとは何か
  2. アーキテクチャ概要
  3. インストールとセットアップ
  4. コレクション、ポイント、ベクトル、ペイロード
  5. 検索機能
  6. インデクシング戦略とパフォーマンスチューニング
  7. スパースベクトルとマルチベクトル対応
  8. スナップショットとバックアップ管理
  9. 分散デプロイメントとクラスタリング
  10. REST API と gRPC インターフェース
  11. クライアントライブラリ
  12. フレームワーク統合
  13. Qdrant Cloud マネージドサービス
  14. 実践的な RAG 実装例
  15. パフォーマンスベンチマークと他VectorDBとの比較
  16. 本番環境のベストプラクティス

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年にかけて、ベクトルデータベース市場は急速に成長している。主要なプレイヤーとしては以下が挙げられる:

データベース開発言語ライセンス特徴
QdrantRustApache 2.0高性能、豊富なフィルタリング
PineconeProprietaryフルマネージド、簡易性重視
WeaviateGoBSD-3GraphQL対応、モジュラー設計
MilvusGo/C++Apache 2.0大規模データ対応
ChromaDBPythonApache 2.0軽量、開発者フレンドリー
pgvectorCPostgreSQLPostgreSQL拡張

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
  }
}
パラメータデフォルト値説明
m16各ノードの最大接続数。大きいほど精度が向上するがメモリ使用量が増加
ef_construct100インデックス構築時の探索幅。大きいほど精度が向上するが構築速度が低下
full_scan_threshold10000この数以下のベクトル数ではHNSWの代わりに全探索を使用
max_indexing_threads0インデクシングに使用するスレッド数(0はCPU数に自動設定)
on_diskfalseHNSWグラフをディスクに保存するか

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
    }
  }
}

圧縮率のオプション:x4x8x16x32x64

バイナリ量子化(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"
uuidUUID"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_shouldshould条件のうち最低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量子化特徴
最高速度RAMRAMなし最大メモリ使用量
バランスRAMRAMスカラー良好な速度と省メモリ
大規模データディスク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,
)

マルチテナンシーにおけるベストプラクティス:

  1. ペイロードインデックスの作成:テナントIDフィールドにkeywordインデックスを必ず作成する
  2. シャーディング最適化:テナントごとのデータ分布を考慮してシャード数を設定する
  3. コレクション分離 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 APIgRPC
レイテンシ基準~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は以下のティアを提供する:

ティア特徴用途
Free1GBメモリ、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

項目QdrantPinecone
ライセンスApache 2.0 (OSS)Proprietary
デプロイメントSelf-hosted / CloudCloud only
言語Rust非公開
フィルタリング非常に豊富基本的
スパースベクトルネイティブ対応対応
マルチベクトル対応非対応
量子化3種類サーバーサイド最適化
価格Self-hosted: 無料従量課金
メタデータフィルタネスト対応、地理検索フラット構造
運用負担Self-hosted: 高い低い(フルマネージド)

選択指針: カスタマイズ性とコスト効率を重視するならQdrant、運用負担の最小化を重視するならPinecone。

Qdrant vs Weaviate

項目QdrantWeaviate
言語RustGo
APIREST + gRPCREST + GraphQL
検索ベクトル + フィルタベクトル + BM25 + ハイブリッド
モジュールなし(コア機能)多数(vectorizer、rerankerなど)
マルチテナンシーペイロードフィルタネイティブ対応
パフォーマンス一般に高速良好
メモリ効率量子化で高効率HNSW + PQ
コミュニティ急成長中大規模

選択指針: 純粋な検索パフォーマンスと柔軟なフィルタリングならQdrant、GraphQL対応や組み込みモジュールの豊富さならWeaviate。

Qdrant vs Milvus

項目QdrantMilvus
言語RustGo + C++
アーキテクチャモノリシック/分散マイクロサービス
スケーラビリティ水平スケーリング大規模分散(10億+ベクトル)
依存関係なしetcd, MinIO, Pulsar
デプロイ複雑性低い高い
GPU対応なし対応(Knowhere)
ストリーミング挿入対応対応
コレクション管理シンプルSchema定義が必要

選択指針: シンプルなデプロイと運用ならQdrant、超大規模データセット(10億以上のベクトル)やGPUアクセラレーションが必要ならMilvus。

Qdrant vs ChromaDB

項目QdrantChromaDB
言語RustPython
パフォーマンス高い中程度
スケーラビリティ分散対応限定的
フィルタリング非常に豊富基本的
プロダクション対応はい発展中
学習コスト中程度低い
組み込みモード対応対応
エンベディング統合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公式ドキュメントを参照してください。