Milvusとは、ベクトル検索専用に設計されたオープンソースのベクトルデータベースであり、その中でも最も広く使われている製品の一つである。だが、Milvusを「使いこなせる」と言えるエンジニアは意外に少ない。公式ドキュメントは「どう設定するか」を教えてくれるものの、「なぜその設定で速くなる、あるいは遅くなるのか」を体系立てて説明してはくれないからだ。書き込み直後のデータが検索に出てこないのはバグなのか仕様なのか。セグメントが増えると遅くなるのはなぜか。consistency levelを変えるとレイテンシがどう変わるのか——こうした問いに答えるには、公開されている設計論文とソースコードの両方まで降りていく必要がある。
本記事はMilvusの内部構造を、SIGMOD 2021論文・VLDB 2022 “Manu"論文という一次情報と、実際のGitHub上のソースコードに基づいて解説する。ANNアルゴリズムそのもの(HNSW・IVF・PQ・DiskANNの原理、recallの定義、セグメントという概念)についてはすでに理解している読者を前提とし、それらの前提知識は本記事では扱わない。まだの読者は先にシリーズ第1回を読むことを勧める。
本シリーズ「ANNとベクトル検索基盤の教科書」(全3回)
- ANN基礎編
- Milvus内部構造編(本記事)
- OpenSearch内部構造編
1. Milvusとは何か: 設計思想を定義した2本の論文
ANN基礎編 で学んだ HNSW・IVF・PQ・DiskANN といったアルゴリズムは、いずれも「メモリ上、あるいはディスク上に固定されたベクトル集合に対して、いかに速く・省メモリで近似最近傍探索を行うか」という問題を解く。しかし実運用では、ベクトル集合は固定されていない。毎秒何千件ものデータが insert され、delete され、それでいて検索クエリは絶え間なく飛んでくる。さらにデータ規模は数百万件から数百億件までスケールする。Milvus という製品の設計は、この「動的性」と「スケール」という二つの現実的制約から逆算して決まっている。その設計思想を最も正確に記述しているのが、Milvus 開発チーム自身が発表した2本の論文である。
SIGMOD 2021 論文が定義した3つの柱
2021年に発表された “Milvus: A Purpose-Built Vector Data Management System”(SIGMOD ‘21)は、Milvus 1.x〜2.0世代の設計思想を要約したものである。この論文は Milvus を「汎用データベースにベクトル型を後付けする」アプローチとは根本的に異なる、purpose-built(専用設計)なベクトルデータ管理システムとして位置づける。論文が挙げる要件は主に次の5つである。
- easy-to-use なアプリケーションインターフェースを提供すること
- CPU/GPU が混在する異種計算基盤(heterogeneous computing platform)向けに最適化すること
- 単純な類似検索を超えた高度なクエリ処理(スカラーフィルタとのハイブリッド検索など)をサポートすること
- 動的データ(dynamic data)に対応し、高速な更新を可能にしながら効率的なクエリ処理を維持すること
- 複数ノードにデータを分散し、スケーラビリティと可用性を実現すること
このうち本記事で特に重要なのが4番目と5番目である。論文はデータのライフサイクルを growing segment(書き込み中の可変セグメント)と sealed segment(不変な確定セグメント)に分けて管理するモデルを提示しており、これは現在のMilvusにもそのまま受け継がれている中核概念である。growing segment は追記のみを受け付け、一定条件でsealされると不変(immutable)になり、インデックス構築とコンパクションの対象になる。削除は物理削除ではなく、削除ベクトル(delete vector)やブルームフィルタによる論理削除として扱われ、実際の物理削除はコンパクション時にまとめて行われる。この「書き込みの増幅(write amplification)をクエリ性能から切り離す」設計判断が、後述する第3章・第5章の内容の土台になる。
また、ストレージ層とコンピュート層の論理的分離(storage/compute separation)も既にこの論文で明言されている。データはS3互換のオブジェクトストアに置かれ、コンピュートリソースはデータ移動なしに柔軟にデプロイできるという発想は、Snowflakeなど現代のクラウドデータウェアハウスと同じ設計原理をベクトル検索に持ち込んだものと言える。
VLDB 2022 “Manu” 論文が示した次世代アーキテクチャ
翌2022年に発表された “Manu: A Cloud Native Vector Database Management System”(VLDB ‘22, PVLDB 15(12))は、Milvus 2.xの背後にある設計思想をさらに一段深く定式化したものである。著者らは1200以上の実企業ユーザーとの対話を通じて、次世代ベクトルデータベースに求められる性質を「長期的な進化可能性(long-term evolvability)」「調整可能な一貫性(tunable consistency)」「良好な弾力性(elasticity)」「高性能」の4つに整理した。
Manu論文の最大の主張は、ログをシステムの背骨(backbone)にするという設計である。原文の表現を借りれば、Manuは「write-ahead log(WAL)とbinlogをバックボーンサービスとして公開し」、書き込み系のコンポーネントはすべて「ログのpublisher」として、読み取り専用の解析・検索系コンポーネントはすべて「独立したsubscriber」として設計される。つまりMilvusにおける「書き込み経路」と「検索経路」は、リクエスト/レスポンス型のRPCで直接結合しているのではなく、ログという単一の真実の源(single source of truth)を介して疎結合になっている。これにより書き込み側(DataNode/StreamingNode相当)と読み取り側(QueryNode相当)は互いを意識せずに独立にスケールでき、しかも新しいsubscriberコンポーネント(例えば新しい検索エンジンや別のインデックス方式)を後から追加することが容易になる。これが「長期的な進化可能性」の実体である。
もう一つの柱が read/write分離 と tunable consistency である。Manuは MVCC(multi-version concurrency control)と delta consistency モデルを採用し、全ての操作にタイムスタンプを付与することで、「どの時点のスナップショットを見るか」をリクエスト単位で選択可能にした。これは、全てのクライアントに対して常に最新かつ厳密に一貫したビューを強制する伝統的なRDBMSのモデルとは対照的である。論文は「ほとんどのベクトルデータアプリケーションは複雑なデータモデルや強い一貫性を必要としない」と明言しており、この前提に立って一貫性レベルをアプリケーション側が選択できるようにした。これが第4章で扱う Strong / Bounded / Session / Eventually という4段階の consistency level の直接の起源である。
1.x から 2.x への転換点
Milvus 1.xは、実質的には単一プロセスに近い構成で、GPU/CPUを使った類似検索エンジンとしての性能に主眼を置いていた。分散化やクラウドネイティブ化は後付けの拡張という位置づけであった。これに対し2.0以降のMilvusは、Manu論文の思想をそのまま製品化したものであり、最初から「ログ中心・read/write分離・ストレージコンピュート分離・tunable consistency」を前提にゼロから再設計されている。Proxy・Coordinator・QueryNode・DataNode・IndexNodeというマイクロサービス的な構成、Pulsar/KafkaをWALとして使う設計、etcdによるメタデータ管理、MinIO/S3によるオブジェクトストレージ利用は、いずれもこの転換の産物である。
そして本稿執筆時点(2026年7月)の最新安定版である Milvus 2.6.x では、この設計思想がさらに徹底され、複数に分かれていたCoordinator(RootCoord/QueryCoord/DataCoordなど)が単一のCoordinatorに統合され、Pulsar/Kafkaに依存しない zero-disk WAL である Woodpecker が導入されるなど、「ログを背骨にする」という発想がオブジェクトストレージそのものをWALの永続化先にするところまで推し進められている。次章ではこの現行アーキテクチャを詳しく見ていく。
2. Milvusのアーキテクチャ: Coordinator・Streaming Node・Query Node・Data Node
本章では2026年7月時点の最新安定版である Milvus 2.6.x のドキュメントに基づき、現行の構成要素を整理する。ただし2.6.xは前章で述べた転換の延長線上にある大きなリアーキテクチャを経ており、巷の解説記事に多い「RootCoord/QueryCoord/DataCoord/IndexCoordの4分割 + QueryNodeがhistorical/streaming両方を担当」という2.2〜2.5世代の説明はすでに古い。両方の姿を把握しておくことは、バージョン間の差分を理解する上でも、また2026年後半にGAが予定されているMilvus 3.0(2026年5月時点でbeta)への移行を見据える上でも有用なので、両方を示す。
旧世代(2.2〜2.5系)の構成
2.4系までのアーキテクチャは「4層構造」として説明される。
- アクセス層(Proxy): ステートレスなgRPCゲートウェイ。クライアントからのリクエストを受け、負荷分散(Nginx/K8s Ingress/LVSなど)の背後に置かれ、MPP(massively parallel processing)的に各ワーカーノードからの中間結果を集約・後処理して返す。
- Coordinator Service(制御プレーン):
- RootCoord: DDL/DCL(コレクション・インデックスの作成/削除)とタイムスタンプ(TSO)発行を担当
- QueryCoord: QueryNodeのトポロジ管理・負荷分散、growing→sealedのハンドオフ制御
- DataCoord: DataNode/IndexNodeのトポロジ管理、メタデータ維持、flush/compaction/インデックス構築のトリガ
- ワーカーノード:
- QueryNode: incrementalなログを購読してgrowing segmentを構築しつつ、object storageからsealed segmentをロードしてhistoricalな検索も担当。つまり1つのQueryNodeが「新しいデータ(streaming)」と「確定データ(historical)」の両方を検索する。
- DataNode: ログ購読、バッファリング、object storageへのflushを担当
- IndexNode: sealed segmentに対するインデックス構築専任。ステートレスでサーバーレス的にも配置可能
- ストレージ層: メタストレージ(etcd)、ログブローカー(Pulsar/Kafka/RocksMQ)、オブジェクトストレージ(MinIO/S3/Azure Blob)
現行世代(2.6.x)の構成
2.6.xでは、コンポーネントの役割分担が明確に再編されている。公式ドキュメントの記述に基づくと以下のようになる。
- Proxy(アクセス層): 役割は変わらずステートレスなフロントエンド。リクエスト検証と結果集約を担当。
- Coordinator(統合済み): 「Milvusの頭脳」と呼ばれる単一コンポーネントに統合された。旧来のRootCoord/QueryCoord/DataCoordが担っていたDDL/DCL/TSO管理、Streaming Serviceの管理(WALとStreaming Nodeの紐付け)、クエリのトポロジ管理と負荷分散、そしてhistorical dataの管理(コンパクションとインデックス構築タスクの分配)を1つのプロセス種別が担う。
- Streaming Node(新設): シャード単位の「ミニ頭脳」。一貫性保証、growing dataに対するクエリ実行、そしてgrowing→sealedへの変換(旧QueryNode+DataNodeの一部機能)を担当する。旧世代でQueryNodeとDataNodeとProxyに分散していたリアルタイム処理(メッセージキューの購読、incremental segmentの書き込み、incrementalクエリの提供、WALベースのリカバリ)がここに集約された。
- Query Node: object storageからhistorical dataをロードし、historicalなクエリ処理に専念する。growing segmentの面倒はもう見ない。
- Data Node: IndexNodeを吸収し、コンパクションとインデックス構築という「オフライン処理」を一手に引き受ける。
つまり2.6.xでは「即時性が要求されるリアルタイム経路(Streaming Node)」と「大量データに対するバッチ的な処理経路(Query Node + Data Node)」が明確に分離された形になっている。これは前章で述べた「read/writeの分離」思想を、実装レベルでさらに徹底したものと理解できる。ここまで述べたコンポーネント群の全体像は図2-1のように整理できる。アクセス層(Client/Proxy)、制御プレーン(Coordinator)、ワーカー3種(Streaming Node/Query Node/Data Node)、ストレージ層(WAL/Object Storage/etcd)という層構造と、データの流れ(実線)・制御の流れ(破線)の対比を確認してほしい。

