OpenSearchのMulti-search APIによるデータ処理のパフォーマンス改善

  • URLをコピーしました!

皆さんこんにちは!PR TIMESの開発部のThaiです。今回はOpenSearchのMulti-Search APIを利用してWebクリッピングのクリップ調査のパフォーマンスを改善したことについて話します。

プレスリリース・ニュースリリース...
PR TIMES Webクリッピング|広報・PRの分析・効果測定ツール 自動でWeb記事収集ができるWebクリッピング機能をPR 広報・PRの効果測定のためのクリッピングなら「PR TIMES Webクリッピング」。プレスリリース配信後の露出調査から競合...

クリップ調査とは、設定したキーワードを含む記事を自動で収集する調査です。調査はクリップ作成後に開始し、期間は最長1年まで指定可能。調査結果はグラフや記事一覧で確認・ダウンロードできます。

クリップ調査の収集された結果は、以下のような形式で表示されます。

目次

「マッチキーワード」とは?

クリップ調査の機能は、設定したキーワードに関係する記事を探します。さらに、どの記事がどのキーワードに該当するかも判別できます。Webクリッピングでは、記事の中にあるキーワードを「マッチキーワード」と呼びます。

たとえば、ユーザーが「Apple」「iPhone」「旅行」というキーワードでクリップをつくります。システムは「来月の旅行にそなえて、Appleで最新のiPhone16を買いました」という記事をみつけました。このとき、その記事のマッチキーワードは「Apple」「iPhone」「旅行」になります。

以前の方法の問題点

Webクリッピングでは全文検索エンジンとしてOpenSearchを活用していますが、課題がありました。OpenSearchの通常の検索クエリでは、複数のキーワードで検索した場合、返ってくるのはいずれかのキーワードに合致した記事のリストだけです。具体的にどのキーワードがどの記事に合致したかという情報は含まれていません。

この「マッチキーワード」を特定するという課題を解決するため、以前はexplain:trueというパラメータを利用していました。

OpenSearch Documentation
Explain Explain API Introduced 1.0

explain:trueを有効にすると、OpenSearchは検索処理および結果の詳細を返しますが、その構造は非常に複雑です。

GET /index/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "body": "Apple"
          }
        },
        {
          "match": {
            "body": "iPhone"
          }
        },
        {
          "match": {
            "body": "旅行"
          }
        }
      ]
    }
  },
  "explain": true
}

[“Apple”, “iPhone”, “旅行”]というキーワードで検索した場合、ヒットした記事1件に対するexplainの結果は、以下のような(簡略化した)形式になります。

"_explanation": {
  "value": 1.834,
  "description": "sum of:",
  "details": [
    {
      // "Apple"がマッチしたことによるスコア計算の詳細
      "value": 0.912,
      "description": "weight(body:apple in 123) [PerFieldSimilarity], result of:",
      "details": [
        // ... さらにネストしたスコア計算の詳細 ...
      ]
    },
    {
      // "iPhone"がマッチしたことによるスコア計算の詳細
      "value": 0.922,
      "description": "weight(body:iphone in 123) [PerFieldSimilarity], result of:",
      "details": [
        // ... さらにネストしたスコア計算の詳細 ...
      ]
    }
		...
  ]
}

このレスポンスから「マッチキーワード」を特定するためには、アプリケーション側で以下の処理が必要でした。

  1. ヒットした記事ごとに、上のネスト構造を持つJSONを再帰的にパースする
  2. descriptionフィールド(例: “weight(body:apple in 123)…”)から、正規表現などを用いてマッチした単語(apple, iphoneなど)を抽出する
  3. 抽出した単語を、Analyze APIの結果も考慮しながら元のキーワードリスト([“Apple”, “iPhone”])と照合し、どのキーワードが実際にマッチしたかを特定する。
OpenSearch Documentation
Analyze API Analyze API Introduced 1.0

この方法は、実装が非常に複雑であるだけでなく、ヒットした記事一件一件に対してこの重い処理を行うため、OpenSearchクラスターから「cancelled task with reason: heap usage exceeded」というエラーが出ました。

{
  "error": {
    "root_cause": [
      {
        "type": "rejected_execution_exception",
        "reason": "cancelled task with reason: heap usage exceeded [2.2gb >= 10.3mb]"
      },
      {
        "type": "rejected_execution_exception",
        "reason": "cancelled task with reason: heap usage exceeded [2.3gb >= 10.3mb]"
      },
      {
        "type": "rejected_execution_exception",
        "reason": "cancelled task with reason: heap usage exceeded [2.1gb >= 10.3mb]"
      }
    ],
    ...
  }
}

