使用者對於搜尋引擎最關注的兩方面一是召回,即滿足條件的doc全部可以被召回;二是排序,即在滿足條件的文檔中將相關度最高的文檔優先召回。其中,二往往是需要根據使用者的實際業務需求進行調整,因此就需要使用者對OpenSearch在排序方面提供的能力有一定的瞭解,本文將詳細介紹OpenSearch在排序方面的能力,並且列舉一些常見情境如何通過OpenSearch的排序能力實現。
sort子句與排序策略的關係
簡單來說sort子句在OpenSearch中代表全域排序,而排序策略可以理解為sort子句中的一個層級的排序,排序策略是通過系統內建的函數結合運算式形成一種複雜的文檔算分邏輯來實現使用者複雜的業務情境,但最終參與排序的還是排序策略中運算式算出的最終得分。舉個例子,某個業務希望通過文檔的新舊程度進行排序,同時新舊程度相同的文檔,可以根據文檔的相似程度進行二級排序,此時實現使用者上述需求,就需要通過sort子句同時結合排序策略,假設使用者業務表中有一個欄位為create_time,同時檢索的欄位為name,則可以在sort子句中加入:
sort=-create_time;-RANK同時在基礎排序設定static_bm25() 函數,在業務排序中設定text_relevance(name)函數即可。(排序策略的配置步驟參考排序策略配置)。
這裡的RANK就表示擷取排序策略的得分,而“-” 表示倒序,“+”表示正序。
系統預設在不配置sort子句的情況下用-RANK作為排序條件,而如果配置了sort子句,在需要排序策略分排序的時候需要顯示的寫入-RANK,否則系統將不會自動引入排序策略分作為排序條件。
為了更形象的表達sort子句和排序策略的關係,這裡以一個案例的方式說明:
假設OpenSearch有一個應用,應用結構為:
欄位 | 類型 | 索引 |
id | int | 關鍵字 |
name | text | 中文通用 |
age | int | 關鍵字 |
為name設定了一個基礎排序,運算式內容如下:

為name設定了一個業務排序,運算式內容如下:

同時在檢索時,配置sort子句為:
sort=age;-RANK開啟排序明細查看算分詳情:

首先可以看到排序分為13,10000.2259030193,因為sort子句設定的是age;-RANK,因此13表示文檔欄位age的值,而10000.2259030193表示排序策略的最終得分,OpenSearch會先根據age的值進行正序排序,age值相同的文檔再根據排序策略的最終得分倒序排序。
再看排序公式:
FirstRank:
expression[static_bm25()], result[0.496452].
SecondRank:
expression[text_relevance(name)], result[0.225903].FirstRank表示基礎排序得分,SecondRank表示業務排序得分,最終的排序分為10000.2259030193,為什麼最終排序策略得分為10000.2259030193而不是[0.496452+0.225903] 在下一節會詳細說明。
由上述現象,可以得出結論:在OpenSearch中sort子句類似於SQL中order by的功能,可以直接通過文檔中的屬性欄位進行排序,也支援複雜的排序策略進行算分,而排序策略又有著特有的函數支援和算分規則,最終根據sort子句的“+”,“-”,以及排序欄位控制文檔排序方式以及文檔得分。
排序策略說明
排序策略打分原理

