すべてのプロダクト
Search
ドキュメントセンター

Artificial Intelligence Recommendation:Java SDK

最終更新日:Mar 21, 2026

Feature Generator (FG) を Java サービスに統合するには、Java ソフトウェア開発キット (SDK) を使用して特徴量変換を実行します。このトピックでは、Feature Generator ツール用の Java SDK の使用方法について説明します。

制限事項

  • Java SDK は、linux-x86_64 および macosx-arm64 プラットフォームでのみ実行されます。

Java SDK の使用

このセクションでは、Maven プロジェクトを使用して Java SDK の使用方法を説明します。

1. FG SDK の JAR パッケージをローカルの場所 (例:/path/to/feature_generator-${version}-${platform}.jar) にダウンロードします。

プラットフォーム

FG SDK JAR パッケージ

linux-x86_64

fg-linux-x86_64.jar

macosx-arm64

fg-macosx-arm64.jar

:現在、FG はデフォルトでハッシュバケット化に std::hash を使用します。C++ 標準ライブラリにおける std::hash の実装はプラットフォームによって異なるため、バケット化の結果に一貫性がなくなります。

バケット化の結果にクロスプラットフォームでの一貫性が必要な場合は、環境変数 USE_FARM_HASH_TO_BUCKETIZE=true を設定してください。詳細については、FG のグローバル構成ドキュメントをご参照ください。

2. mvn install:install-file コマンドを使用して、パッケージをローカルリポジトリにインストールします。

mvn install:install-file \
  -Dfile=/path/to/feature_generator-${version}-${platform}.jar \
  -DgroupId=com.aliyun.pai \
  -DartifactId=feature_generator \
  -Dversion=${version} \
  -Dclassifier=${platform} \
  -Dpackaging=jar

注:コマンド内の ${version} を実際のバージョン番号に、${platform} を実際のプラットフォーム名に置き換えてください。

3. 依存関係を pom.xml ファイルに追加します。

<dependencies>
<dependency>
    <groupId>com.aliyun.pai</groupId>
    <artifactId>feature_generator</artifactId>
    <version>${version}</version>
    <classifier>linux-x86_64</classifier>
</dependency>
</dependencies>

4. fg.json ファイルを作成します。

例:

{
  "features": [
    {
      "feature_name": "query_word",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "user:query_word",
      "default_value": "",
      "combiner": "mean",
      "need_prefix": false,
      "is_multi": true
    },
    {
      "feature_name": "query_match",
      "feature_type": "lookup_feature",
      "map": "user:query_token",
      "key": "item:title",
      "needDiscrete": false,
      "needKey": false,
      "default_value": "0",
      "combiner": "sum",
      "need_prefix": false,
      "value_type": "double"
    },
    {
      "feature_name": "goods_id",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "item:goods_id",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "filter_type",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "item:filter_type",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "day_h",
      "feature_type": "id_feature",
      "value_type": "int64",
      "expression": "user:day_h",
      "default_value": "0",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "week_day",
      "feature_type": "id_feature",
      "value_type": "int64",
      "expression": "user:week_day",
      "default_value": "0",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "city",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "user:city",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "province",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "user:province",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "country",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "user:country",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "is_new_user",
      "feature_type": "id_feature",
      "value_type": "int64",
      "expression": "user:is_new_user",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "value_dimension": 1
    },
    {
      "feature_name": "focus_author",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "user:focus_author",
      "separator": ",",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "is_multi": true
    },
    {
      "feature_name": "title",
      "feature_type": "id_feature",
      "value_type": "String",
      "expression": "item:title",
      "default_value": "-1024",
      "combiner": "mean",
      "need_prefix": false,
      "is_multi": true
    }
  ],
  "reserves": [
    "request_id",
    "user_id",
    "is_click",
    "is_pay"
  ]
}

5. 次の例に示すように、Java API を使用します。

package org.example;

import com.aliyun.pai.fg.*;
import java.net.URL;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        String filePath = "/path/to/fg.json";
        FgHandler handler = new FgHandler(filePath, 4, false);

        List<String> outputs = new ArrayList<>();
        outputs.add("goods_id");
        outputs.add("is_new_user");
        outputs.add("day_h");
        outputs.add("query_match"); // 出力する feature_name を設定
        outputs.add("title");
        outputs.add("filter_type");
        outputs.add("city");
        outputs.add("province");
        outputs.add("country");
        outputs.add("focus_author");
        handler.setOutputs(outputs.toArray(new String[0]));

        List<String> expectGoods = Arrays.asList("218687106", "1142068348", "1142068347");
        VariantVectorMap inputs = new VariantVectorMap.Builder()
                .putOptionalString("goods_id", expectGoods)
                .putOptionalInt32("is_new_user", Arrays.asList(0, 1, 0))
                .putOptionalInt32("day_h", Arrays.asList(6, 8, 11))
                .putListString("title", Arrays.asList(
                        Arrays.asList("k2", "k3", "k5"),
                        Arrays.asList("k1", "k2", "k3"),
                        Arrays.asList("k2", "k4")))
                .putMapStringFloat("query_token", Arrays.asList(
                        new HashMap<String, Float>() {{
                            put("k2", 0.8f);
                            put("k3", 0.5f);
                        }},
                        new HashMap<String, Float>() {{
                            put("k1", 0.9f);
                            put("k4", 0.2f);
                        }},
                        new HashMap<String, Float>() {{
                            put("k1", 0.7f);
                            put("k2", 0.3f);
                            put("k4", 0.6f);
                        }}))
                .putOptionalString("filter_type", Arrays.asList(null, "f1", null))
                .putOptionalString("city", Arrays.asList("hangzhou"))
                .putOptionalString("province", Arrays.asList("zhejiang"))
                .putOptionalString("country", Arrays.asList("china"))
                .putOptionalString("focus_author", Arrays.asList("2255010511022,14164467", "10511022,24164467", "550105110,34164467"))
                .build();

        VariantVectorMap results = handler.Process(inputs);
        if (results == null || results.isNull()) {
            System.out.println("fg result is null");
            return;
        }
        inputs.close();  // ネイティブメモリを解放します。
        
        System.out.println("result size=" + results.size());
        List<String> features = results.getKeys();
        System.out.println("result features=" + features);
        List<String> goodsIds = results.getList("goods_id");
        System.out.println("goods_ids=" + String.join(", ", goodsIds));

        List<List<String>> titles = results.getList("title");
        System.out.println("titles=" + titles);

        List<Long> dayHours = results.getList("day_h");
        System.out.println("day_h=" + dayHours);

        List<String> filters = results.getList("filter_type");
        System.out.println("filter_type=" + String.join(", ", filters));

        List<List<String>> focus = results.getList("focus_author");
        System.out.println("focus_author=" + focus);

        List<String> citys = results.getList("city");
        System.out.println("city=" + String.join(", ", citys));

        List<String> provinces = results.getList("province");
        System.out.println("provinces=" + String.join(", ", provinces));

        List<String> countrys = results.getList("country");
        System.out.println("country=" + String.join(", ", countrys));

        List<Long> isNewUsers = results.getList("is_new_user");
        System.out.println("is_new_user=" + isNewUsers);

        List<Double> queryMatch = results.getList("query_match");
        System.out.println("query_match=" + queryMatch);
        System.out.println("===========================================================");

        Set<String> itemInputs = handler.GetItemInputNames();
        System.out.println("item side inputs=" + itemInputs);

        Set<String> userInputs = handler.GetUserInputNames();
        System.out.println("user side inputs=" + userInputs);

        Set<String> ctxInputs = handler.GetContextInputNames();
        System.out.println("context side inputs=" + ctxInputs);

        Set<String> reserved = handler.GetReserveColumns();
        System.out.println("reserved columns =" + reserved);

        Set<String> qminputs = handler.GetFeatureInputs("query_match");
        System.out.println("inputs of query_match =" + qminputs);

        String defaultVal = handler.DefaultFeatureValue("query_match");
        System.out.println("default feature value of query_match =" + defaultVal);

        List<String> allFeatures = handler.GetAllFeatureNames();
        System.out.println("all feature names:" + allFeatures);

        Map<String, String> schema = handler.GetTableSchema();
        System.out.println("table schema:" + schema);
        
        results.close();  // ネイティブメモリを解放します。
        handler.close();  // リソースを解放します。
    }
}

Java SDK ドキュメント

1. クラスの概要

FgHandler クラスは、構成ファイル (config_json) に基づいて入力特徴量を処理し、処理結果を返します。

