Attuを使ったMilvusの構築と運用管理:大規模RAG向けVector Databaseの実装

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

クイックスタート:5分で構築するMilvus StandaloneとAttu GUI

RAGのデータストアが500万〜1000万ベクトルを超えると、FAISSやChromaなどのインメモリライブラリは大量のRAMを消費し、クラスタ拡張が難しくなります。Milvusは分散アーキテクチャにより、この課題を根本から解決します。Docker Composeを使用すれば、環境全体を迅速に立ち上げることができます。

Milvusサーバー、MinIO(インデックスファイルの保存)、Etcd(メタデータの保存)、Attu(Web GUI)の4つのコンポーネントを含むdocker-compose.ymlファイルを作成します:

version: '3.5'

services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - ./volumes/etcd:/etcd
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd

  minio:
    container_name: milvus-minio
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    volumes:
      - ./volumes/minio:/minio_data
    command: minio server /minio_data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.3.4
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
    volumes:
      - ./volumes/milvus:/var/lib/milvus
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      - "etcd"
      - "minio"

  attu:
    container_name: milvus-attu
    image: zilliz/attu:v2.3.4
    ports:
      - "8000:3000"
    environment:
      MILVUS_URL: milvus-standalone:19530
    depends_on:
      - "standalone"

次のコマンド1つでスタック全体を起動します:

docker compose up -d

各サービスが安定するまで約30秒待機し、ブラウザでhttp://localhost:8000にアクセスします。ホスト名にmilvus-standalone:19530を入力してAttuダッシュボードにログインします。

MilvusのアーキテクチャとAttuの役割

1. Milvusの動作メカニズム

Milvusはコンピュート層(Compute)とストレージ層(Storage)を完全に分離しています。この設計により、検索トラフィックが急増した際にもQuery Nodeを個別にスケールアウトできます:

  • Etcd: コレクションのメタデータ、スキーマ、パーティション情報、クラスタステータスを保存します。
  • MinIO / S3: 生のベクトルデータ、ログファイル、インデックスファイルを保存します。コンピュートノードに障害が発生して再起動しても、データ損失の心配はありません。
  • Query Node & Data Node: Query Nodeはミリ秒単位の検索を提供するためにインデックスをRAMにロードします。Data Nodeは書き込み(insert)データをイミュータブルなセグメントに集約します。

2. 運用におけるAttuの利点

AttuはZillizが開発した公式GUIです。このインターフェースにより、データ確認のたびに手動でスクリプトを書く手間を大幅に削減できます:

  • CollectionやPartitionの作成・編集・削除を直感的に実行。
  • 各コレクションのメモリロード状態(Loaded/Unloaded)を確認。
  • Web画面上で直接Vector Searchやスカラーメタデータのフィルタリングをすばやくテスト。
  • エンティティ数やセグメントサイズをリアルタイムにモニタリング。

PyMilvusによるデータ操作とインデックスの最適化

RAGのパフォーマンスを左右する2大要因は、Dynamic Fieldを活用した柔軟なスキーマ設計と、適切なインデックスアルゴリズムの選定です。

1. RAGシステム向けコレクションの初期化

以下は、ドキュメントチャンクと1536次元ベクトル(OpenAIのtext-embedding-3-small標準)を格納するコレクションを作成するPythonスクリプトです:

from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection

# Milvusへ接続
connections.connect("default", host="localhost", port="19530")

collection_name = "rag_enterprise_docs"

# スキーマの定義
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="doc_id", dtype=DataType.VARCHAR, max_length=64),
    FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=4096),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536)
]

# enable_dynamic_fieldを有効化し、任意のJSONメタデータを自由に追加可能にする
schema = CollectionSchema(fields, description="RAG Knowledge Store", enable_dynamic_field=True)
collection = Collection(name=collection_name, schema=schema)

print(f"コレクション {collection_name} の準備が完了しました!")

2. インデックスの選定:HNSWかIVF_FLATか?

インデックスを作成しない場合、Milvusは全件スキャン(Flat search)を実行します。100万ベクトルの場合、クエリのレイテンシは数百ミリ秒に達します。実際の選定基準は以下の通りです:

  • HNSW: 検索速度が極めて高速(100万ベクトルで通常5ms未満)、Recallは98〜99%に達します。デメリット:グラフ構築のためにRAMを多く消費します。
  • IVF_FLAT: ベクトルをセントロイドクラスタに集約します。HNSWよりもRAM消費が少なく、中規模スペックのサーバーに適しています。

Cosine距離メトリックを使用してHNSWインデックスを作成します:

index_params = {
    "metric_type": "COSINE",
    "index_type": "HNSW",
    "params": {"M": 16, "efConstruction": 200}
}

collection.create_index(field_name="embedding", index_params=index_params)

# インデックス構築後にコレクションをRAMにロード
collection.load()
print("HNSWインデックスの構築が完了し、コレクションがRAMにロードされました。")

3. Hybrid Search:メタデータフィルタリングとベクトル類似度の組み合わせ

実際の運用では、関連チャンクを検索しつつ、特定のドキュメントや部門に属するものに絞り込む必要があります。expr式を使用してフィルタリングを行います:

query_vector = [0.015] * 1536

search_params = {"metric_type": "COSINE", "params": {"ef": 64}}

results = collection.search(
    data=[query_vector],
    anns_field="embedding",
    param=search_params,
    limit=3,
    expr='doc_id == "finance_q1_2026"', # 類似度計算の前にメタデータをフィルタリング
    output_fields=["doc_id", "content"]
)

for hits in results:
    for hit in hits:
        print(f"Score: {hit.distance:.4f} | Content: {hit.entity.get('content')}")

Milvus運用の実践ノウハウ

  • collection.load()の呼び出しを忘れない: MilvusはRAMにロードされたセグメントに対してのみクエリを実行できます。クエリ結果が空の場合は、Attuを開いてコレクションがLoaded状態になっているか確認してください。
  • HNSWに必要なRAM容量の見積もり: 計算式:ベクトル数 * 次元数 * 4バイト * 1.5。例:1536次元の500万ベクトルの場合、約5,000,000 * 1536 * 4 * 1.5 ≈ 46 GB RAMが必要です。最低でも64GB RAMを搭載したサーバーを用意しましょう。
  • パーティション数の制限: 各コレクションのパーティション数は64個未満に抑えることを推奨します。細かなパーティションが多すぎると、Query Nodeのメモリ断片化の原因になります。詳細なデータフィルタリングには、Dynamic Metadataとexpr式を活用してください。
  • Etcdの定期的なCompactionを有効化: スナップショットを整理しないと、Etcdファイルが肥大化して2GB/4GBのクォータを超過し、クラスタがハングアップする原因になります。環境変数ETCD_AUTO_COMPACTION_RETENTION=1000を必ず維持してください。
  • Prometheusによるモニタリング: Milvusはポート9091/metricsでメトリクスエンドポイントを標準提供しています。Grafanaと連携させて、QPS、クエリレイテンシ、未インデックスのセグメント数を監視することをお勧めします。
Share: