顯示具有 Vespa 標籤的文章。 顯示所有文章
顯示具有 Vespa 標籤的文章。 顯示所有文章

2024年5月5日 星期日

Matryoshka Representation Learning (MRL) 與 Vespa

這幾天經由 Vespa 的文章,讀到 OpenAI 在今年(2024 年)二月份的一篇在解說 MRL (Matryoshka Representation Learning) 的文章,看起來真的相當有趣!不過我並不是這個領域的專業,所以細部的內容我也沒有完全理解,這篇筆記大體只是我自己在應用上的基本理解。如果內容有誤也歡迎留言提醒。😇

MRL 是什麼?

Matryoshka doll 就是俄羅斯娃娃,而 Matryoshka Representation Learning 就是一種利用類似俄羅斯娃娃的特性(小的娃娃可以被裝進大的娃娃裡)做出來的 Embedding 方法,或者說是東西的表達手法。

那麼 MRL 跟其他 Embedding 方法最大的差異是什麼呢?就我目前的理解,最大的差異就是上述提到的,它具有俄羅斯娃娃的特性。具體來說,就是小的 Embedding 可以被放在大的 Embedding 裡,或者說是大的 Embedding 切割掉尾巴後,就幾乎等於是小的 Embedding。這個特性最主要的優勢,是在於我們可以不用針對不同維度的 Embedding 分別儲存不同的 Embedding,而是可以統一存一個 Embedding,在需要高維的時候使用 Embedding 裡的比較多內容、需要低維(更快更低成本)的時候則使用 Embedding 裡的一部份內容即可。

俄羅斯娃娃

Vespa 與 MRL

那麼回到 Vespa 的使用情境,MRL 可以帶來什麼效果呢?首先如同 MRL 文章在上圖中提到的,MRL 會很適合用在 multi-phase ranking 的場合,例如先使用低維度來找出較符合結果的 candidates,然後第二階段時再針對這些 candidates 使用高維度來做更精準的計算,找出最佳的結果。

Vespa 二月份的文章中,首先提到他們做了一點基本的實驗,發現 MRL 產生的 Embedding 在使用相同模型的情況下,產生 8-dimension 跟 3072-dimension 的 Embedding,確實中間只差了 scale 而已,本質上非常相似。接著他們實際實驗了把 3072-dimension 的 Embedding,裁掉尾巴並只留下最前面的 8-dimension,然後去跟一開始就用 8-dimension 產生的 Embedding 做 cosine similarity 比較,發現兩者的相似度高達 99.99999999999996%

最後,Vespa 團隊用 COVID-19 的資料集做了實際的搜尋實驗,分別有以下幾種:

  • query_exact - 使用完整的 3072-dimension 並且不使用 ANN,也就是做最精準的語義搜尋。
  • query_256 - 改為使用 256-dimension 的低維度 Embedding,同樣也不使用 ANN。
  • query_256_ann - 使用 256-dimension 的低維度 Embedding,同時使用 ANN 做語義搜尋。
  • query_rerank - 使用 256-dimension 的 Embedding 作為 1st-phase,選出 top 100,然後用 3072-dimension 作為 2nd-phase
    結果如下:
run query_exact
.................................................. avg query time 2.7918 s
run query_256
.................................................. avg query time 0.3040 s
run query_256_ann
.................................................. avg query time 0.0252 s
run query_rerank
.................................................. avg query time 0.0310 s

query_exact     ndcg_cut_10: 0.7870
query_256     ndcg_cut_10: 0.7574
query_256_ann     ndcg_cut_10: 0.7552
query_rerank     ndcg_cut_10: 0.7886

先看效能的部份,從 3072-dimensions 改為使用 256-dimensions 可以大幅加快搜尋速度,query time 只有原本的大約 11% 而已。而使用 ANN 的話,跟最開始的 query_exact 比較,甚至是只用了不到 1% 的時間!但效能提昇了,搜尋結果變差了多少呢?從 NDCG 來看,差異從 0.787 下降到 0.755 ,其實差異並沒有非常顯著。

如果我們直接比較 query_exactquery_rerank 的話,得到的結果是 query time 是原本的 1.1% ,而 NDCG 幾乎一樣!從數據來看,MRL 的潛力真的相當大 😍

2023年10月23日 星期一

Vespa 的 parent document + weightedSet 撞牆筆記

簡要紀錄,最近遇到在 parent document 裡使用 weightedSet 做資料篩選的時候,本來還沒加 weightedSet 時,latency 是大約 25ms,加了 weightedSet 之後變成大約 730ms。

以下是當時的 schema 設定,簡要來說就是有個讓多個 child document 共用的欄位,叫做 sharedField,它被 child document 繼承時,欄位名字會叫做 fieldInParentDoc

# Schema in parent document.
field sharedField type weightedset<string> {
    indexing: attribute
    attribute: fast-search
}

# Schema in child document
field id type string {
    indexing: attribute
    attribute: fast-search
}

field parentReference type reference<parent_doc> {
    indexing: attribute
}

import field parentReference.sharedField as fieldInParentDoc {}

接著,在查詢時使用的 query 大約是這樣:

SELECT * FROM * sources WHERE (id contains "my_id_1" OR id contains "my_id_2") AND weightedSet(fieldInParentDoc, {"my_key":1});

實際上 ID 欄位是唯一值,並且 query 當中會有 5 個 ID,所以搜尋結果一定只會有 5 筆紀錄。理論上這應該會是很快的 query,但在加上後半段的 parent document 篩選條件後,速度就爆增了 28 倍。

不過如果幫這個 parent document 欄位設定 rank: filter,如下所示,讓它改成 bit vector 的形式,速度會變快不少。具體來說,專案裡測試的結果是會從 730ms 變成約 90ms。雖然還是很慢,但至少勉強還過得去…。

# Schema in parent document.
field sharedField type weightedset<string> {
    indexing: attribute
    attribute: fast-search
    rank: filter
}

2023年10月11日 星期三

在 Vespa 使用語義搜尋(一):簡要的基本知識

在搜尋的世界裡,語義搜尋(semantic search)是近代非常熱門的作法,它可以做到一些傳統的文字搜尋(text matching)做不到的事情。概念上來說,語義搜尋就是把句子的語義萃取出來,轉換成數學裡的向量來表達,然後再利用向量距離來衡量兩個句子的相關程度。這篇文章會簡要紀錄一點語義搜尋時,可能需要知道的基本知識。不過由於這裡面比較多跟資料科學比較有關的知識,在這方面我是個外行,所以這篇基本上都會比較以偏外行的視角在看待這些知識。

Vector Search

技術上來說,語義搜尋也可以稱之為 Vector Search,畢竟語義搜尋是透過向量來做搜尋。而要做語義搜尋,第一要務就是「要如何把一個字串轉換成向量」?這個轉換很大幅度決定了語義搜尋的結果,畢竟轉換成什麼樣的向量,就會決定它跟什麼東西比較近了。其中,把文字轉換成向量的行為,稱為詞嵌入(Embedding)。

Embedding 有很多種技術,近年來比較著名的演算法,就我所知大概是 BERT、FastText 等等的。具體來說,依據我們選擇的 embedding 演算法,會產生出不同維度的向量,這些向量放在向量空間中,就可以接著被用來計算點跟點的距離,並用以決定兩段文字是否相關。

Transformer

Transformer 是 Google 在 2017 年發表的神經網路架構,就我的理解是,這個架構最大的不同是讓神經網路的計算能夠平行化,使得大型模型的訓練變成了可能。目前比較常聽到的像是 GPT(Generative Pre-trained Transformers)、BERT(Bidirectional Encoder Representations from Transformers) 等模型,都是基於 Transformer 架構開發出來的。

Hugging Face

由於市場上現在有很多種模型架構和應用環境,例如有些模型用 TensorFlow、有些用 PyTorch 等等。Hugging Face 是用來提供統一的 API 介面,使得不同架構的模型能夠互通。細節可以參考文章

ONNX

ONNX(Open Neural Network Exchange)是一種神經網路的資料交換格式,用來讓不同架構(例如 Google 的 Tensorflow、Meta 的 PyTorch、微軟的 CNTX 等)訓練出來的模型能夠互通在其他架構。不過據說 Tensorflow 因為發展較早,已經有自己成熟的環境,因此並不支援 ONNX,但有工具能夠將 Tensorflow 模型轉換成 ONNX 格式,如 tf2onnx

Nearest Neighbor Search

NN(Nearest Neighbor)是在向量空間中尋找最近的點的問題。比較著名的解法是 KNN(K-nearest neighbor algorithm)。

Approximate Nearest Neighbor Search

