Lucene Collectors を Jira 11 の検索 API に移行する

Jira 11 以降、Lucene に固有の org.apache.lucene.search.Collector を使用して検索結果を繰り返し処理する機能が削除されました。検索 API には、プラットフォームに依存しない代替手段がいくつか用意されています。

検索

com.atlassian.jira.bc.issue.search.SearchService を使用して一致する課題のリストを取得するか、com.atlassian.jira.search.issue.IssueDocumentSearchService を使用して一致するドキュメントのリストを取得し、ローカルで処理します。

OpenSearch を使用する場合、Search には 1 回のリクエストで返せるドキュメントの数に制限があります。プラットフォーム間の互換性を確保するために、結果の数が限られている検索で使用するか、ページネーションと組み合わせて使用してください。既定の制限は 10,000 ですが、OpenSearch インデックスの max_result_window で設定可能です。

関連する OpenSearch ドキュメントをご確認ください。

集計

Lucene コレクターの多くの用途には、検索結果を反復処理してデータを集計することが含まれます。

Jira 10.6 以降では、カウントや合計などの集計操作を実行するための新しい API が利用可能です。この API では、Lucene (内部で Lucene コレクターを使用) と OpenSearch (内部で Aggregations を使用) の両方で高性能なデータ処理を実行できます。

このページでは、新しい集計メソッドについて概説し、Lucene 固有のコレクターから集計 API への移行例を紹介します。

集計メソッド

Jira 11 には、コレクターの一般的なユース ケースに代わるメトリック集計タイプとバケット集計タイプが用意されています。

メトリック集計

メトリック集計では、一連のドキュメントの値を計算します。Jira 11 では、数値フィールドで次のメトリック集計を使用できます。

  • AvgAggregation: 一致するすべてのドキュメントにおけるフィールドの数値の平均を計算します。

  • CountAggregation: 一致するすべてのドキュメントにわたって、フィールドの各値を含むドキュメントの数を計算します。

  • MaxAggregation: 一致するすべてのドキュメントにおけるフィールドの最大数値を決定します。

  • StatsAggregation: 一致するすべてのドキュメントにわたって、フィールドの数値の合計、カウント、平均を計算します。

  • SumAggregation: 一致するすべてのドキュメントにおけるフィールドの数値の合計を計算します。

バケット集計

バケット集計では、メトリック集計とは異なり、指定した条件に基づいてドキュメントをグループ化します。また、サブ集計を含めることができます。Jira 11 では、次のバケット集計を使用できます。

  • DateHistogramAggregation: 日付間隔に基づいて、ドキュメントをバケットにグループ化します。

  • FilterAggregation: バケットを作成する前に、フィルター クエリを適用してドキュメントを絞り込みます。

  • RangeAggregation: 数値範囲に基づいてドキュメントをバケットにグループ化します。

  • TermsAggregation: 指定されたフィールド内の固有の用語に基づいて、ドキュメントをバケットにグループ化します。

今後、その他の集計も導入される予定です。検索 - 検索 API コンポーネントを使用して JAC で提案チケットを提出することで、新しい集計を提案できます。

tip/resting Created with Sketch.

フィールドで集計を行うには、ドキュメント値が有効になっている状態でフィールドがインデックス化されていることを確認してください。

コレクターを集計 API に移行する

コレクターを移行する方法については、次の例を展開して確認してください。

例: 数値型カスタム フィールドの平均値を計算する

レガシー Lucene:

public class AverageValueCollector extends SimpleCollector {

    private final String customFieldId;

    private double sum = 0.0;
    private long count = 0;
    private NumericDocValues customFieldValues;

    public AverageValueCollector(final String customFieldId) {
        this.customFieldId = customFieldId;
    }

    @Override
    public void collect(final int docId) throws IOException {
        if (customFieldValues.advanceExact(docId)) {
            sum += customFieldValues.longValue(); // Assuming the custom field indexes a long value
            count++;
        }
    }

    @Override
    protected void doSetNextReader(final LeafReaderContext context) throws IOException {
        customFieldValues = context.reader().getNumericDocValues(customFieldId);
    }

