集計 API アップグレード ガイド

はじめに

Jira 10.4 では、新しい検索 API を導入し、Lucene 専用の org.apache.lucene.search.Collector を廃止しました。com.atlassian.jira.search.index.IndexSearcher#scan(SearchRequest request, Function<Document, Boolean> callback) に置き換えることを推奨しています。しかし、scan メソッドを集計に使用することは、ローカル処理のデータ転送量が多いため非効率であることを認識しています。

これに対処するため、集計操作専用に設計された新しい集計 API を導入します。

このページでは、新しい集計メソッドを詳しく説明し、Lucene 専用コレクターから集計 API への移行例を紹介します。

集計メソッド

Jira 10.5 では、メトリック集計とバケット集計という 2 つの集計タイプを導入します。

メトリック集計

メトリック集計では、一連のドキュメントの値を計算します。Jira 10.5 では、次のメトリック集計が可能です。

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

バケット集計

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

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

今後、より多くの集計を導入する予定です。Search - Search API コンポーネントを使用して JAC で提案チケットを提出することで、新しい集計を提案できます。

考慮事項

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

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

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

Legacy Lucene

集計 API

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();
// Create an average aggregation on the "customfield_10000"
final var aggregation = new AvgAggregation("customfield_10000");

// Define the aggregation request with the custom "avg_cf" name
final var aggregationRequest = AggregationRequest.builder()
        .addAggregation("avg_cf", aggregation)
        .build();

// Execute the search
final var searchResponse = searchService.search(DocumentSearchRequest.builder()
        .jqlQuery(query)
        .searcher(searcher)
        .aggregationRequest(aggregationRequest)
        .build(), new PagerFilter<>(0));

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

avgResult.getValue();

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

Legacy Lucene

集計 API

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();
// 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();

// Define the aggregation request with the custom "count_issues_by_project" name
final var aggregationRequest = AggregationRequest.builder()
        .addAggregation("count_issues_by_project", aggregation)
        .build();

// Execute the search
final var searchResponse = searchService.search(DocumentSearchRequest.builder()
        .jqlQuery(query)
        .searcher(searcher)
        .aggregationRequest(aggregationRequest)
        .build(), new PagerFilter<>(0));

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

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

    // Handle the results ...
});


最終更新日: 2025 年 2 月 21 日

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

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