主な機能:

  • 構成から特徴量プロセッサを初期化します。スレッド数やバケット化操作のみを実行するかどうかなどのパラメーターを指定できます。

  • Process(VariantVectorMap inputs) メソッドを使用して入力特徴量を処理します。このメソッドは VariantVectorMap を返します。

  • setOutputs(...) メソッドを使用して出力特徴量コレクションを設定します。

  • 入力、出力、特徴量の名前、スキーマ、デフォルト値、ディメンションなどのメタデータを構成からクエリします。

  • ネイティブリソースを明示的に解放するための close() メソッドを提供します。このステップは重要です。

2. クイックスタート

2.1 初期化

4つのコンストラクターメソッドが利用可能です。これらのメソッドはすべて、ネイティブの割り当て関数を呼び出します:

// 1) 構成のみ
FgHandler handler = new FgHandler(configJsonOrPath);
// 2) 構成とスレッド数
FgHandler handler = new FgHandler(configJsonOrPath, threadNum);
// 3) 構成、スレッド数、およびバケット化のみのフラグ
FgHandler handler = new FgHandler(configJsonOrPath, threadNum, bucketizeOnly);
// 4) 構成、スレッド数、バケット化のみのフラグ、および構成がファイルパスであるかのフラグ
FgHandler handler = new FgHandler(configJsonOrPath, threadNum, bucketizeOnly, isCfgPath);

パラメーターの説明:

  • config_json:構成コンテンツまたは構成ファイルへのパス。is_cfg_path パラメーターによって、どちらが使用されるかが決まります。

  • thread_num:ネイティブレイヤーの内部処理スレッド数 (size_t)。

  • bucketize_only:バケット化操作のみを実行するかどうかを指定します。この設定は IsOnlyBucketize() を使用して確認できます。

  • is_cfg_path:true の場合、config_json パラメーターはファイルパスです。false の場合、JSON 構成コンテンツです。

2.2 出力特徴量の設定

2つの方法のいずれかで出力を設定できます:

handler.setOutputs("feature_a", "feature_b");
// または、String[] 配列をパラメーターとして使用します。
List<String> outputs = new ArrayList<>();
outputs.add("goods_id");
outputs.add("is_new_user");
outputs.add("title");
outputs.add("city");
outputs.add("province");
outputs.add("country");
handler.setOutputs(outputs.toArray(new String[0]));

あるいは、低レベル API を使用することもできます:

StrVector outs = new StrVector();
outs.push_back("feature_a");
outs.push_back("feature_b");
handler.setOutputs(outs);

注:出力を設定しない場合、デフォルトで構成内のすべての特徴量が出力されます。

2.3 処理の実行

VariantVectorMap inputs = new VariantVectorMap();
// TODO: VariantVectorMap の規則に基づいて入力を埋めます。
VariantVectorMap outputs = handler.Process(inputs);
if (outputs == null || outputs.isNull()) {
   System.out.println("fg output is null");
   inputs.close();
   return;
}
// TODO: outputs から結果を読み取ります。

FgHandler.Process(inputs) メソッドは、一度にバッチのサンプルを処理します。このメソッドは VariantVectorMap を入力として受け取り、VariantVectorMap を返します。これは次のことを意味します:

  • VariantVectorMap:キーがフィールド名または特徴量名である Map<String, VariantVector>。

  • VariantVector:列指向ストレージ用のバッチデータコンテナで、列ベクターとも呼ばれます。以下のネスト形式をサポートします:

    • オプショナルスカラー:長さがバッチサイズである List<T>。null 値を含むことができます。

    • リストシーケンス:外側のリストの長さがバッチサイズに等しい List<List<T>>。

    • マップ特徴量:外側のリストの長さがバッチサイズに等しい List<Map<K,V>>。

    • マトリックス特徴量:外側のリストの長さがバッチサイズに等しい List<List<List<T>>>。各サンプルは 2D マトリックスです。

  • `Process` 関数は null を返すことがあります。null 値は、特徴量変換が失敗したことを示します。使用する前に結果が null でないか確認してください。

3. 入出力データ構造

3.1 VariantVectorMap (入出力コンテナ)

3.1.1 基本 API

  • 構造:

    • new VariantVectorMap()

    • VariantVectorMap.fromJavaMap(Map<String, VariantVector>)

    • new VariantVectorMap.Builder()...build()

  • テキストの入力:

    • put(String key, VariantVector value)

    • putAll(Map<String, VariantVector>)

  • 読み取り:

    • VariantVector get(String key)

    • boolean contains(String key)

    • List<String> getKeys()

    • long size()

    • Map<String, VariantVector> toJavaMap() (現在のマップ内のキーと値のペアを Java Map に変換します。)

  • リリース:

    • clear():すべての要素をクリアします。

    • close():ネイティブメモリを解放します。メモリリークを防ぐために、このメソッドを呼び出す必要があります。

