Milvus 外部集合:無需移動資料,即可索引與檢索資料湖中的資料
在許多 AI 資料管道中,嵌入向量與中繼資料已經在資料湖中產出並儲存。產品管道可能會將產品屬性與多模態嵌入向量寫入 S3 中的 Parquet 檔案。檢索或訓練語料庫可能存放在 Iceberg 或 Lance 資料表中。資料湖本來就是這些資料集被產生、更新、版本化管理,並供資料堆疊其餘部分使用的地方。
然而,向量資料庫傳統上一直是圍繞著「由資料庫自行管理的服務副本」來設計。如果團隊想要對已存放在資料湖中的資料進行低延遲向量搜尋,通常只有兩個選擇:
- 將資料複製到向量資料庫中。 這樣可以提供 ANN 索引和正式的服務路徑,但會產生第二份資料副本,以及必須與來源保持同步的 ETL 管道。
- 直接查詢資料湖。 這可以避免重複,但由於沒有 ANN 索引與服務層,向量搜尋會退化為不適合正式環境延遲需求的掃描。
Milvus 3.0 外部集合 引入了第三條路徑。 來源資料仍然保留在 Parquet、Iceberg、Lance、Vortex 或其他受支援的外部格式中,而 Milvus 會在其上建立索引並提供服務。您可以將外部欄位對應到 Milvus 的 schema、定義所需的索引、重新整理集合,然後使用一般的 Milvus 搜尋與查詢 API——無需先將來源資料列複製到 Milvus 管理的集合中。
架構上的改變很直接:資料可以繼續留在資料湖中,而 Milvus 負責加上索引與檢索層。
這也使外部集合成為邁向 Vector Lakebase 的重要一步。Vector Lakebase 是一個統一的、以資料湖為原生基礎的 AI 資料架構,結合了向量資料庫等級的服務能力、開放式資料湖儲存、可重用的湖級索引,以及共享的語意層。線上檢索不再需要從一份獨立的服務副本開始,而 Spark、訓練管道、評估作業與治理工具也不必再操作另一份資料版本。它們可以基於同一份湖駐留資料基礎來運作。
什麼是外部集合,以及它帶來了什麼改變
外部集合 是一種 Milvus 集合類型,其來源資料存放在 Milvus 管理儲存之外。
如果沒有外部集合,要將該目錄放到正式環境的向量搜尋後方,通常意味著必須在 Milvus 中建立另一份副本:
每當目錄變更、嵌入模型改變,或某個欄位被回填,就會需要另一條管道將更新後的資料搬移到那個邊界之外。
有了外部集合,架構就變成:
Milvus 不會將外部檔案變成自己的來源資料副本。相反地,外部集合中存放的是 Milvus 為了解讀與搜尋這些檔案所需的資訊:
- 一個
external_source,用來識別外部檔案或資料表。 - 一個
external_spec,用來描述來源格式與儲存存取方式。 external_field對應,將 Milvus schema 中的欄位連接到外部資料集中的欄位。- Milvus 為檢索所建立的索引、manifest 與服務狀態。
來源資料零複製並不代表 Milvus 內部沒有任何狀態。 Milvus 仍然會建立索引。它仍然會使用運算資源。它仍然會快取資料。改變在於:權威資料列不再因為你需要 Milvus 搜尋它們,就必須先被複製到 Milvus 中。
一般 Milvus 集合 vs. 外部集合
| 關注點 | Milvus 管理的集合 | 外部集合 |
|---|---|---|
| 來源資料列 | 由 Milvus 儲存與管理 | 保留在外部檔案或資料表中 |
| 資料如何進入 Milvus | Insert、upsert、匯入或串流寫入 | 外部來源對應 + Refresh |
| 線上變更 | 支援 | 從 Milvus 端為唯讀 |
| 資料新鮮度 | 跟隨 Milvus 寫入路徑與一致性模型 | 跟隨最後一次成功發布的 Refresh |
| Milvus 管理的狀態 | 來源資料、中繼資料、索引、快取 | 對應關係、manifest、索引、快取 |
| 查詢路徑 | Milvus 搜尋與查詢 API | Milvus 搜尋與查詢 API |
| 最適合的情境 | 持續變動的線上資料 | 大量、批次產生、讀取密集的資料湖資料 |
因此,外部集合是補足一般 Milvus 集合,而非取代它們。
系統可以將快速變動的線上狀態放在一般的 Milvus 集合中,同時對大型語料庫、目錄、歷史資料集、模型特徵或其他已在資料湖中產出並受治理的資料使用外部集合。
為什麼移除第二份副本很重要
把外部集合描述成一種儲存最佳化,的確很誘人:不要把好幾 TB 的資料複製到另一個資料庫,就能省下儲存空間。這確實有用,但這不是主要的架構問題。
更高的成本來自於讓兩套資料系統保持一致。
再次以產品目錄為例。資料平台產出權威的 Parquet 資料集。搜尋系統將其匯入向量資料庫。推薦團隊可能透過 Spark 讀取同一份資料湖資料進行離線分析。之後一個新的嵌入模型產生了取代舊有的向量欄位。庫存與中繼資料也在持續變動。
一旦線上服務副本獨立於資料湖,每一次變更都必須跨越那個邊界:
- 資料需要被複製;
- 傳輸需要被排程與監控;
- 失敗的工作需要重試;
- schema 與權限可能需要在多個系統中重複表示;
- 新鮮度取決於同步管道追趕的速度;
- 團隊必須知道哪一份副本才是他們真正想要的版本。
儲存成本只是其中一項。
| 成本 | 分離的資料湖 + 服務副本 | 外部集合 |
|---|---|---|
| 來源資料副本 | 資料湖副本加上一份獨立的服務副本 | 來源資料列保留在資料湖中 |
| 資料搬移 | 持續運作的 ETL/匯入管道 | 對外部來源執行 Refresh |
| 新鮮度 | 取決於匯出/匯入的頻率 | 由何時發布新的 Refresh 來控制 |
| 治理 | 來源與服務副本必須保持對齊 | 來源所有權、血緣與版本管理繼續保留在資料湖平台 |
| 離線重用 | 其他消費者可能各自準備自己的副本 | 現有的資料湖工具可以繼續讀取同一份來源 |
| 服務資源 | 依資料庫副本與查詢工作負載來規劃規模 | 索引、查詢運算與快取可以與來源資料列的所有權分開管理 |
隨著 AI 資料變動越來越頻繁,這個差異變得格外重要。
團隊會對語料庫進行去重。他們會為了分析而對資料進行分群。當模型改變時,他們會產生新的嵌入向量。他們會加上標籤、摘要、抽取出的實體、品質分數或回饋訊號。他們會對同一份語料庫執行評估作業與資料清理管道,而這份語料庫也正是正式環境應用程式所檢索的對象。
如果每個系統都擁有自己的副本,那麼每一次改進都會變成另一個同步工作。
外部集合改變了那個邊界:離線系統可以繼續在資料湖資料集上運作,而 Milvus 在同一份基礎上提供檢索服務。
外部集合支援哪些資料來源
外部集合是圍繞著開放、外部管理的資料來設計,而不是針對 Milvus 專屬的來源佈局。它透過 Storage V3 支援多種外部來源格式:
| 外部格式 | format 值 | Milvus 讀取的內容 |
|---|---|---|
| Apache Parquet | parquet | 包含 Parquet 檔案與 row group 的目錄或物件儲存前綴 |
| Vortex | vortex | Vortex 檔案及其佈局中繼資料 |
| Lance | lance-table | Lance 資料集及其 fragment 中繼資料 |
| Apache Iceberg | iceberg-table | Iceberg 中繼資料加上所選取的 snapshot |
| Milvus snapshot | milvus-table | 以外部來源形式暴露的受支援 Milvus snapshot |
來源與 Milvus 之間的對應是明確的。
名為 product_id 的來源欄位可以變成 Milvus 的 id 欄位;image_vec 可以變成 embedding;而寬型的來源資料表不需要把每個欄位都暴露給集合。這表示資料平台不需要為了配合服務資料庫而重新命名或重寫來源。
具版本管理的格式還增加了另一個有用的特性。對於 Iceberg 這類來源,集合可以指向特定的 snapshot,而不是查詢執行時當下所指向的內容。固定的來源版本對於可重現的評估、回歸測試、歷史分析與稽核工作負載非常有用。
底層檔案也仍然可以被資料堆疊的其他部分使用。Spark、訓練框架、治理系統以及其他相容於資料湖的工具都可以繼續讀取同一份開放資料。
外部集合只是為這份資料增加了一個新的消費者;它不會讓 Milvus 變成唯一的擁有者。
安全地存取外部儲存
Milvus 也需要權限才能讀取外部儲存。
根據儲存供應商的不同,部署可以使用 workload 或 instance identity、AWS STS 角色假設、服務帳戶 impersonation、以 SAS 為基礎的存取,或供應商專屬的角色系統等機制,而不是在應用程式設定中嵌入長期有效的憑證。
這個儲存身分控制的是 Milvus 如何連到來源。Milvus 內部的授權仍然是一個獨立的安全邊界。
如何建立、索引、重新整理與查詢外部集合
外部集合的生命週期有四個主要步驟:
- 定義外部來源,並將其欄位對應到 Milvus schema。
- 定義工作負載所需的索引。
- 執行 Refresh,讓 Milvus 探索來源資料並準備可查詢的版本。
- 載入集合,然後使用一般的 Milvus 搜尋與查詢 API。
以下是以外部集合表示的同一份產品目錄:
import json
import time
from pymilvus import DataType, MilvusClient
client = MilvusClient(
uri=“http://localhost:19530”,
token=“root:Milvus”,
)
schema = client.create_schema(
external_source=“s3://my-lake/datasets/products/”,
external_spec=json.dumps(
{
“format”: “parquet”,
“extfs”: {
“cloud_provider”: “aws”,
“region”: “us-east-1”,
“use_iam”: “true”,
“iam_endpoint”: “https://sts.us-east-1.amazonaws.com”,
},
}
),
)
schema.add_field(
field_name=“id”,
datatype=DataType.INT64,
external_field=“product_id”,
)
schema.add_field(
field_name=“embedding”,
datatype=DataType.FLOAT_VECTOR,
dim=768,
external_field=“image_vec”,
)
schema.add_field(
field_name=“title”,
datatype=DataType.VARCHAR,
max_length=256,
external_field=“product_name”,
)
schema.add_field(
field_name=“stock”,
datatype=DataType.INT64,
external_field=“stock”,
)
schema.add_field(
field_name=“rating”,
datatype=DataType.FLOAT,
external_field=“rating”,
)
client.create_collection(
collection_name=“products_ext”,
schema=schema,
)
索引使用一般的 Milvus 介面:
index_params = client.prepare_index_params()
index_params.add_index(
field_name="embedding",
index_type="HNSW",
metric_type="COSINE",
)
index_params.add_index(field_name="stock", index_type="AUTOINDEX")
index_params.add_index(field_name="rating", index_type="AUTOINDEX")
client.create_index(
collection_name=“products_ext”,
index_params=index_params,
)
然後重新整理外部來源:
job_id = client.refresh_external_collection(
collection_name="products_ext",
)
while True:
progress = client.get_refresh_external_collection_progress(job_id=job_id)
if progress.state == “RefreshCompleted”:
break
if progress.state == “RefreshFailed”:
raise RuntimeError(progress.reason)
time.sleep(2)
一旦重新整理後的版本準備就緒,就可以像一般的 Milvus 集合一樣載入並搜尋:
client.load_collection("products_ext")
results = client.search(
collection_name=“products_ext”,
data=[query_vec],
anns_field=“embedding”,
filter=“stock > 0 and rating >= 4.0”,
limit=10,
output_fields=[“id”, “title”, “stock”, “rating”],
)
重要的差異不在於搜尋呼叫本身,而在於生命週期的起點。Milvus 管理的集合始於資料被寫入或匯入 Milvus。外部集合則始於一個指向已存在於其他地方之資料的參照。
Refresh 如何取得外部資料的變更
外部集合從 Milvus 端來看是唯讀的,但底層的資料湖資料集不需要永遠保持靜止。
假設產品管道新增了一批資料、更新了中繼資料,或寫入了來自新模型的嵌入向量。Milvus 不會持續追蹤來源路徑中出現的每一個物件。這些變更會透過 Refresh 變得可見。
Refresh 會讀取外部中繼資料、解析來源 fragment、更新將它們連到 Milvus 集合的 manifest,並準備對應的索引狀態。
關鍵在於,這項工作可以是增量式的。
Milvus 會識別出沒有變更的來源 fragment,並重用它們既有的 segment 與索引工作。新的或變更過的 fragment 才是需要重新處理的部分。
因此,對一個數 TB 的大型資料集做小幅變更,不需要觸發另一次完整的匯入與完整的索引重建。
Refresh 也為服務系統提供了一個清楚的版本邊界。當新版本正在準備時,查詢會繼續使用先前發布的狀態。一旦 Refresh 完成,新狀態就會以完整版本的形式上線,而不是暴露出新舊資料混雜、或部分準備完成的狀態。
這種模型很自然地符合每小時的目錄建置、每日的知識庫更新、週期性的嵌入向量重新整理、模型產生的特徵管道,以及其他類似的批次導向工作負載。
它不會取代串流寫入路徑。如果每一次 insert 或 delete 都必須立即透過 Milvus 變成可搜尋的,那麼受管理的集合仍然是比較好的模型。
Lazy Loading 如何為寬型資料集減少記憶體使用
把來源資料列留在物件儲存中,只有在服務層不需要在回答查詢前先將每個位元組載入本機時,才真的有幫助。啟用 Milvus 分層儲存之後,確實如此。
在集合載入時,QueryNodes 可以只先保留輕量的中繼資料,例如 schema 資訊、索引定義、chunk 對應表,以及指向遠端物件的參照。欄位資料會在查詢需要時以 chunk 層級的方式擷取;索引可以保留在遠端直到首次使用,之後再快取到本機。經常使用的資料會保持在熱狀態,而較少存取的資料則可以被淘汰。
這對於寬型的 AI 資料集特別有用。
一列產品資料可能包含多個嵌入向量、一段很長的描述、原始 JSON、圖片中繼資料、產生的摘要、庫存、定價、評分,以及許多其他屬性。一個典型的相似度搜尋可能只會碰觸一個向量加上庫存、價格與評分。沒有理由因為其他欄位屬於同一筆記錄,就必須永遠佔用服務記憶體。
外部集合可以在兩個層級縮小服務佔用空間:
- 首先,schema 層級的投影。 透過
external_field,外部集合可以只暴露應用程式需要的來源欄位。其他欄位繼續留在資料湖資料集中,不會被納入這個服務 schema。 - 其次,執行期的投影。 在分層服務模型下,QueryNodes 只會擷取並快取工作負載實際需要的欄位與索引,而不是在一開始就載入整個已對應的資料集。
換句話說,資料集可以在資料湖中保持寬型,而不必迫使服務佔用空間也同樣寬。
這裡有一個顯而易見的取捨。命中冷欄位或冷索引的查詢,在首次存取時可能會有遠端讀取的成本。預熱策略可以預先載入延遲敏感的欄位或索引,而快取與淘汰策略則可以避免較少存取的狀態無限佔用本機資源。
重點不在於物件儲存可以表現得像 RAM 一樣。而是在於記憶體與本機磁碟可以跟著檢索工作負載的工作集走,而不是跟著來源資料集的總大小與寬度走。
來源格式在這裡也很重要。為了廣泛分析掃描而設計的格式,與針對較窄或隨機讀取最佳化的格式,在按需存取時會產生不同的 I/O 行為。外部集合不會抹除這些儲存層級的取捨;它讓 Milvus 可以在這些取捨之上建立檢索層。
外部集合支援哪些搜尋與索引能力
外部集合不只是讓 Milvus 指向一個嵌入向量目錄然後掃描檔案。Milvus 會在外部資料上建立檢索結構,並透過其標準檢索引擎執行查詢。
在外部資料上建立的 Milvus 索引
根據欄位與工作負載的不同,Milvus 可以建立:
- 用於 ANN 搜尋的向量索引;
- 用於中繼資料過濾的純量索引;
- 用於半結構化屬性的 JSON 索引;
- 用於詞彙檢索的 BM25 與全文索引。
- Milvus 資料模型支援的函式產生欄位。
ANN 搜尋會使用這些索引來縮小候選集合,而不是讀取每一個來源向量。
這個區別很重要,因為把嵌入向量存放在資料湖中,並不等於對它營運向量資料庫。持久化給你的是位元組。正式環境的檢索還需要索引、查詢規劃、過濾、排序、快取,以及低延遲的服務路徑。
超越向量 top-K
另一個常見的誤解是把「外部集合」理解成「在 Parquet 上做向量搜尋」。這低估了正式環境檢索實際上的需求。
一個正式環境的搜尋結果很少只依賴向量相似度。它可能還依賴於精確詞彙、存取政策、庫存、時間戳、類別、價格、來源品質或商業排序訊號。
考慮一個像是這樣的查詢:
| 夏季紅色碎花洋裝,有庫存,最高評分優先 |
|---|
一個正式環境的檢索路徑可能需要多種訊號:
- 向量相似度,用於「夏季碎花洋裝」的語意。
- 詞彙或全文搜尋,用於像是「紅色」這樣的精確詞彙。
- 純量過濾,用來排除缺貨或低於評分門檻的產品。
- 混合檢索與排序,用來結合多種檢索訊號。
Milvus 3.0 也將查詢引擎擴展到初始最近鄰檢索之外,加入了伺服器端排序、聚合與分面(facet)等能力。
更廣泛的觀點是,外部集合讓湖駐留資料獲得了一套資料庫級的檢索路徑——而不只是一種從檔案中讀取向量的方式。
同一份資料湖資料如何同時支援線上服務與離線處理
把來源保留在開放式資料湖格式中最強的架構理由,不只是第二份副本要花錢。而是同一份資料集可以繼續被那些持續改進它的系統使用。
回到產品目錄的例子。
白天,Milvus 可以提供一個外部集合來服務產品搜尋、推薦或 agent 檢索。
與此同時,其他系統可以直接在資料湖資料集上運作:
- Spark 可以識別重複的產品。
- 訓練管道可以從新模型產生嵌入向量。
- 資料品質工作可以偵測格式錯誤或異常的記錄。
- 評估管道可以比較不同模型版本之間的檢索品質。
- 批次處理可以產生摘要、標籤或其他中繼資料。
外部集合不會自己執行這些工作。Spark 仍然是 Spark;訓練仍然是訓練。它的角色是移除它們之間那道額外的服務資料邊界。
離線工作可以將改進後的資料或新欄位寫回資料湖。之後的一次 Refresh 就會讓更新後的來源可供 Milvus 檢索路徑使用。
不再需要一個獨立匯出與匯入的迴圈,只為了重建另一份用於服務的權威副本。
治理也能保持明確的劃分。來源版本、血緣與來源所有權留在資料湖平台。Milvus 維護自己集合層級的授權,以及讀取來源所需的憑證。共享同一份資料基礎,並不代表要把每個安全域都收斂到單一系統中。
這就是與 Vector Lakebase 的關聯:資料湖仍然是共享的資料基礎,而 Milvus 在其上提供低延遲的檢索層。外部集合是該架構的一部分,與 Storage V3、Snapshots、Spark 整合、schema 演進和 backfill 並列。
外部集合適合什麼地方——以及不適合什麼地方
外部集合在以下情況非常適合:
- 您的權威資料已經存放在 Parquet、Vortex、Lance、Iceberg 或其他受支援的外部來源中。
- 資料集主要是以批次方式產出,而不是透過高頻率的交易型寫入。
- 維護第二份服務副本會造成顯著的 ETL、新鮮度或治理成本。
- 多個系統需要處理同一份開放資料集。
- 明確的 Refresh 邊界對服務新鮮度來說是可接受的。
- 您想要正式的 Milvus 檢索能力,但不希望 Milvus 成為來源資料列的擁有者。
在以下情況,一般的 Milvus 集合仍然是更好的選擇:
- 應用程式持續進行 insert 或 upsert;
- 刪除需要透過線上寫入路徑立即生效;
- 工作負載依賴於外部 schema 無法提供的集合功能;
- 服務設計刻意將所有必要資料保持在記憶體中,以避免遠端快取未命中。
有幾個邊界值得牢記在心。
- 外部集合是唯讀的。 來源變更發生在 Milvus 之外。
- 零複製適用於來源資料列。 索引、manifest、快取與運算仍然需要消耗資源。
- Refresh 是明確的。 它不是一套串流同步機制。
- 來源必須保持可達。 搜尋、索引與 Refresh 的行為仍然取決於儲存存取與憑證。
- 必須啟用 Storage V3。 在開放原始碼 Milvus 3.0 中,使用外部集合前必須先啟用它。
- 外部集合不會取代上游處理。 嵌入向量產生、分群、去重與資料清理仍然在適當的上游系統中進行。
因此,這個選擇是互補的,而不是二選一。系統可以對快速變動的線上狀態使用一般的 Milvus 集合,並對大型、批次產生、自然歸屬於資料湖的資料集使用外部集合。
在 Milvus 3.0 中試用外部集合
外部集合已在 Milvus 3.0 中提供。從一個有代表性的資料湖資料集開始,評估對您的工作負載重要的各個面向:初始與增量 Refresh、索引建置成本、熱查詢與冷查詢行為,以及您的應用程式所需的新鮮度間隔。
實作細節請參閱:
如果您偏好受管理的路徑,外部集合也可以在 Zilliz Cloud 中以 Zilliz Vector Lakebase 的一部分來使用。請參閱:
您也可以將實作問題或意見回饋帶到 Milvus GitHub 儲存庫 或 Milvus Discord 社群。
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