ANN(Approximate Nearest Neighbor Search)是改良版的 NN,目標是用比較粗略的方式,在向量空間中找出距離最近的點。Vespa 在使用 ANN 去計算尋找最近的點時,使用的演算法是 HNSW。

2022年4月9日 星期六

Vespa timeout 機制

Vespa 在處理查詢的時候,有預設的 timeout 機制,能夠在時間不夠的時候將既有已經收集到的結果吐出,而不是放棄既有的結果並回覆 504 timeout。這樣的行為其實就是現代的 reactive system 的思維。這裡會簡要地介紹 timeout 的機制 [1],並且提一下最近遇到的實例。

2022年2月5日 星期六

準備 Vespa 測試環境

其實本來想紀錄一下建 Vespa container 的過程,但翻了一下之前的文章 [1],發現其實雖然細節有點不同,但大體上也是大同小異,這篇就簡單寫了,畢竟內容其實就跟 Vespa 的 Github 上寫得差不多 😆。

這篇其實算是個前置作業,目的是因為最近想紀錄一點 Vespa 的實驗數據,不過畢竟不能拿公司的數據放部落格(其實也不是公司不允許,單純只是要申請跟審核感覺很麻煩,我懶得弄 🙈),所以想要用簡單的 Vespa container 來做測試。基於這個原因,需要在自己的電腦準備一個 Vespa 環境,並且需要塞一些合理的測試資料進去。Vespa 團隊在他們的 Github [2] 上有準備一個 e-commerce 的範例,看起來還不錯,所以預計會先拿這個來做初始環境的建置。

2022年1月25日 星期二

Vespa 的新功能 hash dictionary

在翻閱 Vespa 的部落格 [1] 時,看到在 2021 年 5 月時,Vespa 新增了 hash dictionary 的功能,所以就來紀錄一下這個功能的細節。

hash dictionary 是什麼?

在 Vespa 的設計中,當欄位被設定為 attribute 時,可以另外加上 fast-search 的設定,讓 Vespa 自動幫這個欄位建立 index 以加快搜尋速度。原本 Vespa 的 fast-search 只能夠使用 b-tree 的資料結構來建立,但現在我們可以選擇使用 hash table 的資料結構來建立 index,在特殊的情境下能夠獲得比 b-tree 更好一點的效能。

hash dictionary 的限制

目前測試發現 hash dictionary 需要設定為 cased,而且必須要同時在 dictionarymatch 兩個設定上都加上 cased 才能通過檢查。不過這點我覺得在文件 [2] 上並沒有很明確地點出…。

field id type string {
    indexing: summary | attribute
    attribute: fast-search
    dictionary {
        hash
        cased
    }
    match: cased
}
hash dictionary 的效果

要比較效果的話,首先需要先看一下它的比較對象,也就是預設的 btree dictionary。對工程師來說,看到 b-tree 跟 hash 兩個關鍵字,應該大概就知道差別是什麼了!簡要來說就是 O(logn) 跟 O(1) 的差別 XD。不過除此之外,由於上述的 hash dictionary 的限制,在 Vespa 上設定 hash dictionary 還會另外衍生出 case-sensitive 的議題需要考慮。

首先先看一下 btree 的狀況,如果使用以下的設定的話,btree 的預設行為是 uncased,意味著 "bear" = "BEAR" = "Bear"

field id type string {
    indexing: summary | attribute
    attribute: fast-search
}

實際使用 [3] 建立出來的測試環境來測試的話,裡面有一個 asin: "B00GQ22Y6Y" 的 document,內容長這樣:

{
    "pathId": "/document/v1/item/item/docid/B00GQ22Y6Y",
    "id": "id:item:item::B00GQ22Y6Y",
    "fields": {
        "title": "Trendy Style Hand-knit Warm Lining inside Winter Bucket Hat w. Cute Flower-Purple #H01",
        "asin": "B00GQ22Y6Y",
        ...(skipped)...
    }
}

此時用以下兩個 YQL 都能夠查到這個 document。這主要是因為預設的設定是 uncased,因此不管大小寫都可以順利查到結果。

SELECT * FROM item WHERE asin contains "b00gq22y6y";
SELECT * FROM item WHERE asin contains "B00GQ22Y6Y";

不過由於使用 hash dictionary 時,會需要設定 cased 屬性,導致更換成以下的 hash dictionary 時,狀況就會不太一樣了:

field id type string {
    indexing: summary | attribute
    attribute: fast-search
    dictionary {
        hash
        cased
    }
    match: cased
}

這時其實結果是 asin contains "b00gq22y6y" 可以查到資料,但 asin contains "B00GQ22Y6Y" 反而查不到…。這結果其實蠻出乎我的意料,不知道是不是 bug 或者是使用方式不正確之類的。

參考資料
  1. Vespa Product Updates, May 2021
  2. Schema Reference – dictionary
  3. 準備 Vespa 測試環境

2021年9月24日 星期五

Vespa 不能接受的 unicodes

筆記,Vespa 不能接受 /u0000 ~ /u001F 這些 unicodes,如果寫入的文字有包含他們的話,會得到下列的錯誤訊息。

The string field value contains illegal code point 0x0

2021年7月18日 星期日

Vespa weighted set

Vespa 有個 weightedset 的型態,我個人其實覺得 Vespa 的文件在針對 weightedset 的描述有點難懂,所以這邊會用範例來整理一下 weightedset 的實際效果。

2020年10月19日 星期一

在 Vespa 使用 Parent/Child 時的限制

Parent/Child 的關聯在實務運用上挺方便的,可以讓有重複的內容統一放在 Parent document 上,使得 Child document 只要單純繼承 Parent 就可以一併繼承那些重複的內容。不過其實它相應的限制其實也頗多,而且這個部份在 Vespa 的文件上寫得並沒有非常清楚。

難以繼承複雜的結構

近期連續遇到的狀況就是,在 Search Definition 上定義欄位時不一定會出現錯誤,但結果實際上繼承是無法生效的。舉例來說,Parent document 上如果是 Array<StructA>,其中 StructA 裡面又包含了 StructB 的狀況時,結果是部署時並不會被檢查出錯誤,但實際上 StructB 無法正常地被 Child document 繼承到。

{
    "weapons": [{
        "type": "sword",
        "material": "iron",
        "price": 300,
        "position": {
            "longitude": 0.0,
            "latitude": 0.0
        }
    }, {
        "type": "sword",
        "material": "mythril",
        "price": 9000,
        "position": {
            "longitude": 0.0,
            "latitude": 0.0
        }
    }]
}

以這個例子來說,Search Definition 可能會長這樣:

struct Weapon {
    field type type string {}
    field material type string {}
    field price type double {}
    field position type map<string, string> {}
}

field weapons type array<Weapon> {
    indexing: summary
    struct-field type { indexing: attribute }
    struct-field material { indexing: attribute }
    struct-field price { indexing: attribute }
    struct-field position.key { indexing: attribute }
    struct-field position.value { indexing: attribute }
}

在這種狀況下,deployment 的檢查都會通過,但實際上 position 這個欄位是無法正常被繼承的。真的進行 Child document 的搜尋時,會發現 position 這個欄位一直都不會出現,但除了 position 以外的其他幾個欄位則都會正常出現。同時如果對 Parent document 呼叫 Document API,還是會看到包含 position 在內的所有欄位都正常地有值。

除了這個例子以外,另外嘗試在 Parent document 有非巢狀但格式有點複雜的欄位型態 map<string, array<string>> 時,結果則是在驗證階段直接拋出錯誤訊息:

For search 'childDocument', import field 'importedField': Field 'mapWithStringArray' via reference field 'weaponReference': Is not an attribute field. Only attribute fields supported

這個部份 Vespa 已經更新了文件 [3],稍微更詳細一點地說明了可支援的型態。可以參考 Vespa 的文件 [1-2]。

踩了幾次地雷以後,簡要來說,就是不要想在 Parent document 上擺什麼複雜的結構,除非那個欄位沒有要被 Child document 繼承,否則還是都用基本型別,最複雜大概就只到 array<struct> 或者 map<string, string> 這種就好了…..。

無法使用 index

因為 Parent document 是 global document,意味著它會被放在所有 content node 的記憶體中,因此它沒有辦法使用需要用到 disk 的 index。更確切地說,是 Child document 在繼承 Parent document 的欄位時,它只能繼承到被定義為 attribute 的欄位而已,index 的欄位無法被繼承。而這個限制也同時導致了繼承下來的欄位沒辦法做 partial match,只能夠作為 filter 來使用而已。

參考資料
  1. Parent/Child
  2. import-field
  3. Clarify restrictions that apply for imported field types.

2020年4月8日 星期三

attribute 的記憶體需求