3.1.2 推奨:Builder で入力を構築

VariantVectorMap.Builder は複数の putXXX メソッドを提供します。

共通 (オプショナルスカラー、1バッチ分):

VariantVectorMap inputs = new VariantVectorMap.Builder()
    .putOptionalInt64("user_id", Arrays.asList(1001L, 1002L))
    .putOptionalString("item_id", Arrays.asList("i1", "i2"))
    .putOptionalDouble("score", Arrays.asList(0.1, null)) // null をサポートします。
    .build();

単一値の便宜的用法 (batch=1):

VariantVectorMap inputs = new VariantVectorMap.Builder()
    .putSingleOptionalInt64("user_id", 1001L)
    .putSingleOptionalString("item_id", "i1")
    .build();

シーケンス (List 型、バッチ内のサンプルごとに1つのリスト):

VariantVectorMap inputs = new VariantVectorMap.Builder()
    .putListInt64("clicked_item_ids",
        Arrays.asList(
            Arrays.asList(11L, 12L),
            Arrays.asList(21L)
        )
    )
    .build();

マップ特徴量 (バッチ内のサンプルごとに1つのマップ):

VariantVectorMap inputs = new VariantVectorMap.Builder()
    .putMapStringFloat("user_dense",
        Arrays.asList(
            Map.of("age", 18.0f, "lvl", 3.0f),
            Map.of("age", 25.0f)
        )
    )
    .build();

マトリックス (サンプルごとに1つの 2D マトリックス):

VariantVectorMap inputs = new VariantVectorMap.Builder()
    .putMatrixFloat("image_emb",
        Arrays.asList(
            Arrays.asList( // sample1 のマトリックス
                Arrays.asList(0.1f, 0.2f),
                Arrays.asList(0.3f, 0.4f)
            ),
            Arrays.asList( // sample2 のマトリックス
                Arrays.asList(1.0f, 2.0f)
            )
        )
    )
    .build();

3.1.3 getList:型安全な方法でのデータ読み取り (出力で共通)

VariantVectorMap.getList(key) メソッドは、VariantVector.getType() の戻り値に基づいて、変換用に登録された TypeReference を自動的に選択します。

例:出力 feature_x を読み取る場合、それが OPTIONAL_FLOAT であると仮定します:

List<Float> xs = out.getList("feature_x");   // 実際には List<Float> を返します。

LIST_INT64 を読み取る場合、List<List> を返します:

List<List<Long>> seq = out.getList("seq_feature");

注:

  • getList の戻り値の型は <T> List<T> であり、コンパイル時に強く制約することはできません。ランタイムでの型不一致は IllegalArgumentException("Type mismatch...") をスローします。

  • TYPE_REGISTRY には、定義されたすべての型定数から TypeReference への事前登録されたマッピングが含まれています。

推奨される実践方法:まず、出力テーブルのスキーマを取得します。次に、スキーマに基づいて getList 関数の戻り値の型を決定します。

Map<String, String> schema = handler.GetTableSchema();
System.out.println("table schema:" + schema);

3.2 VariantVector (列ベクターと型システム)

VariantVector は単一列のデータを表します。その型を設定し、その型に従って内部データを整理する必要があります。

3.2.1 サポートされる型 (定数による)

  • Optional (null を許容、外側のレイヤーはバッチ):

    • OPTIONAL_STRING / INT32 / INT64 / FLOAT / DOUBLE

    • Java 表現:List<T> (長さ = バッチサイズ。要素は null にできます)。

  • List (各サンプルのシーケンス):

    • LIST_STRING / INT32 / INT64 / FLOAT / DOUBLE

    • Java 表現:List<List<T>> (外側のリストの長さ = バッチサイズ)。

  • Map (各サンプルの1つのマップ):

    • MAP_STRING_*:List<Map<String, V>>

    • MAP_INT32_*:List<Map<Integer, V>>

    • MAP_INT64_*:List<Map<Long, V>>

  • Matrix (各サンプルの1つの 2D マトリックス):

    • MATRIX_FLOAT / MATRIX_INT64 / MATRIX_STRING

    • Java 表現:List<List<List<T>>>

