Write a sorting script in Cava

更新时间:
复制 MD 格式

Cava sort scripts let you implement custom scoring logic for fine sort in OpenSearch. Unlike sort expressions, Cava scripts give you full programmatic control — you can call OpenSearch libraries, access document fields, and read query parameters to build any scoring formula your business requires.

Note: Cava sort scripts apply only to fine sort, not rough sort. For an introduction to creating and managing sort scripts, see the API reference for sort scripts. To create scripts using the command-line interface, see Create sort scripts by using SortScript.

How it works

For each search request, OpenSearch runs your sort script in two phases:

  1. Init phase — OpenSearch calls init() once per search request to set up request-level state: declare the attribute fields your script needs, initialize scoring features, and read query parameters.

  2. Score phase — OpenSearch calls score() once per candidate document in the fine sort to compute that document's score. Documents are then ranked by their scores.

If init() fails, OpenSearch returns an error and terminates the request. If the score phase exceeds memory or loop limits, OpenSearch returns partial results with a warning.

The BasicSimilarityScorer class

All Cava sort scripts must implement the BasicSimilarityScorer class in the users.scorer package. The class name and package name are fixed — changing either causes a compilation error.

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

class BasicSimilarityScorer {
    // Declare member variables here (scoring features, query parameters, etc.)

    boolean init(OpsScorerInitParams params) {
        // Initialize member variables for this search request.
        // Called once per request.
        return true;
    }

    double score(OpsScoreParams params) {
        double score = 0;
        // Compute and return the document score.
        return score;
    }
};

Method reference

MethodParameterWhen calledReturn valuePurpose
initOpsScorerInitParamsOnce per search requestbooleanDeclare attribute fields; initialize scoring features and query parameters
scoreOpsScoreParamsOnce per candidate documentdoubleCompute and return the document score

The method signatures — parameter types and return types — cannot be modified. Changing them causes a compilation error.

What to put in each method

  • In init: declare attribute fields with requireAttribute(), initialize scoring features, and read kvpairs parameters. All request-level setup belongs here.

  • In score: read document fields and evaluate scoring features. Avoid reading request-level data in score — it is called once per document, so redundant initialization wastes memory and processing time.

Example sort script

The following script ranks documents by combining a text relevance score with a numeric document field (shop_margin).

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; // Scoring feature declared as a member variable

    boolean init(OpsScorerInitParams params) {
        if (!params.getDoc().requireAttribute("shop_margin")) { // Declare attribute fields used in score()
            return false;
        }
        _textRelevance = TextRelevance.create(params, "default", "name"); // Initialize scoring feature once
        return true;
    }

    double score(OpsScoreParams params) {
        float shopMargin = params.getDoc().docFieldFloat("shop_margin"); // Read document field
        float textScore = _textRelevance.evaluate(params);               // Evaluate scoring feature
        double score = textScore * 30.0 + shopMargin;
        return score;
    }
}

Key patterns in this example:

  • _textRelevance is a member variable initialized once in init() and reused in every score() call — this avoids redundant initialization.

  • shop_margin is declared in init() with requireAttribute() before being read in score().

  • The final score combines a weighted text relevance signal with a raw document attribute.

Performance considerations

Sort scripts run per-document during fine sort. Two hard limits apply per search request:

LimitValueBehavior when exceeded
Memory40 MBOpenSearch reports an error and returns partial results. Only some documents are scored based on scores; others may not be scored.
For loops and function invocations100,000OpenSearch reports an error and returns results early.

To stay within these limits:

  • Avoid frequent use of `new` in `score()` — object allocation is expensive when called once per document across thousands of candidates.

  • Minimize string operations in `score()` — strings consume disproportionate memory at high call volumes.

  • Adjust `rerank_size` — use the rerank_size parameter to control how many documents enter the fine sort stage. Reducing rerank_size directly reduces memory usage and loop count.

Usage notes

  • Single-file upload, single package — define all classes in the users.scorer package. Only one file can be uploaded, so all your classes must be in that file.

  • Explicit imports only — the wildcard import com.aliyun.opensearch.cava.framework.* is not supported. Import each class explicitly.

  • Attribute fields must be in the application schema — a field must be defined as an attribute field in the application schema before you can declare it with requireAttribute() and read it in score().

What's next