背景
搜索或推薦場景,需要將非結(jié)構(gòu)化的物料(媒資)結(jié)構(gòu)化,也即提取特征,然后將特征存儲向量數(shù)據(jù)庫,從而實(shí)現(xiàn)海量數(shù)據(jù)快速檢索功能。
當(dāng)前,開源市場比較火的搜索引擎有Faiss,但Faiss更類似于es的lucene,需要上層解決分布式水平擴(kuò)容、數(shù)據(jù)一致性、高可用等問題。所以對于數(shù)據(jù)量大,要求高可用等架構(gòu)場景,使用milvus。
milvus想做的事
Lucene——Faiss
Milvus——Elasticsearch
專注向量檢索框架,解決數(shù)據(jù)一致性,分布式水平擴(kuò)容等問題
設(shè)計(jì)思想:
- CAP中選擇去犧牲一定的一致性,來實(shí)現(xiàn)可用性和 Latency
- 日志即數(shù)據(jù),流批一體
做一個數(shù)據(jù)庫,而不是引擎。如何做管理、計(jì)費(fèi)、可視化,數(shù)據(jù)遷移。數(shù)據(jù)庫不僅要提供傳統(tǒng)的增刪改查能力,還提供數(shù)據(jù)轉(zhuǎn)換、遷移、多租戶加密管理、計(jì)費(fèi)、限流、可視化、備份快找等更加多樣的服務(wù)
- 做數(shù)據(jù)分片
- 如何保證數(shù)據(jù)的高可靠性
- 如何保證分布式系統(tǒng)有節(jié)點(diǎn)出現(xiàn)異常時如何恢復(fù)
- 如何在一個大規(guī)模集群中實(shí)現(xiàn)負(fù)載均衡
- 如何查詢語句
- 如何做 Parse 和 Optimize
- 系統(tǒng)做持久化存儲,需要考量不同的數(shù)據(jù)存儲格式
milvus之前——向量檢索的一些基礎(chǔ)
近似算法
歐式距離
各個點(diǎn)的具體坐標(biāo)數(shù)值對結(jié)果會有比較大的影響。在推薦系統(tǒng)場景下,歐式距離一般用于需要從維度的數(shù)值大小中體現(xiàn)差異的相關(guān)度分析
例如以登陸次數(shù)和平均觀看時長作為特征時,余弦相似度會認(rèn)為(1,10)、(10,100)兩個用戶距離很近,但顯然這兩個用戶的活躍度是有著很大差異的,(10,100)這個用戶的價值更高,此時我們更關(guān)注數(shù)值絕對差異,應(yīng)當(dāng)使用歐氏距離
余弦距離
跟歐式距離的差別主要在于它對具體數(shù)值的差異并不敏感。一句話總結(jié)就是,雖然數(shù)值上確實(shí)有差異,但是兩者的x,y軸相對應(yīng)的數(shù)值的分值之差保持相近,所以兩者的相似度還是很高。余弦相似度更傾向于衡量兩者在方向趨勢上的差異,余弦相似度更多的適用于使用用戶對內(nèi)容評分來區(qū)分興趣的相似度和差異
常見向量索引
1) FLAT
也就是大家常說的暴力搜索,這種方式是典型的犧牲性能和成本換取準(zhǔn)確性,是唯一可以實(shí)現(xiàn) 100% 召回率的方式,同時可以較好地使用顯卡等異構(gòu)硬件加速。
2) Hash based
基于 locality sensitive hashing 將數(shù)據(jù)分到不同的哈希桶中。這種方式實(shí)現(xiàn)簡單,性能較高,但是召回率不夠理想。
3) Tree based
代表是 KDTree 或者 BallTree,通過將高維空間進(jìn)行分割,并在檢索時通過剪枝來減少搜索的數(shù)據(jù)量,這種方式性能不高,尤其是在維度較高時性能不理想。
4) 基于聚類的倒排
通過 k-means 算法找到數(shù)據(jù)的一組中心點(diǎn),并在查詢時利用查詢向量和中心點(diǎn)距離選擇部分桶進(jìn)行查詢。倒排這一類又擁有很多的變種,比如可以通過 PCA 將數(shù)據(jù)進(jìn)行降維,進(jìn)行標(biāo)量量化,或者通過乘積量化 PQ 將數(shù)據(jù)降精度,這些都有助于減少系統(tǒng)的內(nèi)存使用和單次數(shù)據(jù)計(jì)算量。
5) NSW(Navigable Small World)圖
是一種基于圖存儲的數(shù)據(jù)結(jié)構(gòu),這種索引基于一種樸素的假設(shè),通過在構(gòu)建圖連接相鄰的友點(diǎn),然后在查詢時不斷尋找距離更近的節(jié)點(diǎn)實(shí)現(xiàn)局部最優(yōu)。在 NSW 的基礎(chǔ)上,HNSW(Navigable Small World)圖借鑒了跳表的機(jī)制,通過層狀結(jié)構(gòu)構(gòu)建了快速通道,提升了查詢效率。
hnsw參考:https://www.pinecone.io/learn/series/faiss/hnsw/
k-means動態(tài)算法:
https://www.naftaliharris.com/blog/visualizing-k-means-clustering/
dbscan動態(tài)算法:
https://www.naftaliharris.com/blog/visualizing-dbscan-clustering/
向量數(shù)據(jù)庫對比
相比較其他向量數(shù)據(jù)庫,Milvus:
- 支持的索引類型較多
- 代碼開源,社區(qū)比較活躍,生態(tài)良好(工具)
- GO語言實(shí)現(xiàn),性能高
- 流批一體的設(shè)計(jì)模式,很好的解決了數(shù)據(jù)一致性、高可用等問題
https://zhuanlan.zhihu.com/p/364923722
https://www.jianshu.com/p/43cc19426113
milvus架構(gòu)
milvus的四大角色和十一組件
四大角色
- Access layer:主要功能驗(yàn)證請求參數(shù)和合并返回結(jié)果
- Coordinator service: 如系統(tǒng)大腦,分配任務(wù);包括集群拓?fù)涔芾?、?fù)載均衡、時間戳生成、數(shù)據(jù)聲明和數(shù)據(jù)管理等
- Worker nodes: 執(zhí)行具體工作節(jié)點(diǎn)
- Storage:數(shù)據(jù)存儲和持久化
十一組件
- proxy:驗(yàn)證請求參數(shù)和合并返回結(jié)果
- Root coordinator:處理DDL和DCL請求,如創(chuàng)建(刪除)collection、partition、index,以及TSO (timestamp Oracle)管理
- Query coordinator :管理查詢節(jié)點(diǎn)的拓?fù)浣Y(jié)構(gòu)和負(fù)載均衡,以及將growing的segmend切換到sealed
- Data coordinator:管理數(shù)據(jù)節(jié)點(diǎn)的拓?fù)浣Y(jié)構(gòu),維護(hù)元數(shù)據(jù),并觸發(fā)刷新、壓縮和其他后臺數(shù)據(jù)操作;如1)分配 segment 數(shù)據(jù)2)記錄分配空間及其過期時間3)Segment flush 邏輯 4)哪些 channel 被哪些 Data Node 消費(fèi)則需要 data coord 來做一個整體的分配
- Index coordinator:管理索引結(jié)點(diǎn)的拓?fù)浣Y(jié)構(gòu),建立索引,并維護(hù)索引元數(shù)據(jù)。
- Data node:訂閱日志代理獲取增量日志數(shù)據(jù),處理變更請求,將日志數(shù)據(jù)打包成日志快照,并存儲在對象存儲中。
- Index node:建立索引文件,存儲對象存儲中
- Query node: 訂閱日志代理檢索增量日志數(shù)據(jù),將它們轉(zhuǎn)化為growing segments,從對象存儲加載歷史數(shù)據(jù),并在向量數(shù)據(jù)和標(biāo)量數(shù)據(jù)之間運(yùn)行混合搜索。
- Meta storage(etcd):存儲了諸如collection schema、節(jié)點(diǎn)狀態(tài)、消息消費(fèi)檢查點(diǎn)等元數(shù)據(jù)的快照。此外,Milvus還使用etcd進(jìn)行服務(wù)注冊和健康檢查
- Object storage:存儲日志的快照文件、標(biāo)量數(shù)據(jù)和矢量數(shù)據(jù)的索引文件以及中間查詢結(jié)果。
- Log broker:負(fù)責(zé)數(shù)據(jù)流的持久化、可靠異步查詢的執(zhí)行、事件通知以及查詢結(jié)果的返回,還在Worker節(jié)點(diǎn)從系統(tǒng)故障中恢復(fù)時,確保增量數(shù)據(jù)的完整性。
proxy和其他系統(tǒng)所有主要組件的交互
milvus的數(shù)據(jù)模型
milvus屬性和關(guān)系數(shù)據(jù)庫類比
database:類比關(guān)系數(shù)據(jù)庫database, 2.2.9之后支持;為多租戶,一個租戶一個database設(shè)計(jì)
collection:類比關(guān)系數(shù)據(jù)庫表
Entity: 是傳統(tǒng)數(shù)據(jù)庫里面“一行”的概念
Field:字段
創(chuàng)建一個collection
# We're going to create a collection with 3 fields.
# +-+------------+------------+------------------+------------------------------+
# | | field name | field type | other attributes | field description |
# +-+------------+------------+------------------+------------------------------+
# |1| "pk" | VarChar | is_primary=True | "primary field" |
# | | | | auto_id=False | |
# +-+------------+------------+------------------+------------------------------+
# |2| "random" | Double | | "a double field" |
# +-+------------+------------+------------------+------------------------------+
# |3|"embeddings"| FloatVector| dim=8 | "float vector with dim 8" |
# +-+------------+------------+------------------+------------------------------+
fields = [
FieldSchema(name="pk", dtype=DataType.VARCHAR, is_primary=True, auto_id=False, max_length=100),
FieldSchema(name="random", dtype=DataType.DOUBLE),
FieldSchema(name="embeddings", dtype=DataType.FLOAT_VECTOR, dim=dim)
]
schema = CollectionSchema(fields, "hello_milvus is the simplest demo to introduce the APIs")
print(fmt.format("Create collection `hello_milvus`"))
hello_milvus = Collection("hello_milvus", schema, consistency_level="Strong")
參考:
https://raw.githubusercontent.com/milvus-io/pymilvus/master/examples/hello_milvus.py
shard、partition和segment
- shard:提升寫能力。有的文檔也稱channel,類似 Kafka 中的 topic。Shard 是指將數(shù)據(jù)寫入操作分散到不同節(jié)點(diǎn)上,使 Milvus 能充分利用集群的并行計(jì)算能力進(jìn)行寫入。
- partition:提升讀能力。MMS通過partition key區(qū)分libId
- segment :整個系統(tǒng)調(diào)度的最小單元,分為 Growing Segment 和 Sealed Segment
DML:任何傳入的插入/刪除請求都根據(jù)主鍵的哈希值被路由到shard,默認(rèn)情況下是兩個 Shard,推薦 Shard 的規(guī)模做到 Data Node 的兩到三倍。
DDL:僅分享一個shard。
virtual channel VS physical channel
- collection 在創(chuàng)建時可以指定 shard 的數(shù)目,一個 shard 代表一個 virtual channel
- 將消息存儲系統(tǒng)中的 channel 稱之為 physical channel
一個 proxy 都會對應(yīng)所有的 VChannel
多個 V channel 可以對應(yīng)到同一個 PChannel
一個data node/query node對應(yīng)多個PChannel
collection 級別的 VChannel可以很多,而且不同 collection 之間也可以共用 PChannel;從而利用消息系統(tǒng)高并發(fā)特性提高吞吐量。
https://zhuanlan.zhihu.com/p/517553501?utm_id=0
segment
Segment 在內(nèi)存中的狀態(tài)有 3 種,分別是 growing、sealed 和 flushed。 Growing:當(dāng)新建了一個 segment 時就是 growing 的狀態(tài),它在一個可分配的狀態(tài)。 Sealed:Segment 已經(jīng)被關(guān)閉了,它的空間不可以再往外分配。 Flushed:Segment 已經(jīng)被寫入磁盤
Growing segment 內(nèi)部的空間可以分為三部份:
- Used (已經(jīng)使用的空間):已經(jīng)被 data node 消費(fèi)掉。
- Allocated:Proxy 向 Data coord deletor 去請求 segment 分配出的空間。
- Free:還沒有用到的空間。
Sealed segment 表示這個 segment 的空間不可以再進(jìn)行分配。有幾種條件可以 seal 一個 segment:
- 空間使用了達(dá)到上限(75%)。
- 收到 flush collection 要把這個 collection 里面所有的數(shù)據(jù)都持久化,這個 segment 就不能再分配空間了。
- Segment 存活時間太長。
- 太多 growing segment 會導(dǎo)致 data node 內(nèi)存使用較多,進(jìn)而強(qiáng)制關(guān)閉存活時間最久的那一部分 segment。
數(shù)據(jù)存儲
minio中數(shù)據(jù)存儲
-
insert_log
bucketName/file/insert_log/ collectionId/ partitionId/ segmentId/ field_ids
featureId: 100
libId: 101
feature: 102 -
index_files
bucketName/file/index_files/ index build id/IndexTaskVersion/ partitionId/ segmentId/index file -
delta_log
bucketName/file/delta_log/ collectionId/ partitionId/ segmentId/unique ID -
stats_log
bucketName/file/stats_log/ collectionId/ partitionId/ segmentId/field_id
文件內(nèi)部內(nèi)容
@TODO
Binlog 里面分成了很多 event,每個 event 都會有兩部分,一個是 event header 和 event data。Event header 存的就是一些元信息,比如說創(chuàng)建時間、寫入節(jié)點(diǎn) ID、event length 和 NextPosition(下個 event 的偏移量)
INSERT_EVENT 的 event data 固定的部分主要有三個,StartTimestamp、EndTimestamp 和 reserved。Reserved 也就是保留了一部分空間來擴(kuò)展這個 fixed part。 Variable part 存的就是實(shí)際的插入數(shù)據(jù)。我們把這個數(shù)據(jù)序列化成一個 parquet 的形式存到這個文件里
https://zhuanlan.zhihu.com/p/486971488
milvus一些限制
https://milvus.io/docs/limitations.md
數(shù)據(jù)流向
Create Collection
- 會請求RootCoood,組織好格式,將數(shù)據(jù)存儲etcd
- 會組織成Msg格式,發(fā)送消息隊(duì)列
Flush Collection
主要內(nèi)容:1)將segment 由growing改為sealed狀態(tài),數(shù)據(jù)不可再寫入 2)將數(shù)據(jù)持久化到Object storage
兩個問題:
- sealed segments可能還在內(nèi)存,沒有持久化
解決:通過定期調(diào)用GetSegmentInfo請求DataCoord,直到所有sealed segments flushed - DataCoord 對sealed segments不再分配,但如何確認(rèn)所有分配的都被DataNode消費(fèi)了
解決:1)DataCoord收到凍結(jié)后應(yīng)該會記錄當(dāng)前的ts位點(diǎn)
2)DataNode從MsgStream消費(fèi)package時會向DataCoord 發(fā)送DataNodeTtMsg報告timestamp位點(diǎn)
3)DataCoord后臺線程解析該請求,判斷是否已經(jīng)消費(fèi)到凍結(jié)的位點(diǎn)
https://github.com/milvus-io/milvus/blob/master/docs/design_docs/20211109-milvus_flush_collections.md
Insert Data
- 請求proxy,進(jìn)行參數(shù)檢驗(yàn)
- Proxy向RootCoord請求Timestamp(全局時鐘)
- Proxy向DataCoord批量請求entities的segments以及primary keys
- 按照primary keys列進(jìn)行一致性哈希映射到shard X,確定其pchannel(c1,…c6)
- 構(gòu)造MsgStream對象<collection, partition, channel,…>并插入pchannel中
- DataNode(QueryNode)根據(jù)DataCoord配置從固定pchannel取出數(shù)據(jù),并按照collection聚類(flowgraph)形成log snapshot,并寫入s3等;并向DataCoord匯報binlog paths;
- DataCoord將寫入路徑記錄在etcd
參考:https://zhuanlan.zhihu.com/p/517553501?utm_id=0
Create Index
索引按照segment進(jìn)行構(gòu)建(索引異步刪除邏輯類似)
- RootCoord首先獲取出該collection所有sealed segments;
- 對每個segments,RootCoord復(fù)雜索引構(gòu)建任務(wù)管理:
- 向DataCoord獲取其Binlog paths(GetInsertBinlogPathsRequest)
- 向IndexCoord發(fā)送創(chuàng)建segment index請求(BuildIndexRequest)
- IndexCoord收到請求,對該segment任務(wù)進(jìn)行如下調(diào)度:
- 生成segment索引構(gòu)建任務(wù)(初始狀態(tài)位unissued)存入etcd,
- 根據(jù)負(fù)載均衡選擇IndexNode并發(fā)送請求
- IndexCoord監(jiān)控segment索引構(gòu)建任務(wù)狀態(tài)
- IndexNode segment索引構(gòu)建過程
- segment的binlogpaths中l(wèi)oad log snapshots到memory中
- 反序列化log snapshot為data blocks
- 內(nèi)存中構(gòu)建segment index
- index構(gòu)建完畢后序列化為data blocks,寫入index files(indexBuildID對應(yīng)一個segment):(indexBuildID/IndexTaskVersion/partitionID/segmentID/key)
- IndexNode修改etcd中index meta狀態(tài)
參考:
https://milvus.io/docs/data_processing.md
https://github.com/milvus-io/milvus/blob/master/docs/design_docs/20211227-milvus_create_index.md
Search
- 從Object Storage獲取Index Files中的flushed segment建立索引
- 也會從Growing Segments中建立索引,每個索引單位是一個segment
- Segments從Growing 到flushed 狀態(tài)轉(zhuǎn)換,也會有索引轉(zhuǎn)換
具體流程:
- query coord 會詢問 data coord。Data coord 因?yàn)橐恢痹谪?fù)責(zé)持續(xù)的插入數(shù)據(jù),它可以反饋給 query coord 兩種信息:一種是已經(jīng)持久化存儲了哪些 segment,另一種是這些已經(jīng)持久化的 segment 所對應(yīng) checkpoint 信息,根據(jù) checkpoint 可以知道從 log broker 中獲得這些 segment 所消費(fèi)到的最后位置
- query coord 會輸出一定的分配策略。這些策略也分成兩部分:按照 segment 進(jìn)行分配(如圖示 segment allocator),或按照 channel 進(jìn)行分配(如圖示 channel allocator)
- 分配給不同的 query node 進(jìn)行處理
- query node 就會按照策略進(jìn)行相應(yīng)的 load 和 watch 操作。如圖示 query node 1 中,historical (批數(shù)據(jù))部分會將分配給它的 S1、S3 數(shù)據(jù)從持久化存儲中加載進(jìn)來,而 streaming 部分會訂閱 log broker 中的 Ch1,將這部分流數(shù)據(jù)接入
knowhere
對于 Knowhere,不區(qū)分訓(xùn)練數(shù)據(jù)和查詢數(shù)據(jù)。對于每一個 segment,Knowhere 都是用該 segment 的全量數(shù)據(jù)做訓(xùn)練,再基于該訓(xùn)練結(jié)果插入全量數(shù)據(jù)構(gòu)建索引
Milvus如何解決單機(jī)架構(gòu)的一些問題
水平擴(kuò)容
milvus的索引內(nèi)存數(shù)據(jù),存儲在query node中,當(dāng)query擴(kuò)容(或縮容)時,由于索引文件持久化在對象存儲中,query coord會進(jìn)行重新分配,從而擁有水平擴(kuò)(縮)容的能力
數(shù)據(jù)丟失
插入的數(shù)據(jù),只要寫入消息系統(tǒng),就不會丟失;索引數(shù)據(jù)、插入日志也持久化到了對象存儲中
數(shù)據(jù)一致性
Milvus每一條 insert message 中都有分配了一個時間戳,如果 service time 大于 query message 中的 guarantee timestamp,那么就會執(zhí)行這個查詢;從而通過配置,達(dá)到不同級別的數(shù)據(jù)一致性
如何使用 Milvus 向量數(shù)據(jù)庫實(shí)現(xiàn)實(shí)時查詢
效果
Milvus針對一個segment構(gòu)建一個索引,最后proxy合并檢索結(jié)果,默認(rèn)一個segment 1g,從而避免單個索引過大導(dǎo)致效果問題
helm安裝部署及升級
開源chart
# Add Milvus Helm repository.
$ helm repo add milvus https://milvus-io.github.io/milvus-helm/
# Update charts locally.
$ helm repo update
# show chart
helm show chart milvus/milvus
# pull chart
helm pull milvus/milvus
prometheus+grafana監(jiān)控
https://milvus.io/docs/monitor.md文章來源:http://www.zghlxwxcb.cn/news/detail-718921.html
參考
https://zhuanlan.zhihu.com/p/473617910
https://zhuanlan.zhihu.com/p/491030589
https://zhuanlan.zhihu.com/p/500551056
https://zhuanlan.zhihu.com/p/486703915
https://zhuanlan.zhihu.com/p/486971488
https://zhuanlan.zhihu.com/p/502880424
https://zhuanlan.zhihu.com/p/506698319
https://www.modb.pro/db/590924文章來源地址http://www.zghlxwxcb.cn/news/detail-718921.html
到了這里,關(guān)于向量檢索庫Milvus架構(gòu)及數(shù)據(jù)處理流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!