3.2.2 推奨される構築方法:静的な fromXXX メソッド

低レベルの Builder(type).withXXXData() メソッドを手動で呼び出す必要はありません。代わりに、以下の静的メソッドを使用できます:

  • VariantVector.fromOptionalInt64(List<Long> values)

  • VariantVector.fromListFloat(List<List<Float>> values)

  • VariantVector.fromMapStringInt32(List<Map<String,Integer>> maps)

  • VariantVector.fromMatrixFloat(List<List<List<Float>>> matrices) など。

例 (手動で構築してからマップに入れる):

VariantVector userIds = VariantVector.fromOptionalInt64(Arrays.asList(1L, 2L, null));
VariantVectorMap inputs = new VariantVectorMap();
inputs.put("user_id", userIds);

この方法で作成されたオブジェクトは、自身のネイティブメモリを所有します。使用後にネイティブメモリを解放するために close() を呼び出す必要があります

別のタイプのオブジェクトは、借用メモリモデルを使用します。例:

VariantVectorMap results;
// results を構築
VariantVector v = results.get("goods_id");

このタイプのオブジェクトはビューです。基になるメモリは `VariantVectorMap` オブジェクトと共有されます。`VariantVectorMap` オブジェクトが解放されると、ビューオブジェクトはダングリングポインターとなり、使用できなくなります。

借用された `VariantVector` オブジェクトについては、使用後に close() を呼び出す必要はありません。長期間にわたって別々に保存しないでください。そのライフサイクルは、それらを所有する `VariantVectorMap` オブジェクトに依存します

3.2.3 データの読み取り:toXXXList

VariantVector vv を取得した後、その型に基づいて変換メソッドを呼び出すことができます:

VariantVector vv = out.get("feature_x");
if (vv.getType() == VariantVector.OPTIONAL_DOUBLE) {
    List<Double> vals = vv.toOptionalDoubleList();
}

自動ディスパッチのために VariantVectorMap.getList(key) を使用することもできます。詳細については、セクション 3.1.3 をご参照ください。

3.2.4 ヘルパーメソッド:size、isEmpty、および totalElementCount

  • vv.size():

    • Optional:バッチサイズ、つまり nullFlags の長さを返します。

    • その他:sizes[0]、つまり外側の要素の数を返します。これは通常バッチサイズです。

  • vv.isEmpty():型に基づいて、基になるデータが空かどうかをチェックします。

  • vv.totalElementCount():ネストされたリスト、マップ、またはマトリックスを展開した後の総要素数を計算します。このメソッドは主にデバッグと検証に使用されます。

4. メタデータクエリ API

これらの API は、主に構成内容と特徴量の依存関係を検査するために使用されます。

4.1 クエリ入力フィールドのコレクション

Set<String> itemInputs = handler.GetItemInputNames();
Set<String> userInputs = handler.GetUserInputNames();
Set<String> ctxInputs  = handler.GetContextInputNames();

4.2 特徴量名、予約列、および特殊コレクションのクエリ

List<String> allFeatures = handler.GetAllFeatureNames();
Set<String> reserveCols  = handler.GetReserveColumns();
Set<String> userSide     = handler.GetUserSideFeatures();
Set<String> seqFeatures  = handler.GetSequenceFeatures();

4.3 特徴量の依存関係、デフォルト値、ディメンション、およびバケット化処理のクエリ

Set<String> deps = handler.GetFeatureInputs("feature_x");
String defaultVal = handler.DefaultFeatureValue("feature_x");
long defaultBucket = handler.DefaultBucketizedFeatureValue("feature_x");
long dim = handler.GetFeatureValueDim("feature_x");
boolean hasBucketizer = handler.HasBucketizer("feature_x");
boolean onlyBucketize = handler.IsOnlyBucketize();

シナリオ

  • 入力準備ロジックを生成して、特徴量に必要な生の入力を特定します。

  • デフォルト値を使用して、欠落している入力を埋めるか、検証します。

  • 下流モデルが固定ディメンション (dim) を必要とするケースを処理します。

  • 追加のバケット化処理が必要かどうかを判断します (hasBucketizer/onlyBucketize)。

4.4 出力テーブルスキーマのクエリ

使用方法

Map<String, String> schema = handler.GetTableSchema();
String type = schema.get("feature_x");

5. リソースの解放 (必読)