図2-1: Milvus 2.6.x の全体アーキテクチャ。Coordinatorに統合された制御プレーンと、Streaming Node/Query Node/Data Nodeへの役割再編、ログ(WAL)を背骨とするデータフローを示す。
WAL: Pulsar/Kafkaから Woodpecker へ
WAL(write-ahead log)の実体は、旧世代ではPulsarまたはKafka(スタンドアロンではRocksMQ)という外部メッセージキューであった。2.6.xではこれに加えて Woodpecker という新しいWAL実装が導入されている。Woodpeckerは「zero-disk design」、つまりローカルディスクに依存せず、ログデータを直接オブジェクトストレージに書き込むクラウドネイティブなWAL実装である。Kafka/Pulsarという重量級の外部依存を排除できる点が最大の利点であり、運用コストの削減に直結する。
メタストレージとオブジェクトストレージ
- etcd: コレクションスキーマなどのメタデータのスナップショットと、メッセージ消費のチェックポイントを保持する。高可用性・強一貫性・トランザクションサポートを理由に採用されている。
- オブジェクトストレージ(MinIO/S3/Azure Blob互換): ログのスナップショットファイル、スカラー/ベクトルのインデックスファイル、中間クエリ結果などを格納する。
knowhereとfaiss/hnswlibの関係
Milvusはベクトル検索の実行エンジンそのものを自前実装しているわけではない。Knowhere という独立したC++ライブラリがMilvusの内部コアとして働き、Faiss・hnswlib・DiskANNといった既存の著名なANNライブラリをラップして統一的なインターフェースを提供している。KnowhereはMilvusをビルドすると暗黙にビルドされる下位レイヤーであり、HNSW・IVF系・FLAT(ブルートフォース)・SCANN・DiskANN・量子化(PQ/SQ)系のバリアント・mmap対応など、 ANN基礎編 で学んだほぼ全てのインデックス手法をここで実装している。つまり「Milvusのインデックス」を語るとき、実際にアルゴリズムを実行しているのはKnowhereであり、Milvus本体(Go言語で書かれた制御プレーン)はKnowhereをCGO経由で呼び出すオーケストレーション層に徹している。GPU対応のインデックス(GPU_IVF_FLATやGPU_CAGRAなど)もKnowhere側で実装される。
Standalone と Distributed の違い
Milvusは同一のコードベースから2つのデプロイモードを提供する。
- Standalone: 全コンポーネントが単一プロセス内で動作する。中小規模データセット(目安として1億ベクトル程度まで)向け。開発・検証環境や小規模本番に向く。
- Distributed: Proxy・Coordinator・Streaming Node・Query Node・Data Nodeがそれぞれ独立したプロセス(Kubernetes上のPodなど)として動作し、コンポーネントごとに独立してスケールアウトできる。書き込み負荷と検索負荷を別々のノード群に分離できるため、数億〜数百億ベクトル規模のエンタープライズ本番運用に向く。
両モードは機能的には同一で、スケールと運用形態が異なるだけである。学習・検証はStandaloneで行い、本番はDistributedに移行するという流れが一般的である。
3. Milvusの書き込みの仕組み: insertからgrowing segment・flushまで
ベクトル検索システムを評価する上で見落とされがちな指標が「鮮度(freshness)」、すなわち insert したデータがどれだけ早く検索対象になるかである。バッチ的にインデックスを再構築するタイプのシステムでは、新規データが検索に反映されるまでに分単位〜時間単位の遅延が生じることがある。Milvusが「鮮度」を作り出す仕組みを理解するには、書き込み経路をステップごとに追う必要がある。
書き込みの全体経路
insert リクエストは概ね次の経路をたどる。
- Proxy: クライアントから insert リクエストを受け取り、Primary Key に基づいてシャード(vchannel)ごとのパッケージに分割する。
- WAL(Streaming Node / Woodpecker等): 各シャードのデータは対応するpchannel経由でStreaming Nodeに送られる。Streaming NodeはTSO(Timestamp Oracle)からタイムスタンプを払い出して操作順序を確定させ、一貫性チェックを行った上でWALに書き込む。WALへのコミットが完了した時点で、そのデータは「たとえクラッシュしても失われない」ことが保証される。障害発生時はStreaming NodeがWALをリプレイして未処理の操作を再現できる。
- Growing Segment: WALにコミットされたレコードは、Streaming Node(旧世代ではQueryNode)上のメモリに growing segment として構築される。この時点でまだ object storage には永続化されていない。
- Flush: 一定の条件を満たすと growing segment は sealed になり、object storage へ永続化される。永続化されたデータは sealed segment(あるいは flushed segment)と呼ばれる。
- インデックス構築とロード: sealed segment に対して Data Node がインデックスを構築し、Query Node が改めてそれを object storage からロードして historical な検索対象に加える。
この経路をステップ番号付きで図示すると図3-1のようになる。特に③ growing segment の時点で「ここから検索可能」になっている点、すなわちflushやインデックス構築を待たずに鮮度が確保される点に注目してほしい。