Vespa 的欄位如果 index-type 被宣告為 attribute 的話,就會直接被放在記憶體上,因此實務上會需要估計到底需要多少記憶體才能撐住所有的資料的問題。

2020年3月1日 星期日

Vespa 入門(一):在本機建立 containerized Vespa

因為在公司持續地在用 Vespa,所以打算寫個系列文章慢慢紀錄 Vespa 的一些使用、調校經驗。(雖然說過去寫的系列文章大多…寫到第二或第三篇就………。)

本篇會紀錄在本機啟動 Vespa 的 container 的方法,通常這麼做可以在開發階段用來測試 Vespa 的語法。可以參考 Vespa 官方文件 [1]。

環境準備

依照 [1] 的描述,Docker 環境最重要的就是需要有至少 6GB 的記憶體。而在我的環境中,我是在 Windows 10 上跑 Docker 的,因此至少要是個有支援 Hyper-V 的 Windows 版本,才能夠執行 Docker。

取得 Vespa 範例

首先,Vespa 有提供一些簡單的範例 Application,可以從以下的 Github 取得。裡面有很多資料夾,包含了數種在 Vespa 官方文件中會講到的例子。

git clone https://github.com/vespa-engine/sample-apps.git

這裡假設我把這個 repository 放在 G:\git 裡。

建立並啟動 Vespa container

要啟動 Vespa 之前,我們需要執行兩個動作。第一個是先啟動一個 Vespa 的 container,這個步驟只會準備好 Vespa 需要的執行環境而已。接著第二個是部署 Vespa Application,這個動作就像是在 RDBMS 裡執行 CREATE DATABASE 一樣,是讓 Vespa 準備好 schema 並且真的啟動伺服器。

docker run --detach --name sample-vespa --hostname vespa-container --privileged --volume g:\git\vespa-sample-apps:/vespa-sample-apps --publish 8080:8080 vespaengine/vespa
docker exec sample-vespa bash -c 'curl -s --head http://localhost:19071/ApplicationStatus'

首先如前所說,我們先以 vespaengine/vespa 的 image 啟動一個 Vespa container,這裡我們把這個 container 命名為 sample-vespa,並且把資料夾 g:\git\vespa-sample-apps 掛到 container 的 /vespa-sample-apps 路徑上,方便之後我們在這個 container 中部署與啟動 Vespa。

container 開好以後,可以透過第二行指令來確認 Vespa 的狀態。不過因為 container 雖然綁了 port,但卻還沒有啟動任何服務,因此我們要確認狀態只能利用在 container 裡執行 curl 指令來確認。如果正常的話會獲得類似這樣的回覆:

HTTP/1.1 200 OK
Date: Sun, 01 Mar 2020 11:31:41 GMT
Content-Type: application/json
Content-Length: 9232

接著就是要在裡面部署和啟動指定的 Vespa Application 了。

docker exec sample-vespa bash -c '/opt/vespa/bin/vespa-deploy prepare /vespa-sample-apps/album-recommendation-selfhosted/src/main/application/ && /opt/vespa/bin/vespa-deploy activate'
curl -s --head http://localhost:8080/ApplicationStatus

這邊我們先部署 album-recommentation-selfhosted 這個 Vespa Application。因為 Vespa 的 Search Definition 是放在 src/main/application 裡的,因此在部署的時候要指定路徑是這個資料夾。第一個指令會獲得類似這樣的反應,表示已經順利地啟用 Vespa Application。

Uploading application '/vespa-sample-apps/album-recommendation-selfhosted/src/main/application/' using http://localhost:19071/application/v2/tenant/default/session
Session 3 for tenant 'default' created.
Preparing session 3 using http://localhost:19071/application/v2/tenant/default/session/3/prepared
WARNING: Host named 'vespa-container' may not receive any config since it is not a canonical hostname. Disregard this warning when testing in a Docker container.
Session 3 for tenant 'default' prepared.
Activating session 3 using http://localhost:19071/application/v2/tenant/default/session/3/active
Session 3 for tenant 'default' activated.
Checksum:   71cf99ec40a0a08c8edbd1aa3cee03ed
Timestamp:  1583062394458
Generation: 3

再使用第二個指令確認 Application 狀態時,可以獲得:

HTTP/1.1 200 OK
Date: Sun, 01 Mar 2020 11:34:39 GMT
Content-Type: application/json
Transfer-Encoding: chunked
參考資料
  1. Vespa: Quick Start