FgHandler オブジェクトはネイティブオブジェクトへのポインターを保持します。メモリリークを防ぐために、使用されなくなったときにこのオブジェクトを解放する必要があります。

これはヘビーウェイトオブジェクトです。一般的に:

  • 一度初期化します。

  • 複数回再利用します。

  • サービスが終了するときに `close()` を呼び出します。

ベストプラクティス

  • リクエストごとに新しい `FgHandler()` を作成しないでください。

  • 構成ごとに1つの長期間再利用可能なハンドラを使用してください。

5.1 FgHandler が使用されなくなった場合、リソースを解放するために close() 関数を呼び出す必要があります。この関数には以下の属性があります:

  • スレッドセーフ:メソッドは同期されています。

  • べき等:メソッドを複数回呼び出しても、リソースが複数回解放されることはありません。

重要:close() を呼び出した後、他のネイティブメソッド (Process やクエリインターフェイスなど) を呼び出さないでください。さもなければ、use-after-free エラーによりアプリケーションがクラッシュする可能性があります。

FgHandler handler = new FgHandler(cfg, 4);
try {
    ...
} finally {
    handler.close();
}

5.2 デバッグフェーズでは、try-with-resources 文を使用できます。

try (FgHandler handler = new FgHandler(cfg, 4)) {
    handler.setOutputs("f1", "f2");
    VariantVectorMap out = handler.Process(in);
}

5.3 本番環境では、同一の FgHandler オブジェクトを頻繁に作成しないでください。

重要:サービス内で FgHandler を使用する場合、リクエストごとに新しい FgHandler オブジェクトを作成しないでください。このオブジェクトの初期化オーバーヘッドは高く、頻繁な作成はサービスパフォーマンスを低下させます。

サービスの初期化関数でグローバルオブジェクトを作成します。サービスの終了関数で close() メソッドを呼び出します。複数の異なる FgHandler オブジェクトを作成する必要がある場合も、同じパターンに従ってください。


最も推奨されるリクエスト処理パターン

パターン A:同期リクエスト、最も安定

public Result handle(Request req, FgHandler handler) {
    try (VariantVectorMap inputs = buildInputs(req);
         VariantVectorMap results = handler.Process(inputs)) {
        Result r = new Result();
        r.goodsIds = results.get("goods_id").toOptionalStringList();
        r.dayHs = results.get("day_h").toOptionalInt32List();
        r.titles = results.get("title").toListStringList();
        return r;
    }
}

特徴:

  • ネイティブリソースのスコープが明確です。

  • リソースはリクエストが終了すると解放されます。

  • OOM エラーを引き起こす可能性が低いです。

パターン B:サービスレベルのシングルトンハンドラ

public class FgService implements AutoCloseable {
    private final FgHandler handler;
    public FgService(String configPath) {
        this.handler = new FgHandler(configPath, 4, false, true);
    }
    public Result process(Request req) {
        try (VariantVectorMap inputs = buildInputs(req);
             VariantVectorMap results = handler.Process(inputs)) {
            return decode(results);
        }
    }
    @Override
    public void close() {
        handler.close();
    }
}

これは本番環境で最も推奨されるアプローチです。

6. 一般的な使用例

処理の前に、構成に従って入力フィールドを準備します。

FgHandler handler = new FgHandler(cfgJson, 8, false, false);
try {
    // 1) 必要な出力を設定します。
    handler.setOutputs("feature_a", "feature_b");
    // 2) 準備する入力を調べます。
    Set<String> itemInputs = handler.GetItemInputNames();
    Set<String> userInputs = handler.GetUserInputNames();
    Set<String> ctxInputs  = handler.GetContextInputNames();
    // 3) 入力を構築します (例、VariantVectorMap API に依存)。
    VariantVectorMap inputs = new VariantVectorMap();
    // inputs.put("user_id", ...)
    // inputs.put("item_id", ...)
    // inputs.put("ts", ...)
    VariantVectorMap outputs = handler.Process(inputs);
    inputs.close();
    // 出力を読み取ります...
    outputs.close();
} finally {
    handler.close();
}

バケット化操作のみの場合:

FgHandler handler = new FgHandler(cfgJson, 4, true, false);
try {
    if (!handler.IsOnlyBucketize()) {
        // 構成またはコンストラクターのパラメーターが一致しない場合にプロンプトを追加できます。
    }
    ...
} finally {
    handler.close();
}

安全な標準的な使用方法