図3-1: Milvusの書き込み経路。WALコミット後、Growing Segment上でブルートフォース検索が可能になった時点が「鮮度」の起点であり、その後のflush・インデックス構築を経てQuery NodeでのANN検索対象になる。
growing segment 上でも検索できる仕組みこそが「鮮度」の正体
ここが最も重要な点である。もし insert されたデータが sealed され、インデックスが構築されて Query Node にロードされるまで検索できないとしたら、鮮度は「flush間隔+インデックス構築時間」のオーダーになってしまい、実用に耐えない。Milvusはこれを避けるため、growing segmentに対してもその場でクエリを実行できるようにしている。growing segmentにはまだ確定インデックス(HNSWグラフなど)が構築されていないため、この検索はブルートフォース(全件距離計算)で行われる。実際、Milvusのソースコード上でも SkipGrowingSegmentBF というパラメータが存在し(internal/querynodev2/segments/segment.go)、デフォルトでは growing segment に対してブルートフォース検索(BF = Brute Force)を行うことが読み取れる。
つまり、search リクエストが飛んでくると、Milvusは(a) 既にインデックスが構築されているsealed segment群への近似検索と、(b) まだインデックスのないgrowing segment群へのブルートフォース検索を同時に実行し、両者の結果を後段でマージ(reduce)する。growing segmentは一般に総データ量に対して小さいため、ブルートフォースのコストは許容範囲に収まる。この「セグメント単位でインデックスの有無が異なり、検索時にはその両方を跨いで統合する」という設計が、Milvusにおける鮮度と検索性能の両立を可能にしている。 ANN基礎編 で学んだ「セグメントという概念」がここで実践的な意味を持つわけである。
flush のトリガ条件
growing segment がいつ sealed されるかは複数の条件のOR条件で決まる。2.6.x のデータコーディネータ設定(dataCoord.segment.*)から読み取れる主な条件は次の通りである。
- サイズ条件:
dataCoord.segment.maxSize(デフォルト1024MB)とdataCoord.segment.sealProportion(デフォルト0.12)の組み合わせで、セグメントサイズが閾値に達するとsealされる。ジッター(sealProportionJitter、デフォルト0.1)が加えられ、多数のセグメントが同時にsealされて負荷が集中することを防いでいる。 - アイドル条件:
dataCoord.segment.maxIdleTime(デフォルト600秒)の間データが書き込まれず、かつサイズがminSizeFromIdleToSealed(デフォルト16MB)を超えている場合、自動的にsealされる。 - 最大寿命条件:
dataCoord.segment.maxLife(デフォルト86400秒=24時間)を超えたセグメントは強制的にsealされる。 - binlogファイル数条件:
dataCoord.segment.maxBinlogFileNumber(デフォルト32)に達した場合もsealされる。 - 明示的flush: クライアントが
flush()API を呼び出した場合、またはdrop_collectionなどのタイミングでも即座にflushが行われる。
Woodpeckerの記述にもある通り、Streaming Nodeは「そのセグメントに対応するWALエントリを全て取り込み終えた時点」でflushをトリガーする、というイベント駆動的な性質も持つ。
delete の扱い(delta log)
Milvusにおけるdeleteは即座の物理削除ではない。delete操作もinsertと同様にWALを経由してタイムスタンプを付与され、対象セグメントに対する delta log(削除ログ)として記録される。検索時には、対象セグメントの本体データに加えてそのセグメントのdelta logが参照され、削除済みのPrimary Keyにヒットしたエントリは結果から除外される(bloom filterで高速に削除候補を絞り込む実装になっている)。実際の物理的な削除(ディスク上のデータからの除去)は、後述する compaction のタイミングでまとめて行われる。この設計により、delete操作はinsertと同じくWALへの追記だけで完結する軽量な操作になり、書き込みパスの一貫性が保たれる。
4. Milvusのconsistency level: GuaranteeTimestampとtSafeの仕組み
search の経路
search リクエストは次の経路で処理される。
- Proxy: クライアントの検索リクエストを受け取り、対象コレクションの全シャード(vchannel)に対してリクエストを転送する。同時に、リクエストにどの時点のデータまでを見るべきかを示す
GuaranteeTimestampを付与する。 - Query Node(Delegator): 各シャードを代表する Query Node(shard delegator)が、自身が保持する historical(sealed segment)データと streaming(growing segment、または Streaming Node 経由のデータ)の両方に対して検索を実行する。
- Reduce: 各シャード・各セグメントから返ってきた部分結果(topk候補)を、Query Node内、さらにはProxy側で段階的にマージ(reduce)し、最終的なtopk結果に絞り込んでクライアントに返す。
consistency level と GuaranteeTimestamp
Milvusは4段階の consistency level を提供し、Bounded がデフォルトである。
- Strong: 最新のタイムスタンプを
GuaranteeTimestampとして使う。Query Nodeは自身のServiceTime(処理済みデータのタイムスタンプ上限)がこのGuaranteeTimestampに追いつくまで待ってから検索を実行する。 - Bounded:
GuaranteeTimestampを「最新時刻より少し前」の時刻に設定し、一定の遅延データ欠落を許容する。デフォルトの許容幅はcommon.gracefulTimeパラメータで制御される。 - Session: クライアントが最後に書き込みを行った時点のタイムスタンプを
GuaranteeTimestampとして使う。同一クライアントが書いたデータは即座に見えるが、他クライアントの最新書き込みは保証されない。 - Eventually:
GuaranteeTimestampを実質的に無視できる小さな値(1など)に設定し、一貫性チェックそのものをスキップする。最速だが、直近の書き込みが検索結果に反映されない可能性がある。
この仕組みの実装は internal/proxy/util.go の parseGuaranteeTsFromConsistency 関数に明確に現れている。
func parseGuaranteeTsFromConsistency(ts, tMax typeutil.Timestamp, consistency commonpb.ConsistencyLevel) typeutil.Timestamp {
switch consistency {
case commonpb.ConsistencyLevel_Strong:
ts = tMax
case commonpb.ConsistencyLevel_Bounded:
ratio := Params.CommonCfg.GracefulTime.GetAsDuration(time.Millisecond)
ts = tsoutil.AddPhysicalDurationOnTs(tMax, -ratio)
case commonpb.ConsistencyLevel_Eventually:
ts = 1
}
return ts
}
tMax はその時点でProxyが把握している最新のタイムスタンプ(TSOから発行された値)である。Strongの場合はこれをそのまま GuaranteeTimestamp にし、Boundedの場合は GracefulTime の分だけ過去に巻き戻し、Eventuallyの場合はほぼゼロ(=常に条件を満たす)にしてしまう、という実装が読み取れる。
なぜ Strong は遅いのか
GuaranteeTimestamp を受け取ったQuery Node(正確にはshard delegator)は、internal/querynodev2/delegator/delegator.go の waitTSafe という関数でこの値を処理する。
// waitTSafe returns when tsafe listener notifies a timestamp which meet the guarantee ts.
func (sd *shardDelegator) waitTSafe(ctx context.Context, ts uint64) (uint64, error) {
...
// Fast path: tSafe already meets the guarantee timestamp.
latestTSafe := sd.latestTsafe.Load()
if latestTSafe >= ts {
return latestTSafe, nil
}
// Slow path: tSafe has not yet reached the guarantee timestamp,
// need to wait for tSafe to advance via condition variable.
...
}
tSafe(TimeSafe / ServiceTime)は、そのQuery Nodeが「ここまでのデータは確実に取り込み終えた」と保証できる時刻の上限を表す内部状態である。検索リクエストの GuaranteeTimestamp が現在の tSafe より新しい場合、Query Nodeはコンディション変数を使って tSafe が追いつくまで実際にブロックして待つ。これはWALの取り込みパイプライン(Streaming Nodeから配信されるタイムスティック=time tickによってtSafeが進行する)の処理速度に依存するため、直近に大量のinsertが行われた直後にStrong consistencyで検索すると、その分だけレイテンシが増加する。この待ち時間は監視上 Wait tSafe Latency というメトリクス名で観測できる(第6章で詳述)。
一方Boundedであれば GuaranteeTimestamp は現在時刻より一定時間過去にずらされているため、多くの場合すでに tSafe がそれを上回っており、待つことなく即座に検索が実行できる。これがBoundedがデフォルトとして採用されている理由であり、「一貫性とレイテンシのトレードオフ」を最も直接的に体現しているのがこの GuaranteeTimestamp と tSafe の関係である。この待ち時間の有無を時間軸上で対比したものが図4-1である。