    @Override
    public boolean needsScores() {
        return false;
    }

    public double getResult() {
        return count > 0 ? sum / count : 0.0;
    }
}

// ...

final var collector = new AverageValueCollector("customfield_10000");

searchProvider.search(SearchQuery.create(query, searcher), collector);

collector.getResult();

集計 API:

// Create an average aggregation on the "customfield_10000"
final var aggregation = new AvgAggregation("customfield_10000");

// Execute the search
final var searchResponse = searchService.search(DocumentSearchRequest.builder()
        .jqlQuery(query)
        .searcher(searcher)
        // define aggregation with name "avg_cf"
        .aggregation("avg_cf", aggregation)
        .build(), new PagerFilter<>(0));

// Read the results
final var avgResult = searchResponse.getAggregations().getAvg("avg_cf");

avgResult.getValue();


例: プロジェクトごとに課題をカウントする

レガシー Lucene:

public class IssuePerProjectCollector extends SimpleCollector {
  
    private final Map<Long, Long> projectToIssueCount = new HashMap<>();
    private SortedDocValues docIdToProjectIdValues;

    @Override
    public void collect(final int docId) throws IOException {
        if (docIdToProjectIdValues.advanceExact(docId)) {
            final long projectId = Long.parseLong(docIdToProjectIdValues.binaryValue().utf8ToString());
            projectToIssueCount.merge(projectId, 1L, Long::sum);
        }
    }

    @Override
    protected void doSetNextReader(final LeafReaderContext context) throws IOException {
        docIdToProjectIdValues = context.reader().getSortedDocValues(DocumentConstants.PROJECT_ID);
    }

    @Override
    public boolean needsScores() {
        return false;
    }

    public Map<Long, Long> getResult() {
        return projectToIssueCount;
    }
}

// ...

final var collector = new IssuePerProjectCollector();

searchProvider.search(SearchQuery.create(query, searcher), collector);

collector.getResult();

集計 API:

// Create a terms aggregation on the PROJECT_ID field
final var aggregation = TermsAggregation.builder()
        .withField(DocumentConstants.PROJECT_ID)
        //.withSubAggregation(...) - you can define a sub-aggregation too
        .build();

// Execute the search
final var searchResponse = searchService.search(DocumentSearchRequest.builder()
        .jqlQuery(query)
        .searcher(searcher)
         // define aggregation with name "count_issues_by_project"
        .aggregation("count_issues_by_project", aggregation)
        .build(), new PagerFilter<>(0));

// Read the results
final var termsResult = searchResponse.getAggregations().getTerms("count_issues_by_project");

termsResult.getBuckets().forEach(bucket -> {
    final var projectId = Long.parseLong(bucket.getKey());
    final var issueCount = bucket.getDocCount();

    // Handle the results ...
});


集計バケット制限

OpenSearch では、1 回のレスポンスに含まれる集計バケットの数が制限されます。既定の制限は 65,535 ですが、search.max_buckets設定で調整可能です。詳細については、OpenSearch 設定ガイドを参照してください。

検索ストリーム

IssueDocumentSearchService#searchStream() では、検索ヒットがストリームとして返されます。Search メソッドとは異なり、検索ストリームは次のようになっています。

  • 検索で返されるドキュメントの数に制限はありません。
  • ソートには対応していません。ユーザー自身が結果をソートする必要があります。クエリで指定したソート句はすべて削除されます。
  • OpenSearch の実装では結果が段階的に取得されるため、結果セット全体を Jira 内のメモリに保持する必要はありません。

このメソッドで返されるデータの量は制限されていないため、大きなデータセットや緩いフィルターで使用すると、パフォーマンスやスケーラビリティの問題が発生する可能性があります。これらの問題を回避するために、可能な限り他のメカニズムを使用することをお勧めします。



最終更新日 2025 年 6 月 4 日

この内容はお役に立ちましたか?

はい
いいえ
この記事についてのフィードバックを送信する
Powered by Confluence and Scroll Viewport.