全部產品
Search
文件中心

OpenSearch:使用cava編寫排序指令碼

更新時間:Jul 13, 2024

cava排序指令碼簡介

Opensearch支援兩階段排序,粗排和精排。目前cava實現的排序指令碼只支援在精排階段生效。本文只聚焦在通過cava如何編寫排序指令碼,至於指令碼的建立和使用請參考排序指令碼API使用手冊,另外也可以通過我們提供的命令列工具來建立排序指令碼。與排序運算式相比,使用cava編寫排序指令碼具有更強的靈活性,可以定製性。使用者可以在指令碼中通過cava支援的文法和opensearch提供的feature lib來實現自己的商務邏輯。

cava排序指令碼介面

為了能夠使用自訂的指令碼進行算分排序,使用者需要實現opensearch提供的算分介面類。介面類的代碼如下:

package users.scorer;
import com.aliyun.opensearch.cava.framework.OpsScoreParams;
import com.aliyun.opensearch.cava.framework.OpsScorerInitParams;

class BasicSimilarityScorer {
    //可以定義一些成員變數
    boolean init(OpsScorerInitParams params) {
        //實現你的代碼,初始請求層級的變數,比如類的成員變數
        return true;
    }

    double score(OpsScoreParams params) {
        double score = 0;
        //實現你的代碼,並將算分結果賦值給score
        return score;
    }
};

介面類BasicSimilarityScorer在包users.scorer下面,使用者不能修改類名或者包名,否則編譯會報錯。BasicSimilarityScorer提供了init和score兩個方法,使用者可以在這個兩個方法中實現自己的商務邏輯。

在opensearch中,對於每一個請求opensearch會先調用BasicSimilarityScorer的init方法初始化一些請求層級的變數(比如類的成員變數),該方法對於每個請求只會執行一次,如果返回失敗請求終止。然後對於每個命中的且參與精排的文檔,opensearch會依次調用score方法對文檔進行算分,最終會根據算分結果進行排序。

init介面的輸入參數為OpsScorerInitParams,通過該參數使用者可以擷取一些請求層級的資源。我們建議BasicSimilarityScorer的成員變數在init階段進行初始化。

score介面的輸入參數為OpsScoreParams,通過該參數使用者可以擷取一些請求和doc層級的資源。score介面對於每個參與算分的文檔都會調用一次,所以對於請求層級的資源(比如kvpairs中的一些參數,一些feature對象的建立)盡量不要在score介面中擷取(可以在init介面中擷取),對於doc層級的資源可以在score介面中進行擷取。

init和score的函數定義使用者不能修改(比如改變傳回值類型或者輸入參數),否則編譯的時候會報錯。

cava排序指令碼樣本

package users.scorer;
import cava.lang.CString;
import com.aliyun.opensearch.cava.framework.OpsScoreParams;
import com.aliyun.opensearch.cava.framework.OpsScorerInitParams;
import com.aliyun.opensearch.cava.framework.OpsRequest;
import com.aliyun.opensearch.cava.framework.OpsKvPairs;
import com.aliyun.opensearch.cava.framework.OpsDoc;
import com.aliyun.opensearch.cava.features.similarity.TextRelevance; //引用需要使用的特徵
class BasicSimilarityScorer {
    TextRelevance _textRelevance; //定義算分特徵作為成員變數

    boolean init(OpsScorerInitParams params) {
        if (!params.getDoc().requireAttribute("shop_margin")) { //算分中使用的屬性欄位,需要在init介面中聲明
            return false;
        }
        _textRelevance = TextRelevance.create(params, "default", "name"); //算分特徵在init介面中聲明
        return true;
    }

    double score(OpsScoreParams params) {
        float shopMargin = params.getDoc().docFieldFloat("shop_margin"); //擷取文檔中欄位的值
        float textScore = _textRelevance.evaluate(params); //計算特徵分數
        double score = textScore * 30.0 + shopMargin;
        return score;
    }
}

開發排序指令碼注意事項

  • Opensearch提供的特徵類,建議定義為BasicSimilarityScorer的成員變數,並在init函數中初始化,在score函數中進行算分,如果在score階段初始化會造成極大的效能浪費。

  • 對於請求中自訂的參數,建議定義為BasicSimilarityScorer的成員變數,並在init函數中擷取它的值。

  • 如果要擷取文檔中的欄位,欄位首先要在應用結構中定義為屬性欄位,然後再init函數中進行聲明,在score函數中擷取具體的值。

  • 由於目前只支援使用者上傳單個檔案,所以只能在users.scorer包下面定義類。

  • 可以使用import關鍵字引用opensearch提供的系統庫,但是目前不支援import com.aliyun.opensearch.cava.framework.*;這種文法。

  • 對於單次查詢請求,排序指令碼運行時使用記憶體的上限是40M,如果記憶體使用量超過限制,查詢請求會報錯並返回,所以不要在指令碼中進行大塊記憶體操作,特別是不要在score函數中頻繁的進行new操作,或者使用大量的字串。對於記憶體超過限制的請求,opensearch在報錯的同時也會有結果返回(盡量保證有結果),只是其中一部分文檔使用了排序指令碼進行算分,一部分沒有。

  • 在排序指令碼中可以使用for迴圈,或者進行函數調用。為了避免死迴圈和無限次函數調用,對於單次查詢請求,opensearch限制排序指令碼中for迴圈和函數調用的次數不能超過100000次,如果超過限制查詢請求會報錯並提前返回。

  • 使用排序指令碼排序的文檔數受限於rerank_size參數,使用者可以通過該參數來調整參與排序的文檔個數,從而避免觸發記憶體和迴圈次數的限制。