図4-1: Strong consistencyではGuaranteeTimestampが現在時刻に一致するためtSafeが追いつくまでの待ち時間が発生するのに対し、BoundedではgracefulTime分過去にずらすことで待ち時間がゼロになる。
pymilvus での指定方法
現行の pymilvus(MilvusClient)では、コレクション作成時にデフォルトの consistency level を設定でき、個々の検索・クエリ呼び出し時にも上書きできる。
from pymilvus import MilvusClient
client = MilvusClient(uri="http://localhost:19530")
# コレクション作成時にデフォルトの consistency level を指定
client.create_collection(
collection_name="my_collection",
dimension=768,
consistency_level="Bounded", # Strong / Bounded / Session / Eventually
)
# 検索時に個別の consistency level を上書きすることも可能
res = client.search(
collection_name="my_collection",
data=[query_vector],
limit=10,
search_params={"metric_type": "IP", "params": {"ef": 128}},
consistency_level="Strong", # このリクエストだけ強一貫性で検索
)
# 分析用途などレイテンシ最優先の場合
res = client.query(
collection_name="my_collection",
filter='color like "red%"',
output_fields=["vector", "color"],
limit=10,
consistency_level="Eventually",
)
書き込み直後に検索して結果を検証するようなテストコードではStrongを、リアルタイム性よりスループットを優先するレコメンドや分析用途ではBoundedやEventuallyを選ぶ、という使い分けが基本方針になる。
5. Milvusのsegmentとcompaction: ライフサイクルと検索性能
第3章で書き込み経路を追った際に触れた通り、Milvusの性能特性はセグメントのライフサイクルと密接に結びついている。本章ではその全体像と、実際のチューニングパラメータを見ていく。
ライフサイクルの全体像
セグメントは概ね次の状態遷移をたどる。
growing → sealed(flushed) → indexed → compacted
- growing: メモリ上でinsertを受け付け続ける状態。object storageにはまだ永続化されていない。この状態のセグメントはブルートフォースで検索される(第3章参照)。
- sealed / flushed: サイズ・アイドル時間・寿命などの条件を満たしflushされ、object storageに永続化された不変の状態。まだインデックスは構築されていないため、この直後はまだブルートフォース、あるいは暫定的な軽量インデックスでの検索が行われる。
- indexed: Data Node(旧IndexNode)がsealed segmentに対してインデックス(HNSW/IVF/DiskANNなど)を構築し終えた状態。Query Nodeがこれをロードすることで、近似検索の対象になる。
- compacted: 複数のsealed segmentが統合され、より少数の大きなセグメントにまとめられた状態。論理削除されたデータの物理的な除去もこのタイミングで行われる。
したがって「insertしてから検索性能が最大化されるまで」には、flush→インデックス構築→ロードという複数の非同期ステップが挟まる。この間はブルートフォースまたは低効率なパスで検索が行われるため、大量insert直後は一時的にレイテンシが悪化しうる。
compaction の種類
Milvusのソースコード(internal/datacoord/compaction_policy_*.go)を見ると、複数のコンパクション戦略が並存していることが分かる。
- L0 Compaction(
compaction_policy_l0.go/compaction_task_l0.go): delta log(delete情報)を対象データセグメントにマージし、削除済みエントリの参照コストを下げるためのコンパクション。 - Mix Compaction(
compaction_task_mix.go): 複数の小さなsealed segmentを1つにマージする、いわゆる標準的なコンパクション。 - Single Compaction(
compaction_policy_single.go): 単一セグメントに対する最適化(delete分の除去など)。 - Clustering Compaction(
compaction_policy_clustering.go/compaction_task_clustering.go): 指定したスカラーフィールド(clustering key)の値に基づいてエントリを再配置し、同じ値を持つデータを同じセグメントに集約する。これにより PartitionStats という「セグメントとクラスタリングキー値の対応関係を記録するインデックス」が構築され、クラスタリングキーで絞り込むフィルタ検索において不要なセグメントを丸ごとスキップ(prune)できるようになる。公式ベンチマークでは、prune率99%のケースでQPSが28.38から431.41へ、約25倍向上したと報告されている。 - Force Merge Compaction(
compaction_policy_forcemerge.go/compaction_view_forcemerge.go): 運用者が明示的にトリガーする「多対多」のマージで、通常のコンパクションが持つセグメントサイズ上限を超えて統合できる。公式ブログのベンチマーク(1M件・768次元・HNSW)では、並列数160の条件でQPSが約3,099から約5,780(+86.5%)に向上し、p99レイテンシは約1/3に低減したと報告されている。
ここまでのセグメント状態遷移とコンパクション5種の関係を整理したものが図5-1である。growing→sealed/flushed→indexed→compactedという遷移と、各状態での検索方式(ブルートフォース/ANN)の対応を確認してほしい。