try (FgHandler handler = new FgHandler(configPath, 4, false, true)) {
    handler.setOutputs("goods_id", "day_h", "title");
    try (VariantVectorMap inputs = new VariantVectorMap.Builder()
            .putOptionalString("goods_id", Arrays.asList("1", "2"))
            .putOptionalInt32("day_h", Arrays.asList(6, 8))
            .putListString("title", Arrays.asList(
                    Arrays.asList("a", "b"),
                    Arrays.asList("c", "d")))
            .build();
         VariantVectorMap results = handler.Process(inputs)) {
        List<String> goodsIds = results.get("goods_id").toOptionalStringList();
        List<Integer> dayHs = results.get("day_h").toOptionalInt32List();
        List<List<String>> titles = results.get("title").toListStringList();
        // すぐに使用し、results や借用した VariantVector オブジェクトをスコープ外で長期間保持しないでください。
    }
}

7. OOM エラーを引き起こす可能性が最も高い使用方法

1. 閉じ忘れによるVariantVectorMapのエラー

誤った例

for (...) {
    VariantVectorMap inputs = new VariantVectorMap.Builder()...build();
    VariantVectorMap results = handler.Process(inputs);
    // close されていない
}

これにより、以下の問題が発生します:

  • Java オブジェクトはすぐにガベージコレクションされる可能性があります。

  • しかし、ネイティブメモリが迅速に解放される保証はありません。

  • 高い秒間クエリ数 (QPS) の下では、多くのオブジェクトが割り当てられますが解放されません。これにより、OOM エラーが発生したり、ネイティブの常駐セットサイズ (RSS) が制限を超えたりする可能性があります。

正しい例

for (...) {
    try (VariantVectorMap inputs = new VariantVectorMap.Builder()...build();
         VariantVectorMap results = handler.Process(inputs)) {
        ...
    }
}

リソースは try-with-resources ブロックで自動的に解放されます。

2. リクエストごとに新しい FgHandler を作成する

誤った例

for (...) {
    FgHandler handler = new FgHandler(configPath, 4, false, true);
    ...
}

この操作は通常、非常にヘビーウェイトです:

  • 構成を繰り返しロードします。

  • スレッドプールと内部構造を初期化します。

  • ネイティブリソースを繰り返し要求します。

正しい例

FgHandler handler = new FgHandler(configPath, 4, false, true);
try {
    for (...) {
        ...
    }
} finally {
    handler.close();
}

3. results.get(key) の戻り値のキャッシュ

誤った例

VariantVector v = results.get("goods_id");
cache.add(v);   // 誤り

これは借用されているため:

  • results.close() が呼び出されると、これらのオブジェクトは無効になります。

  • results を close しないと、メモリは決して解放されません。

正しい例

すぐにプレーンな Java オブジェクトにデコードします:

List<String> goodsIds = results.get("goods_id").toOptionalStringList();
cache.add(goodsIds);

または、ネイティブオブジェクトを保存する必要がある場合は、getCopy() を使用し、後で必ず close します:

VariantVector copy = results.getCopy("goods_id");
try {
    ...
} finally {
    copy.close();
}

ただし、必要でない限り、ネイティブコピーをキャッシュすることはお勧めしません。

4. toJavaMap() の多用後に値の解放が行われないtoJavaMap()

toJavaMap() が所有コピーを返す場合、多くのネイティブオブジェクトを簡単に作成してしまいます。

誤った例

Map<String, VariantVector> map = results.toJavaMap();
// close せずに放置。

正しい例

  • 必要でない限り、これを行わないでください。

  • データをフィールドごとに Java オブジェクトに読み取ることをお勧めします。

  • どうしてもこれを行う必要がある場合:

Map<String, VariantVector> map = results.toJavaMap();
try {
    ...
} finally {
    for (VariantVector v : map.values()) {
        if (v != null) v.close();
    }
}

5. Builder からの中間 VariantVector オブジェクトを Java Map に長期間放置する

例:

VariantVectorMap.Builder builder = new VariantVectorMap.Builder();
builder.putOptionalString(...);
builder.putOptionalInt32(...);
// builder/data が長期間存在する

Builder は内部的に多くの VariantVector オブジェクトを保持しており、これらはすべて所有されたネイティブオブジェクトです。

Builder が長期間解放されない場合、ネイティブメモリも消費します。

正しい手順

  • Builder のライフサイクルをできるだけ短く保ちます。単一のリクエストを構築するためにのみ使用してください。