2019年9月18日 星期三

Vespa 的 keep-rank-count 和 rerank-count

在文件 [1] 上對於 keep-rank-count 的描述如下:

How many documents to keep the first phase top rank values for. Default value is 10000.

而對於 rerank-count 的描述則是如下:

Specifies the number of hits to be re-ranked in the second phase. Default value is 100. This can also be set in the query. Note that this value is local to each node involved in a query.

其實很難看懂這到底在說什麼…。

事實上,它們的功能是應該要放在一起看待的。Vespa 的 ranking 有分 first-phase 和 second-phase,其中 first-phase 會肩負減少資料量的工作,以確保當 Vespa 內存放的資料非常多的時候,不需要對所有資料都做完所有運算。keep-rank-countrerank-count 都是適用在 first-phase 階段的參數,以下分別討論它們的效果,會比較好理解。

keep-rank-count 造成的影響是當 Vespa 的每一個 search node (a.k.a. content node) 在 first-phase 取資料時,最多只會依據 rank profile 的計算去取出 keep-rank-count 指定數量的分數(和 docid),例如預設值 10,000 代表的是每個 nonde 都會暫存 10,000 筆資料的分數(和 docid),而超出這 10,000 筆資料的部份,分數就會被忽略(即只會留下 docid )。因此走到 second-phase 時就只會有 10,000 筆資料能夠被後續做更複雜的處理。這也會反應在 Vespa 最後回覆的資料中,如果是超出 keep-rank-count 的部份的資料,分數都會顯示 –infinite,代表因為分數已經在處理過程被丟棄,因此在結果中會不知道分數是多少。

rerank-count 則是決定離開 first-phase 進入到 second-phase 時,會留下多少筆資料。需要注意的是,這裡的數字一樣都是每個 search node 計算的。舉例來說,如果有 10 台 search node,rerank-count 設定為 100,那麼總共就會有 1,000 筆資料會被送進 second-phase。

keep-rank-countrerank-count 兩個結合起來,假設我的 rank profile 設定是 keep-rank-count=100, rerank-count=500,同時現在有個搜尋的指令,會搜尋到 1,000 筆資料,那麼結果會是如何呢?…..結果會是 second-phase 會看到 500 筆資料,但這 500 筆當中,只會有 100 筆的 relevance score 存在,其他 400 筆的 relevance 會顯示 -infinite

參考資料

  1. Search Definition Reference

2019年7月28日 星期日

Vespa 的資料與索引型態

Vespa 裡面支援了許多資料型態(data type),包括 int、long、byte、double、boolean、string、struct(物件)、collection(例如 array、weighted set)等等,可以參考官方文件 [1],這些資料型態最主要的影響在於存放時所消耗的空間大小。而關於使用時的使用模式,則主要是來自於設定的索引型態(index type)來決定。

索引型態

索引型態主要分為 attribute 和 index 兩種。概略來說,可以直接把它們看成是「存放在記憶體」和「存放在硬碟」的差別。在 Vespa 的設計裡,好像基本上會假定字串以外的型態都放在 attribute 比較好的樣子,因為如果要以資料欄位作為像是排序的依據的話,資料欄位必須要是 attribute index 才行。

attribute

attribute 的欄位都會被存放在記憶體裡,意味著其實如果可以的話,最好不要把長度無法預測的資料欄位設定為 attribute,因為這樣會導致身為稀缺資源的記憶體被大量耗用在存放這些字串。不過實際運作時,因為 OS 會有虛擬記憶體的概念,因此不一定會真的完全保證 attribute 的欄位就一定是被放在記憶體上,如果想要確保這件事,首先就得確保 OS 不會使用到虛擬記憶體。

設定為 attribute 的欄位,就能夠執行以下的這些操作:

  • grouping
  • sorting
  • word-match search
  • numerical search
  • prefix search
  • ranking functions
  • document summaries
  • document reference

在對 attribute 的欄位做搜尋的時候,原則上 Vespa 只會做線性搜尋,只是因為搜尋時是對記憶體內的資料做,所以一般狀況下效率不會太差。但當有必要的時候,對 attribute 欄位設定 fast-search 可能是蠻重要的事情,這會讓 Vespa 對該欄位建立 B-tree 索引,就像在 RDBMS 上對欄位建索引一樣。到這裡,某種程度上可以感覺到 Vespa 對這種基本型態的處理其實算是比較粗糙的,效能很大程度是直接依賴「資料放在記憶體上」的這件事。所以如果沒有要做什麼比較複雜的排序或計算之類的話,基本資料的儲存並不是 Vespa 的強項,把資料放在 RDBMS 裡可能可以獲得相差不遠的效率但大幅下降的維運成本。