図5-1: Milvusのsegment lifecycle。growing(ブルートフォース検索)からsealed/flushed、indexed(ANN検索)、compactedへの状態遷移と、5種類のコンパクション戦略の対応関係。
small segment 問題
継続的なinsert・update・delete・flushはそれぞれがsealed segmentを生み出す。Milvusは「書き込みがいつ終わるか」を事前に知ることができないため、安全側に倒して積極的な統合を避ける傾向があり、結果として小さなセグメントが大量に蓄積しやすい。これが small segment 問題 である。検索時には全てのsealed segmentのインデックスを個別に検索して結果をマージする必要があるため、セグメント数が増えるほど「ファンアウト税(fan-out tax)」と呼ぶべき、スケジューリングとマージのオーバーヘッドが線形に近い形で蓄積する。総データ量が変わらなくてもセグメント数が増えるだけでQPSが低下するのはこのためであり、Force Merge Compactionはこの問題への直接的な対処法として提供されている。
segment サイズ設定のチューニング
主要な dataCoord.segment.* パラメータ(2.6.x)は次の通りである。
| パラメータ | デフォルト値 | 意味 |
|---|---|---|
dataCoord.segment.maxSize | 1024 MB | セグメントの最大サイズ。sealProportion と組み合わせてseal判定に使う |
dataCoord.segment.diskSegmentMaxSize | 2048 MB | ディスクベースインデックス(DiskANNなど)を使うコレクションでの上限 |
dataCoord.segment.sealProportion | 0.12 | maxSize に対する最小割合に達したらsealする |
dataCoord.segment.sealProportionJitter | 0.1 | seal判定にゆらぎを与え、同時seal集中を防ぐ |
dataCoord.segment.maxLife | 86400 秒 | セグメントの最大寿命 |
dataCoord.segment.maxIdleTime | 600 秒 | この時間データを受けず、かつminSizeFromIdleToSealedを超えていればseal |
dataCoord.segment.minSizeFromIdleToSealed | 16 MB | アイドルsealのための最小サイズ |
dataCoord.segment.smallProportion | 0.5 | 「小さいセグメント」と判定する閾値(最大行数に対する割合) |
dataCoord.segment.compactableProportion | 0.85 | 小セグメントをマージ対象とする目標サイズ比 |
dataCoord.segment.expansionRate | 1.25 | コンパクション時に行数上限を超えて良い割合 |
dataCoord.segment.maxBinlogFileNumber | 32 | binlogファイル数の上限、達するとseal |
書き込みが極端に多いワークロードでは maxSize を大きくしてセグメント数自体を減らす、あるいは頻繁なupsertでsmall segmentが溜まりやすい場合は明示的にForce Merge Compactionをスケジュールする、といった調整が典型的な打ち手になる。逆にmaxSizeを小さくしすぎると、seal・インデックス構築の頻度が増えてCPU/IOオーバーヘッドが増大するため、単純に小さくすればよいわけではない点に注意が必要である。
6. Milvusが遅い時のチューニング: 性能障害の切り分け手順
検索が「遅い」という問題は、Milvusでは原因の層が多岐にわたるため、当てずっぽうでパラメータをいじる前に、まず観測データに基づいて層を切り分ける必要がある。本章は公式ブログ “How to Debug Slow Search Requests in Milvus” の内容を軸に、切り分けの手順を整理する。
まず見るべきPrometheusメトリクス
- Slow Query パネル:
proxy.slowQuerySpanInSeconds(デフォルト5秒)を超えたリクエストを検出する。ログ上は[Search slow]として出力される。 - Search Latency(全体): クラスタ全体のレイテンシ分布。
- Query Node の Search Latency by Phase: 検索処理を queue(キュー待ち)・query(実行)・reduce(結果統合) の3フェーズに分解して可視化する。ここがボトルネック特定の出発点になる。
- Scalar Filter Latency: スカラーフィルタの評価にかかった時間。
- Vector Search Latency: ベクトル距離計算そのものにかかった時間。
- Wait tSafe Latency: 第4章で見た
waitTSafeによるブロック時間。Strong consistencyや直後のinsertが影響する。
目安として、健全な状態ではレイテンシは30ms未満、100msを超えたら調査対象、1秒を超えると [Search slow] としてログに記録されるレベルの異常とされる。
queue time vs query time の切り分け
レイテンシが高いとき、まず queue time(キュー待ち時間) が支配的か、query time(実際の検索実行時間) が支配的かを見る。
- queue timeが支配的 → Query Nodeのスケジューラ(
queryNode.scheduler.*)がボトルネック。CPUリソース不足、あるいは単一リクエストのnq(クエリベクトル数)が大きすぎて他のリクエストをブロックしている可能性が高い。queryNode.scheduler.maxReadConcurrentRatio(デフォルト1、実効並列数はCPUコア数×2相当)やqueryNode.scheduler.cpuRatio(デフォルト10、読み取りタスクのCPU使用量見積もりに使う係数)を確認する。 - query timeが支配的 → インデックス選択やフィルタ条件そのものが重い可能性が高い。
nq と topk の影響
nq(number of queries、1リクエストに含まれるクエリベクトルの本数)が大きいバッチリクエストは、Query Node上のリソースを長時間占有し、他のリクエストのqueue timeを押し上げる。同一QPSであっても、「nq=1のリクエストを200QPSで送る」方が「nq=200のリクエストを1QPSで送る」よりも遥かに安定したレイテンシになることが実運用ベンチマークで確認されている。したがって高スループットが必要な場合は、無理に1リクエストへバッチをまとめるのではなく、QPSを分散させる、あるいはQuery Nodeをスケールアウトして並列度を確保する方が有効である。topk を大きくする場合も同様に、reduceフェーズで扱う候補数が増えるため、特にシャード数・セグメント数が多い構成ではreduce latencyへの影響を確認する必要がある。
フィルタ(スカラーフィルタ)と性能
スカラーフィールドに対してフィルタ式(filter=...)を使うハイブリッド検索において、対象フィールドにスカラーインデックスが設定されていないと「フルスキャン」にフォールバックし、Scalar Filter Latencyが急増する。またJSON型のフィールドに対するフィルタや、厳格な consistency level との組み合わせはオーバーヘッドをさらに増幅させる。対処としては、フィルタに使うフィールドには必ずスカラーインデックスを張ること、ORチェーンで書かれた条件は可能な限りIN式に書き換えて構文解析コストを下げること、が挙げられている。
QueryNode のメモリ不足と load 失敗
Query Nodeにコレクションをロードする際、必要なメモリが確保できないと load 自体が失敗する。これを緩和する仕組みとして次のパラメータ群がある。
queryNode.mmap.vectorField/vectorIndex/scalarField/scalarIndex: それぞれのデータをメモリに全展開せず mmap 経由でロードするかどうか。デフォルトは全てfalse(フルロード)。メモリ不足時にはこれらを有効化することでロード可能になるが、ページフォールトによる速度低下とのトレードオフになる。queryNode.mmap.chunkCache: 生ベクトル取得用のチャンクキャッシュに対するmmap。デフォルトはtrue。queryNode.lazyload.enabled: 有効化するとロード時に全データを読み込まず、検索時に必要な部分だけ遅延ロードする。コールドスタート時のロード時間を短縮できるが、初回検索のレイテンシは増加する。queryNode.loadMemoryUsageFactor: セグメントロード時のメモリ使用量見積もりに使う係数。実メモリ使用量がこの見積もりより大きいと予期せぬOOMを招くことがあるため、実測値に基づいて調整することが推奨される。
よくあるアンチパターン
公式ブログが挙げているアンチパターンは実務上も頻出する。
- 単一リクエストの
nqを不必要に大きくする(バッチをまとめすぎる) - フィルタに使うスカラーフィールドにインデックスを張らない
- 過度に複雑・巨大なフィルタ式を毎回文字列として送る(繰り返し使う場合は expression templating の利用でパース負荷を下げられる)
- 使っていないコレクションをロードしたままメモリを占有し続ける
- ピーク時間帯にクラスタリングコンパクションなどの重いバックグラウンドタスクをスケジュールする
- upsertをバッチ化せず1件ずつ発行し、大量の小さなセグメントを生み出す(第5章のsmall segment問題を自ら誘発する)
これらはいずれも、第2〜5章で見た「セグメント」「一貫性」「コンパクション」という設計の裏側を理解していれば、事前に避けられる、あるいは発生時に素早く原因特定できる問題である。Milvusのチューニングは個々のパラメータの暗記ではなく、書き込み経路・検索経路・セグメントライフサイクルという3つの流れがどう連動しているかを把握することに尽きる。
7. Milvusの監視: 見るべきPrometheusメトリクス
ANNインデックスは「正しく動いているように見えて実は劣化している」状態になりやすい。索引パラメータやハードウェアが変わらなくても、削除の蓄積・データドリフト・レプリカ間のリバランスによってrecallは静かに下がる。本章ではMilvusの本番運用で最低限見るべきメトリクスを整理する。
Milvusで見るべきPrometheusメトリクス
Milvusはmilvus名前空間でPrometheus形式のメトリクスを各コンポーネントの:9091/metricsエンドポイントから公開する(
Deploy Monitoring Services
、
Milvus Metrics Dashboard
)。実務上重要なものを層別に挙げる。
| レイヤ | メトリクス例 | 意味 |
|---|---|---|
| Proxy | milvus_proxy_sq_latency, milvus_proxy_req_latency, milvus_proxy_cache_hit_count | クライアントから見た検索/クエリ全体のレイテンシとキャッシュ効率 |
| Query Node | milvus_querynode_sq_req_latency, milvus_querynode_sq_segment_latency, milvus_querynode_sq_queue_latency, milvus_querynode_estimate_cpu_usage | セグメント単位の検索時間、キューイング遅延、CPU使用率(検索側のボトルネック特定に必須) |
| Data Node | milvus_datanode_flushed_data_size, milvus_datanode_compaction_latency | フラッシュ・compactionの頻度と所要時間(鮮度と書き込みバックプレッシャの指標) |
| Index Node | milvus_indexnode_build_index_latency, milvus_indexnode_index_task_count | インデックス再構築の所要時間とキューの詰まり具合 |
p99のmilvus_querynode_sq_req_latencyが悪化しているのにsq_segment_latencyは横ばいという場合、ボトルネックはコーディネーションやキューイングにある可能性が高く、sq_queue_latencyを確認するというように、レイヤ間の相関を見ることが診断の基本になる。第6章で見たqueue time/query timeの切り分けは、まさにこのメトリクス群を根拠に行う判断である。
キャパシティ指標
Milvusで重要なのは、メモリ使用量そのものに加えて「セグメント数」と「compaction頻度」である。第5章で見たsmall segment問題の通り、sealedセグメントが増えすぎると検索対象セグメント数が増えてレイテンシが線形に悪化するため、compactionの遅延(milvus_datanode_compaction_latencyの増加や実行間隔の広がり)を監視し、セグメント数の推移と合わせて追跡する必要がある。compactionが追いつかなくなっている兆候(遅延の増加、実行間隔の拡大)は、セグメント数の増加による検索レイテンシ悪化の先行指標として扱うべきである。
recallは監視しないと静かに劣化する
recallが劣化する典型的な原因は三つある。第一にデータドリフトで、埋め込みモデルが変わらなくても実データの分布が変化すると、HNSWのグラフ構造が最適でなくなり同じefSearchでもrecallが下がる。第二に削除の蓄積で、論理削除されたベクトル(delta log)が物理的に取り除かれるまで(第5章のcompaction)、探索空間に「ゴースト」が残り実効recallを圧迫する。第三にオートスケールやレプリカ再配置に伴うインデックス再構築中の一時的なrecall低下である。いずれもレイテンシメトリクスには現れないため、正解集合(ブルートフォースで計算したtop-K)を用意したゴールデンクエリセットに対する定期recall測定をSLOの一部として組み込む必要がある。recallとレイテンシを常にペアで扱うべきだという考え方そのものは、 ANN基礎編 第9章で述べた「候補数パラメータがrecallとレイテンシを同時に動かす」という共通原理の帰結である。
SLO設計の例
Milvusにおける実務的なSLOの一例を示す。「p95検索レイテンシ200ms以下」「recall@10が週次サンプリングで0.95を下回らない」「compaction遅延(milvus_datanode_compaction_latency)が1時間以内」といった具体的な数値目標を組み合わせ、レイテンシとrecallを必ずペアで扱うことが、ANNシステム特有のSLO設計の要点である。
8. Milvusの導入事例: Tokopedia・Shopee・BIGO
理屈だけでなく、実際に本番投入した組織の一次情報から学べることは多い。ここでは出典が明確に確認できるMilvusの公開事例を3件紹介する。
Tokopedia — PoCで複数候補を比較した上での採用
インドネシア最大級のECプラットフォームであるTokopediaは、月間アクティブユーザー9,000万・出品者860万という規模の商品検索の関連性向上のため、類似検索の導入を検討した。FAISS・Vearch・Milvusを含む複数のベクトル検索スタックでPoCを実施し、負荷テストの結果からMilvusを選定している。選定理由として、Dockerイメージを取得してパラメータを調整するだけで使い始められる導入の容易さと、当時のFAISS/HNSW/DiskANN/ScaNNを含む豊富なインデックス選択肢が挙げられている。低成約キーワードと高成約キーワードのマッチングにMilvusを使う広告サービスでは、クリック率(CTR)・コンバージョン率(CVR)ともに10倍という結果を得た。単一ノードでの開発運用から、ミドルウェアMishardsとMilvus-Helmを使った高可用構成へ移行している( Tokopedia Achieved a 10x Smarter Search with Milvus )。ここでの教訓は、PoCの初期段階で複数OSSを横並びで負荷試験にかけたこと、そして本番投入後にHA構成への移行という二段階のロールアウトを踏んでいることである。
Shopee — 非構造化データ基盤としての採用とバージョン運用の現実
東南アジア・中南米で展開するShopeeは、動画配信事業への参入に伴い、動画・画像・音声・テキストという非構造化データを扱う必要に迫られ、既存のリレーショナルDBでは対応しきれなくなった。動画の特徴量をベクトル化してMilvusに格納し、新規アップロード動画と類似検索することで著作権マッチングと動画の重複排除(deduplication)を実現している。Milvusは動画推薦システムへのリアルタイム動画レコールにも組み込まれた。特筆すべきは、2026年時点でもShopeeがMilvus 2.2を本番で使い続けており、Mmap・GPUインデキシング・range searchといった新機能を目的に新しいバージョンへのアップグレードを検討中と明言している点である( How Milvus Empowers Shopee’s Multimedia Ambition )。本番稼働中のバージョンアップグレードは慎重に計画されるものであり、「最新機能が魅力的でも、稼働実績のあるバージョンを容易には手放さない」という運用上の現実を示す事例である。
BIGO(Likee) — バッチ類似検索による重複動画検出
同じZillizグループのBIGO(Likee)も動画重複排除にMilvusを採用し、日々大量にアップロードされる動画特徴ベクトルのバッチ類似検索に使っていることが報告されている( BIGO Leverages Milvus for Duplicate Video Removal )。ShopeeとBIGOはいずれも「動画特徴ベクトルの重複排除」という共通のユースケースでMilvusを採用しており、動画プラットフォームにおける著作権マッチング・重複排除がMilvusの代表的な適用領域の一つになっていることが分かる。
Milvus視点の教訓
3事例を通じて見えてくる教訓は次の3点に整理できる。第一に、Tokopediaのように「PoCで複数候補を横並びに比較してから決める」姿勢が、後悔のない技術選定につながっている。第二に、ShopeeやBIGOのように、Milvusは非構造化データ(動画・画像)の類似検索基盤として、EC・SNSといった大規模消費者向けサービスの裏側で実運用されている。第三に、Shopeeの事例が象徴するように、本番投入後もバージョンアップグレードは性能上のメリットだけでは決断されず、稼働実績という慣性が強く働く。ベンチマーク上の数値差よりも、こうした組織的・運用的な制約が実際の運用を左右していることが、これらの一次情報から読み取れる。
9. まとめ
本記事の要点を章ごとに整理する。
| 章 | 要点 |
|---|---|
| 第1章 | Milvusの設計はSIGMOD 2021論文とVLDB 2022 “Manu"論文が定義した「ログを背骨にする」「read/write分離」「tunable consistency」という思想の延長線上にあり、2.0以降はこれらをゼロから製品化したものである。 |
| 第2章 | 2.6.xではCoordinatorに制御プレーンが統合され、Streaming Node(リアルタイム経路)とQuery Node/Data Node(バッチ経路)に役割が再編された。実行エンジンはKnowhereが担い、Standalone/Distributedの2モードで同一機能を提供する(図2-1)。 |
| 第3章 | insertはProxyでシャード分割された後WALにコミットされ、Streaming Node上のgrowing segmentに書き込まれた時点でブルートフォース検索が可能になる。この即時検索性こそが「鮮度」の正体である(図3-1)。 |
| 第4章 | consistency levelはGuaranteeTimestampとtSafeの関係で実装されており、Strongは待ち時間が生じ得るのに対しBoundedはgracefulTime分過去にずらすことで待ちゼロを実現する(図4-1)。 |
| 第5章 | segmentはgrowing→sealed/flushed→indexed→compactedと遷移し、L0/Mix/Single/Clustering/Force Mergeという5種のコンパクションがsmall segment問題への対処や検索性能維持を担う(図5-1)。 |
| 第6章 | 性能障害の切り分けはqueue time/query timeの区別、nq/topkの影響、スカラーフィルタのインデックス有無、メモリ不足時のmmap/lazyload設定など、観測データに基づく層別の原因特定が基本方針となる。 |
| 第7章 | Prometheusメトリクスはレイヤ間の相関(sq_req_latency/sq_segment_latency/sq_queue_latency)で読み解き、セグメント数とcompaction遅延をキャパシティ指標として追跡し、recallはレイテンシとは独立に定期測定する必要がある。 |
| 第8章 | Tokopedia・Shopee・BIGOの公開事例は、PoCでの複数候補比較、非構造化データ基盤としての実運用、稼働実績を優先するバージョン運用という3つの教訓を示している。 |
Milvusの内部構造を貫く一本の糸は、「書き込みをログ(WAL)にコミットした瞬間から検索可能にする」という鮮度の設計と、「その検索可能性の範囲をリクエスト単位で調整できる」consistency levelの設計、そして「不変なセグメントの集合をいかに少ないファンアウトで検索するか」というsegment lifecycleの設計が、すべて同じ一つの思想——ログを背骨にし、read/writeを分離する——から一貫して導かれているという点である。性能障害の切り分けも観測設計も、突き詰めればこの3つの設計がどう連動しているかを追う作業に帰着する。
同じ「ANNを分散システムとして実装する」という課題に対し、全文検索エンジンの上に構築するとどうなるか。次回は、この同じ原理が全文検索エンジンの上ではどう実装されるかを、 OpenSearch内部構造編 で見ていく。
よくある質問(FAQ)
Milvusとはどんなデータベースですか?
Milvusは、ベクトル検索専用に設計された(purpose-built)オープンソースのベクトルデータベースである。SIGMOD 2021論文とVLDB 2022 “Manu"論文が示す通り、growing segment(書き込み中の可変セグメント)とsealed segment(不変な確定セグメント)にデータのライフサイクルを分けて管理し、ログ(WAL)を背骨にした read/write 分離とtunable consistencyを核とする設計思想を持つ。2.0以降はこの思想をゼロから製品化しており、2.6.xでは制御プレーンがCoordinatorに統合され、Streaming Node/Query Node/Data Nodeという役割分担で構成される。詳細は第1章・第2章。
MilvusのStandaloneとDistributedはどう違いますか?
Standaloneは全コンポーネントが単一プロセス内で動作するモードで、1億ベクトル程度までの中小規模データセットや開発・検証環境に向く。Distributedは Proxy・Coordinator・Streaming Node・Query Node・Data Node がそれぞれ独立したプロセスとして動作し、コンポーネントごとに独立してスケールアウトできるため、数億〜数百億ベクトル規模のエンタープライズ本番運用に向く。両モードは同一コードベースで機能的には同一であり、スケールと運用形態のみが異なる。詳細は第2章。
Milvusでinsertした直後のデータが検索にヒットしないのはなぜですか?
Milvusの検索リクエストには、どの時点までのデータを見るべきかを示す GuaranteeTimestamp が付与され、consistency levelによってこの値の決まり方が変わる。デフォルトのBoundedでは GuaranteeTimestamp を現在時刻より gracefulTime 分過去にずらして設定するため、直近の書き込みが検索結果に反映されないことがある。書き込み直後のデータを確実に検索したい場合はStrongを指定すればよいが、その場合はQuery Nodeの tSafe が GuaranteeTimestamp に追いつくまで待つ分レイテンシが増加する。詳細は第4章。
Milvusの検索が遅い時、まず何を確認すべきですか?
まず当てずっぽうでパラメータを変えるのではなく、Query Nodeの「Search Latency by Phase」パネルで検索処理をqueue(キュー待ち)・query(実行)・reduce(結果統合)の3フェーズに分解し、どのフェーズが支配的かを観測データから切り分ける。queue timeが支配的であればQuery Nodeのスケジューラやリソース不足、あるいは単一リクエストの nq の大きさが原因である可能性が高く、query timeが支配的であればインデックス選択やフィルタ条件そのものが重い可能性が高い。詳細は第6章。
MilvusとOpenSearchのベクトル検索はどう違いますか?
Milvusはベクトル検索のためにゼロから設計されたpurpose-builtなベクトルデータベースであるのに対し、OpenSearchはもともと全文検索エンジンとして生まれ、そこにk-NN検索機能を後から統合したという出自の違いがある。両者のより詳細な比較は ANN基礎編 の比較章、OpenSearch側の内部構造は OpenSearch内部構造編 を参照してほしい。
関連書籍
本シリーズの内容を書籍でも体系的に学びたい読者には、日本語で書かれたベクトル検索の実践的な入門書として次の一冊を挙げておく。
他の分野の定番書は エンジニアにおすすめの技術書10選 にまとめている。
付録: 用語集(Milvus編)
- growing segment / sealed segment: Milvusのセグメントが辿るライフサイクル上の2状態。growing segmentはメモリ上でinsertを受け付け続ける可変状態でブルートフォース検索の対象になり、sealed segmentはflushされ不変になった状態でインデックス構築・compactionの対象になる(第3章、第5章)。
- WAL(Write-Ahead Log): Milvusで各シャードの書き込みがコミットされる先。Pulsar/Kafka(旧世代)またはWoodpecker(2.6のzero-disk実装)が用いられ、コミット完了時点でクラッシュ耐性が保証される(第1章、第3章)。
- TSO(Timestamp Oracle): Milvusで操作順序を確定させるためのタイムスタンプ発行機構。Coordinatorが担う(第3章)。
- GuaranteeTimestamp: Milvusの検索リクエストに付与される「どの時点のデータまでを見るべきか」を示すタイムスタンプ。consistency levelに応じて、最新時刻(tMax)からの巻き戻し幅が変わる(第4章)。
- tSafe(TimeSafe): Query Node(shard delegator)が「ここまでのデータは確実に取り込み終えた」と保証できる時刻の上限を表す内部状態。
GuaranteeTimestampがtSafeより新しい場合、検索はtSafeが追いつくまでブロックされる(第4章)。 - compaction: Milvusでsealed segmentを統合・整理する処理の総称。delta logをマージするL0 Compaction、複数の小セグメントを統合するMix Compaction、クラスタリングキーで再配置するClustering Compaction、運用者が明示的にトリガーするForce Merge Compactionなどがある(第5章)。
- tunable consistency(調整可能な一貫性): MilvusがStrong/Bounded/Session/Eventuallyの4段階で提供する、リクエスト単位で選択可能な一貫性レベル。Boundedがデフォルト(第4章)。
参考文献
論文
- Jianguo Wang et al., “Milvus: A Purpose-Built Vector Data Management System,” SIGMOD 2021. https://www.cs.purdue.edu/homes/csjgwang/pubs/SIGMOD21_Milvus.pdf
- Rentong Guo et al., “Manu: A Cloud Native Vector Database Management System,” VLDB 2022 (PVLDB 15(12)). https://www.vldb.org/pvldb/vol15/p3548-yan.pdf (arXiv抄録: https://arxiv.org/abs/2206.13843 )
公式ドキュメント
- Milvus Architecture Overview(最新版): https://milvus.io/docs/architecture_overview.md
- Milvus Architecture Overview(v2.6.x): https://milvus.io/docs/v2.6.x/architecture_overview.md
- Milvus Four-Layer Architecture(v2.4.x): https://milvus.io/docs/v2.4.x/four_layers.md
- Data Processing(最新版): https://milvus.io/docs/data_processing.md
- Data Processing(v2.6.x): https://milvus.io/docs/v2.6.x/data_processing.md
- Consistency(最新版): https://milvus.io/docs/consistency.md
- Consistency(v2.6.x): https://milvus.io/docs/v2.6.x/consistency.md
- In-memory Index: https://milvus.io/docs/index.md
- Filtered Search: https://milvus.io/docs/filtered-search.md
- MMap-enabled Data Storage(v2.3.x): https://milvus.io/docs/v2.3.x/mmap.md
- Clustering Compaction(v2.6.x): https://milvus.io/docs/v2.6.x/clustering-compaction.md
- Configure DataCoord(v2.6.x): https://milvus.io/docs/v2.6.x/configure_datacoord.md
- Configure QueryNode(v2.6.x): https://milvus.io/docs/v2.6.x/configure_querynode.md
- Deploy Monitoring Services: https://milvus.io/docs/monitor.md
- Milvus Metrics Dashboard: https://milvus.io/docs/metrics_dashboard.md
- Scale a Milvus Cluster: https://milvus.io/docs/scaleout.md
- Milvus Roadmap(3.0 betaの状況): https://milvus.io/docs/roadmap.md
ブログ・事例
- Introducing Milvus 2.6: https://milvus.io/blog/introduce-milvus-2-6-built-for-scale-designed-to-reduce-costs.md
- How to Debug Slow Search Requests in Milvus: https://milvus.io/blog/how-to-debug-slow-requests-in-milvus.md
- Force Merge Compaction: Nearly 2x Milvus Search QPS: https://milvus.io/blog/force-merge-compaction-milvus-qps.md
- How to Safely Upgrade from Milvus 2.5.x to Milvus 2.6.x: https://milvus.io/blog/how-to-safely-upgrade-from-milvu-2-5-x-to-milvus-2-6-x.md
- Zilliz launches Milvus vector lakebase(3.0-betaの文脈, Blocks and Files): https://www.blocksandfiles.com/ai-ml/2026/07/16/zilliz-launches-milvus-vector-lakebase/5273617
- Tokopedia Achieved a 10x Smarter Search with Milvus(Zilliz customers): https://zilliz.com/customers/tokopedia
- How Milvus Empowers Shopee’s Multimedia Ambition(Zilliz blog): https://zilliz.com/blog/how-milvus-empowers-shopee-multimedia-ambition
- BIGO Leverages Milvus for Duplicate Video Removal(Zilliz blog): https://zilliz.com/blog/bigo-leverages-milvus-for-duplicate-video-removal
ソースコード
- milvus-io/milvus(GitHub, masterブランチ, 2026-07-19時点でクローン):
https://github.com/milvus-io/milvus
internal/proxy/util.go(parseGuaranteeTsFromConsistency,parseGuaranteeTs)internal/querynodev2/delegator/delegator.go(waitTSafe, tSafe wait mechanism)internal/querynodev2/segments/segment.go(SkipGrowingSegmentBF, growing segment brute-force search)internal/datacoord/segment_manager.go,internal/datacoord/segment_info.go,internal/datacoord/segment_allocation_policy.go(segment lifecycle / sealing)internal/datacoord/compaction_policy_l0.go,compaction_task_l0.go(L0 compaction)internal/datacoord/compaction_task_mix.go(mix compaction)internal/datacoord/compaction_policy_single.go(single compaction)internal/datacoord/compaction_policy_clustering.go,compaction_task_clustering.go(clustering compaction / PartitionStats)internal/datacoord/compaction_policy_forcemerge.go,compaction_view_forcemerge.go(force merge compaction)internal/coordinator/(unified coordinator directory structure)internal/streamingnode/,internal/streamingcoord/(Streaming Node components)
- zilliztech/knowhere(GitHub README, Milvusのインデックス実行エンジン): https://github.com/zilliztech/knowhere