このエラーは、ヒープメモリの使用量が許容限界を超えたために発生します。調査の結果、主な原因は

explain:true の使用であることが判明しました。explainは「どのキーワードがマッチしたか」を調べるためには役立ちますが、その詳細な計算過程を出力するためにOpenSearchのCPUやメモリなどのリソースを大量に消費するのです。 エラーはステージング・開発環境でのみ発生していましたが、データ量が増加する本番環境でも同様の問題が発生する懸念があり、解決策を検討しました。

新しい抽出方法:Multi-Search APIによる改善

上記の問題を解決するために、OpenSearchのMulti-Search APIを導入しました。

OpenSearch Documentation
Multi-search Multi-search API Introduced 1.0

このアプローチの最大のポイントは、上記の問題の原因だったexplain:true不要にできる点です。

複雑な単一の大きなクエリを投げる代わりに、キーワードごとに独立した単純な検索クエリに分割します。これらのクエリをMulti-Search APIで一度のHTTPリクエストにまとめて送信します。これにより、ネットワークの往復回数を増やすことなく、複数の検索を効率的に実行できます。

キーワードごとにヘッダー(インデックス指定)とクエリボディをペアにして並べるだけ、という非常に分かりやすい形式です。

GET /_msearch
// "Apple"のクエリ
{"index": "index"}
{"query": {"match": {"body": "Apple"}}}
// "iPhone"のクエリ
{"index": "index"}
{"query": {"match": {"body": "iPhone"}}}
// "旅行"のクエリ
{"index": "index"}
{"query": {"match": {"body": "旅行"}}}

Multi-Search APIからのレスポンスは、リクエストしたクエリと同じ順序の結果が配列で返ってきます。

{
  "responses": [
    { // "Apple"に対する検索結果
      "hits": { "hits": [ /* ... */ ] }
    },
    { // "iPhone"に対する検索結果
      "hits": { "hits": [ /* ... */ ] }
    },
    { // "旅行"に対する検索結果
      "hits": { "hits": [ /* ... */ ] }
    }
  ]
}

これにより、どのレスポンスがどのキーワード(「Apple」など)に対応するかが明確になり、explain:trueを使わなくても、どの記事がどのキーワードにマッチしたかを簡単に特定できるようになります。

複数のキーワードがマッチする記事の扱い

では、一つの記事に複数のキーワードが含まれる場合はどう処理するのでしょうか。先ほどの「来月の旅行にそなえて、Appleで最新のiPhone16を買いました」という記事を例に考えてみましょう。

この場合、この記事は「Apple」「iPhone」「旅行」の3つのクエリ全ての検索結果に含まれることになります。

{
  "responses": [
    { // "Apple"に対する検索結果
      "hits": { "hits": [ /* ... 例の記事がここに含まれる ... */ ] }
    },
    { // "iPhone"に対する検索結果
      "hits": { "hits": [ /* ... 例の記事がここにも含まれる ... */ ] }
    },
    { // "旅行"に対する検索結果
      "hits": { "hits": [ /* ... ここにも含まれる ... */ ] }
    }
  ]
}

アプリケーション側では、レスポンス配列を順番に見ていき、記事IDごとにどのキーワードの検索結果に含まれていたかを記録します。

例えば、

  1. 最初のレスポンス(”Apple”の結果)に記事Aがあれば、「記事Aは”Apple”にマッチ」と記録します。
  2. 次のレスポンス(”iPhone”の結果)にも記事Aがあれば、「記事Aは”iPhone”にもマッチ」と追加で記録します。
  3. 同様に、3番目のレスポンス(”旅行”の結果)にも記事Aがあれば、「記事Aは”旅行”にもマッチ」と記録します。

この処理を全記事に対して行うことで、最終的に「記事AはApple、iPhone、旅行の3つのキーワードにマッチした」と簡単に特定できます。これは、複雑なexplainの結果をパースする以前の方法に比べて、はるかにシンプルで直感的な実装です。

検索速度とパフォーマンスの向上

Multi-Search APIに切り替えた後、良い結果が見られました。以前発生していた「cancelled task with reason: heap usage exceeded」というエラーは解消されました。また、OpenSearchのレスポンスタイムが約2〜3倍に速くなりました。

結論

OpenSearchでMulti-Search APIを利用することで、クリップデータ処理プロセスにポジティブな改善をもたらすことができました。これはOpenSearchの機能を活用し、パフォーマンスを最適化した一例です。もしあなたが検索パフォーマンスに関する同様の問題に直面しているなら、Multi-Search APIは有効な解決策となるかもしれません。

  • URLをコピーしました!

この記事を書いた人

開発部のバックエンドエンジニア

目次