對於排序策略的算分分為兩個階段:基礎排序和業務排序,通過query召回並通過filter過濾後的文檔,首先進入基礎排序,根據基礎排序運算式海選出文檔得分較高的文檔,然後取出TOP N個結果再按照業務排序運算式進行精細算分,最終返回排序策略的最終得分。算分規則如下:
若只配置了基礎排序,則文檔得分為(10000+基礎排序運算式計算的結果),總分最大為20000,超過20000結果仍為20000。
若只配置了業務排序,則文檔得分為(10000+業務排序運算式計算的結果),總分無上限。
若同時配置了基礎排序和業務排序,那麼進入業務排序的文檔最終得分為(10000+業務排序運算式計算的結果),其餘文檔最終得分為(10000+基礎排序運算式計算的結果,總分最大為20000,超過20000結果仍為20000)。
再結合上一節排序算分為:
FirstRank:
expression[static_bm25()], result[0.496452].
SecondRank:
expression[text_relevance(name)], result[0.225903].最終得分為10000.2259030193。
通過上述原理,可理解為該命中的doc,在基礎排序階段在召回100W的doc中,參與了基礎排序,通過static_bm25函數算分為0.496452,同時該文檔的基礎排序分正好使該文檔排在了所有命中文檔的前200內,參與了業務排序,在文檔從基礎排序進入業務排序時文檔分會預設加10000分,同時捨棄基礎排序的得分,以業務排序得分+10000分為最終的排序策略得分,由於該文檔在業務排序中通過text_relevance函數算分為0.225903,所以該文檔的最終排序策略得分為10000.2259030193。
業務排序函數用法
注意:以下內建函數中引用到的應用結構中的欄位均需要設定為屬性欄位,否則會報錯Invalid formula。
函數 | 描述 | 案例 |
i in (value1, value2, …, valuen) | 如果i的值在集合[value1, value2, …,valuen]中出現,則該運算式值為1,否則為0。 | 欄位age=5 age in (1,2,3,4,5) # 結果為1 age in (6,7,8,9) # 結果為0 |
if(cond, then_value, else_value) | 如果cond的值非0,則該if運算式的實際值為then_value,否則為else_value。 如if( 2,3, 5)的值為3,if( 0,3,5)的值為5。 | 欄位a=1 if(a==1,5,10) #結果為5 if(1,5,10) #結果為5 if(a==2,5,10) #結果為10 if(0,5,10) #結果為10 |
random() | 返回[0,1]間的一個隨機值。 | - |
now() | 返回目前時間,自Epoch ( 00:00:00 UTC,January 1,1970)開始計算,單位是秒。 | - |
若排序策略的運算式仍無法滿足複雜情境的算分邏輯,可以通過cava外掛程式,編寫指令碼進行複雜情境的算分,關於cava外掛程式的使用以及原理此處不再贅述,有興趣的使用者可以參考排序外掛程式開發-Cava語言
常用情境的排序策略配置
1. 比如想根據age>10 的 +10分,age>40的+20分,根據weight>60的+30,最後根據最後得分排序。
實現1:
#業務排序運算式設定為:預設是匹配到只加1分
(age>10)*10+(age>40)*20+(weight>60)*30實現2:
#精排運算式設定為:
if(age>10,10,0) + if(age>40,20,0) +if(weight>60,30,0)2. 比如,xxx公司,xxx杭州分公司,那麼“xxx公司”要排在“xxx杭州分公司”的前面。
實現:
#可以在業務排序(精排運算式)是裡配置field_match_ratioc函數
field_match_ratio(title)3. 比如搜尋 all:'dim_itm_tb',那麼“dim_itm_tb”想在“dim_itm_tb_dst_itm_relation_dd”前面。
實現:
#可以在業務排序(精排運算式)是裡配置field_match_ratioc函數:
field_match_ratio(detail) 4. 如何?query = item:"iphone 8" OR item: 'iphone 8' 類似這種查詢呢
實現:
#可以在業務排序(精排運算式)是裡配置query_min_slide_window函數:
query_min_slide_window(title)5. 我設定了一個精排 text_relevance,搜尋關鍵詞是 "民國",但是不知道為什麼 "民國趣聞-民國", "中國民族史-民國" 等要比 "民國" 排序要考前。(需要讓“民國”排在最前面)
實現:
#可以在業務排序(精排運算式)是裡配置query_min_slide_window函數:
query_min_slide_window(title)6. 搜尋索引鍵在欄位內容重複出現,會導致static_bm25()函數重複算分,如果規避這種情況?
實現:
#在業務排序裡配置query_match_ratio
query_match_ratio(title) 7. 如何把搜尋存在關鍵詞堆積的文檔給排到後面去?
實現:
#使用query_term_match_count,定義重複多少次為結果堆積。
if(field_term_match_count(title)>3,1,10)8.如何配置字串不為空白時增加一定的分數?
實現:
可以先在源庫中增加標記欄位(mark),如過被判斷欄位為空白就標記成0,不為空白就標記成1。然後在精排運算式中使用if函數進行判斷。
精排運算式設定為:當mark=1時排序分加500
if(mark==1,500,0)