index

index 的欄位是存放在硬碟中的 [2-3],代表資料被定義為 index 的話,儲存成本相較於 attribute 會比較低,但同時存取速度也會比較慢。index 欄位只能用在字串上(這又突顯了 Vespa 對於基本型態的處理比較粗糙的事實),除了存放位置以外,index 跟 attribute 最大的不同就是 index 的欄位都會做 normalization 和 tokenization [3]。換言之,index 主要(唯一?)的使用情境是在於做各種字串處理或者字串搜尋。

Search Definition 的定義

在 Search Definition 上定義資料的索引型態時,attribute 和 index 並非是只能設定其中一個,其實可以同時設定兩個,只是同時設定時有個有點有趣的小細節。因為索引型態是透過 pipe 連接起來的,pipe 連接的意思就是會依序執行,所以它的結果會很像是使用 Java 8 的 stream API 那樣。舉例來說,如果我對某個欄位設定 indexing: index | attribute,這表示的是這個欄位的資料會先進行 index,在這個過程中會被 tokenize 等等,接著被 tokenized 的資料再送去 attribute 做索引!換言之,在一般狀況下如果我們想要同時設定 attribute 跟 index,通常我們應該會想設定 indexing: attribute | index,而不太會是反過來放的狀況。同時,當我們設定 indexing: attribute | index 時,Vespa 的搜尋會去搜尋 index 而不會搜尋 attribute [4]。(不過這樣的話那幹麻要支援同時寫 attribute 和 index 呀….?)

參考資料
  1. Search Definition Reference: field
  2. Proton: index
  3. Search Definition Reference: index
  4. Search Definition Reference: indexing
  5. Performance And Tuning: Document Attributes
  6. Performance And Tuning: Attribute Memory Usage

2019年5月14日 星期二

Vespa 的設定檔用途

Yahoo 的 Vespa 大數據系統 [1],是 Yahoo 開源的系統,主要功能可以想像是 Elasticsearch 的對照。不過在基本使用上,Vespa 跟 Elasticsearch 稍微有點不太一樣的概念。首先 Vespa 包含了整個環境佈署的程序,也就是在討論「設定 Vespa」時,其實是在討論如何在環境中開啟一組 Vespa 的叢集,並且「Vespa 叢集」在 Vespa 的概念當中是被稱為 Application。換言之,在 Vespa 的世界裡,它是假設了我們要先有一整群 IaaS 的伺服器在等待 Vespa 的 Application 被佈署上去,或者說等待將 IaaS 環境中的節點被分配來安裝 Vespa。等到 Vespa Application 成功被佈署了,之後才會開始討論如何操作 Vespa。

要佈署 Vespa 需要指派一些設定檔,簡單紀錄一下其中一些設定檔的用途。其中因為整個 Vespa 佈署的設定除了要定義資料的 schema、叢集的設定以外,還需要 pom.xml 的搭配,所以通常直接開一個 Java 專案專門用來做 Vespa 佈署設定,會比較自然一點點。

Application Package

Application Package [2] 一般指的是 /src/main/application 這個資料夾,資料夾內的東西基本上都是用來指定要如何進行 Vespa 佈署的。

  • services.xml [3]:叢集的配置檔。命名上感覺是說要在這裡定義要佈署的 Vespa 服務是要用來提供什麼服務的,不過具體而言就是在指定一個 Vespa 叢集,例如要用幾個節點建置叢集、叢集的節點當中哪些節點用來存放哪個資料、資料是否需要 replication 等等的。其中,會需要指定哪個資料放在哪個節點,是因為 Vespa 的佈署包含了 nginx,因此這個設定同時會指派 nginx 做資料的路由。
  • searchdefinitions/*.sd [4]:定義在 Vespa 當中存放的資料的 schema。schema 的主要用途是告訴 Vespa 該用什麼方法來處理資料,例如該建立什麼樣的索引、該把什麼欄位放在記憶體中等等。
參考資料
  1. Vespa
  2. Application Package
  3. Services Configuration
  4. Search Definitions