本記事は、パート3で説明された制御プレーンアーキテクチャのElasticsearch実装についての技術的な詳細を掘り下げ、Elasticsearchパーコレーターを使用して構築する方法を示します。本稿では、決定論的でガバナンスを備えたポリシーエンジンを実運用環境で実装するために使用されるパターンについて概説します。
アーキテクチャーから実装へ
パート3では、制御プレーンアーキテクチャについて説明しました。具体的には、ルックアップのプリミティブとしての逆マッチング、マッチングとアクションを分離するポリシー文書、そして複数のポリシーを単一の実行計画に構成するカスケード変換についてです。この投稿では、ポリシールックアップを駆動するElasticsearchの機能であるパーコレータークエリについて詳しく説明します。
パーコレーターは、制御プレーンが必要とするまさにその方法で探索の方向を反転させるため、ガバナンスに非常に適しています。この投稿では、パーコレーターの機能とその重要性の明確な説明から始め、インデックス設計、ポリシーストレージ、クエリ時評価、マルチポリシー構成に至るまでの実施手順を詳しくご紹介します。
通常の検索の仕組み
eコマースシステムでは、 title 、 category 、 priceなどのフィールドを含む数十万または数百万の商品ドキュメントが存在する場合があります。ユーザーが一致するドキュメントを検索すると、Elasticsearchに対して、ユーザーの検索文字列をこれらの商品ドキュメントに保存されている1つ以上のフィールドと比較するように指示することになります。Elasticsearchのデフォルトアナライザーである標準アナライザーは、テキストを小文字に変換し、トークンに分割します。「oranges」で検索すると「Oranges」が小文字で表示され、検索結果に一致します。語幹解析を含む言語認識アナライザーを使えば、両方の形が同じ語幹に還元されるため「orange」にも合致します。例えば、次の一致クエリは、 “title”フィールドに「orange」または「oranges」を含むドキュメントを返します。
そのため、上記のクエリでは、Elasticsearchはtitleフィールドが「oranges」にマッチする商品ドキュメントを返します。これには「Orange Fruit Spread」、「Orange Juice」、「Juicy oranges」、「Orange Marmalade」などの結果が含まれる可能性があります。覚えておくべき重要な点は、Elasticsearchは一般的に検索文字列をドキュメントと比較し、検索文字列に一致するドキュメントを返すために使用されるということです。

ガバナンスの問題:商品を検索する前に関連するポリシーを見つけること
パート1~3で確立されたように、ガバナンスを備えた検索システムは、ユーザーの検索文字列を直接商品カタログに送信しません。まず、その検索文字列に適用されるポリシーがあるかどうかを確認します。
マーチャンダイザーは、誰かが「オレンジ」を検索したときに、結果をオレンジのカテゴリーに制限し、オレンジジュース、オレンジマーマレード、オレンジソーダを除外することを決定しました。そのビジネス上の意思決定はポリシーとして格納されます。ユーザーが「オレンジ」と入力すると、制御プレーンはそのポリシーを見つけ、その指示を読み取り、それに応じて商品カタログに対する検索を修正する必要があります。そのためには、制御プレーンは、どの保存済みポリシーがこの検索文字列に関連するかを判断する必要があります。
企業環境における導入事例では、このようなポリシーが数百、あるいは数千にも及ぶ可能性があります。それらをif/elseロジックで1つずつチェックすることは、パート2で説明されているアプリケーションレイヤーのアンチパターンです。必要なのは、すべてのポリシーをインデックスに格納し、与えられた検索文字列に一致するものを即座に見つける方法です。そこでパーコレーターが役立ちます。
方向転換:パーコレーター
以前にも述べたように、通常の検索では、Elasticsearchは検索文字列をドキュメントと比較し、その検索文字列を含むドキュメントを返すためによく使用されます。
パーコレーターはこれを反転させます。パーコレーターを使用すると、各ドキュメントがクエリパターンを格納するインデックスがあり、その入力された検索文字列がこれらの格納されたクエリと比較され、どの格納されたクエリパターンがトリガーされたかを判断します。

ガバナンスにおいて、「保存されたクエリパターン」はポリシーとなります。各ポリシーには、一致させる検索文字列の種類を示すパターンが含まれています。例えば、検索文字列は「oranges」と完全に一致するでしょうか、それとも検索文字列に「olive oil」が含まれているでしょうか。入力される文字列はユーザーの検索テキストであり、クエリ実行時に到着し、保存されているすべてのポリシーパターンと照合する必要があります。これはPRISMの関連動画4:09で取り上げられています。
段階的に確認:検索で「oranges」がポリシーを見つける方法
ポリシー
あるマーチャンダイザーが、ユーザーが他の言葉を含めずに正確に「オレンジ」を検索した場合に一致するポリシーを作成しました。パーコレーターが一致すると、ドキュメントの残りの部分には、制御プレーンがプロダクトクエリの構築に使用するルールが含まれます。この例では、ルールの1つは結果を「果物」カテゴリーに制限(フィルター)することです。
percolatorフィールドには、このポリシーがいつ発動するべきかを定義するパターンが含まれています。この場合、それはフレーズ"START oranges END"に一致します。rule_typeフィールドとrule_argsフィールドは、ポリシーが発動したときに実行する内容を定義します。STARTトークンとENDトークンは境界マーカーです。これについては後ほど説明します。
PRISM StudioのUIでポリシーがどのように作成されているかは、関連PRISM動画の2:52で確認できます。
ユーザーが検索
購入者が検索バーに「oranges」と入力します。
制御プレーンがポリシーの一致をチェック
商品カタログを検索する前に、制御プレーンはユーザーの検索文字列をインターセプトし、境界マーカーでラップして、それをパーコレーターに送信します。
文字列 "START oranges END" は、保存されているすべてのポリシーパターンと照合されます。内部的には、Elasticsearchは保存されているポリシーパターンをこの文字列に対して実行し、一致するものを返します。それがパーコレーターです。ユーザーの検索文字列は、保存されているすべてのポリシーパターンと照合され、一致するものが返されました。if/elseチェーンや逐次評価はありません。インデックスがマッチングを処理します。
制御プレーンはポリシーを適用
制御プレーンはマッチしたポリシーのアクションを読み取ります。上記のポリシーは、制御プレーンに対し、検索結果を「果物」カテゴリに限定するよう指示するものです。コントロールプレーンは、商品カタログに対して最終的なElasticsearchクエリを以下のように構築します:
ユーザーは「oranges」を検索しました。商品カタログは、果物カテゴリに限定された「oranges」のクエリを受け取ります。この制約のため、オレンジジュース、オレンジマーマレード、オレンジソーダは除外されます。
「オレンジマーマレード」がオレンジポリシーを発動しない理由
別のユーザーが「orange marmalade」を検索すると仮定します。制御プレーンは文字列をラップし、パーコレーションします: "START orange marmalade END" 。オレンジポリシーのパターンは match_phrase: "START oranges END"です。オレンジポリシーは一致しないため、ポリシーは適用されず、結果は「果物」カテゴリーに限定されません。
これがSTARTおよびEND境界マーカーの目的です。これがないと、「oranges」という単語にマッチするポリシーが、誤って「orange marmalade」のようなクエリにマッチしてしまう可能性があります。ユーザーの検索文字列をSTARTとENDで囲み、それらのマーカーをポリシーのパターンに含めることで、ポリシーが「oranges」が完全な検索文字列であり、他の単語を含まない場合にのみ発動することを保証します。これは購入者とマーチャンダイザーの意図の両方に一致しています。
第二のポリシー:語幹解析されたフィールド上の「olive oil」
すべてのポリシーが正確な文字列一致を必要とするわけではありません。「olive oil」ポリシーは語幹解析されたフィールドで一致するため、単語形式のわずかな変化に関係なく発動します。
このポリシーのパターンはqueryではなくquery.stemmedに一致します。ユーザーの検索文字列が到着すると、それはqueryフィールド(正確なテキスト)とquery.stemmedフィールド(語幹解析アナライザーで分析され、単語がその語幹に削減されるため、「olives」と「olive」は同じ語幹に削減され、「oils」と「oil」も同じ語幹に削減される)の両方に格納されます。ポリシーのパターンは、文字列の語幹解析されたバージョンに対してチェックされるため、単語形式のわずかな変化に関係なくトリガーされます。
STARTとEND境界マーカーは、語幹解析されたフィールドでも機能するため、このポリシーは「olive oil」が検索文字列全体である場合にのみ発動し、より長い文字列の一部として出現する場合は発動しません。
この記事の残りの部分では、本番環境で使用できるようにするための実装の詳細について説明します。具体的には、2つのマッチングモードをサポートするインデックスマッピング、ハイライトがフレーズの削除と消費フレーズの追跡をどのように促進するか、そして複数の相反するポリシーがどのように単一の実行プランに統合されるかなどです。
ポリシーインデックスのマッピング
ポリシーインデックスには、格納するクエリパターンを保持するパーコレーターフィールドと、パーコレーターが一致させる対象となる入力検索文字列の構造を反映するテキストフィールドが必要です。以下のマッピングは明確さのために簡略化されています。本番環境への導入はより複雑で、境界マーカーの処理、変数パターンマッチング(例えば、「4ドル未満」に通貨値が含まれていることを認識するなど)、その他の種類の分析を行うために、カスタムアナライザーが使用されます。
インデックスにはpoliciesという名前が付けられています。これは、各ドキュメントがパート2で定義されているように完全な管理ポリシーを表しているためです。これには、一致基準、アクション、優先度、メタデータが含まれます。rule_typeおよびrule_argsフィールドにはポリシーのアクションコンポーネントが含まれており、これらには制御プレーンが商品カタログに対してクエリを実行するために使用する指示が含まれています。
queryフィールドとは、パーコレーターがマッチングする文字列のことです。このバージョンには、完全一致バージョンと語幹解析されたバージョンの2つのバリエーションがあります。ユーザーの検索文字列が到着すると、そのフィールドに一時的なメモリインデックスの中に入力されます。queryに一致するポリシーは完全一致する文字列を参照し、 query.stemmedに一致するポリシーは語幹解析されたバージョンを参照します。
ハイライト、フィルタリング、ソート機能を備えたパーコレーター
上記の簡単な例では、最小限のパーコレーター要求を示しました。実際には、制御プレーンはハイライト表示を追加し、無効化されたポリシーをフィルタリングし、優先度順に並べ替えます。
ハイライト構成では、フィールドキーとして"query"を使用し、matched_fields内の"query.stemmed"を使用します。これは、Elasticsearchの統合ハイライターに、親フィールドqueryハイライトを返すだけでなく、ハイライトするトークンを決定する際にquery.stemmedサブフィールドの一致も考慮するように指示します。これにより、語幹解析されたフィールドで一致するポリシーでも元のテキスト上に正確なハイライト範囲を生成できるようになり、これは制御プレーンがフレーズの削除や消費フレーズの追跡を行うために必要となります。
enabled: trueフィルターは無効なポリシーをスキップすることを保証します。優先度のsortは、優先度の高いポリシーが最初に返されることを保証し、制御プレーンがカスケード変換のために正しい順序でそれらを処理できるようにします。highlightフィールドは最も重要な追加要素です。これにより、ユーザーの検索文字列のどの単語が各マッチをトリガーしたかを正確に確認できます。
「olive oil」の検索結果は、以下のようなものになる場合があります。
ハイライトが重要な理由
応答のハイライト部分 "<em>START olive oil END</em>" に注目してください。Elasticsearchは、ユーザーの検索文字列のどの単語がポリシーを一致させたかを正確に教えてくれます。これは表面的なものではありません。ハイライトメタデータは、以下の2つの重要な下流の動作を左右します。
フレーズの削除。一部のポリシーでは、商品カタログクエリを作成する前に、検索文字列から一致したテキストを削除する必要があります。例えば、「安い」という条件に一致するポリシーでは、その単語が削除され、代わりに価格フィルターに変換されます。ハイライト表示によって、検索文字列のどの部分がポリシーに一致したかが正確に識別されるため、システムは削除すべき箇所を把握できます。
消費されたフレーズの追跡。パート3で説明したように、複数のポリシーが同じ検索文字列に一致した場合、優先度の高いポリシーが優先度の低いポリシーでも一致した単語を削除することがあります。各ポリシーのハイライトを現在の(進化する)検索文字列と比較することで、システムはフレーズが消費されたことを検出し、優先度の低いポリシーをスキップすることができます。これにより、二重処理が防止され、決定論的な動作が保証されます。
ハイライト表示の仕組みについてはこちらの記事で詳しく解説しています。
パーコレーターから実行計画へ
パーコレーターは一致するポリシーのセットを返します。しかし、パート3で説明したように、ルックアップは全体の一部に過ぎません。その後、それらの一致を一貫した実行計画にまとめる必要があります。具体的なクエリは次のようになります。
例:クリスマスキャンペーン中の「安いチョコレート」
システムには、「安いチョコレート」ポリシー(優先度210)と「クリスマスチョコレート」ポリシー(優先度300)という2つのアクティブなポリシーがあるとします。どちらもパート3で詳しく説明されています。
ステップ1:抽出する。ユーザーは「cheap chocolate(安いチョコレート)」を検索します。制御プレーンは検索文字列を "START cheap chocolate END" として巻き込み、パーコレーターに送ります。2つのポリシーが一致します。「安いチョコレート」ポリシーのパターンは「安いチョコレート」というフレーズに一致し、「クリスマスチョコレート」ポリシーのパターンは語幹解析されたフィールドを介して「チョコレート」に一致します。
ステップ2:優先順位で並べ替える。パーコレーターは両方のポリシーを優先順に並べて返します。「クリスマスチョコレート」ポリシー(300)が最初に処理され、次に「安いチョコレート」ポリシー(210)が処理されます。
ステップ3:カスケード変換を適用する。これはパート3のinitial state → [Policy A] → state' → [Policy B] → state'' → execution planモデルです。
「クリスマスチョコレート」ポリシー(優先順位300)が最初に適用されます。
- 「クリスマスの食べ物と飲み物」、「クリスマスのお菓子」というカテゴリーのハードフィルターを追加します。
- 「7ドル未満」価格フィルターを追加します。
- カテゴリーのソフトブースト「アドベントカレンダー」(3倍)を追加します。
「安いチョコレート」ポリシー(優先度210)は、修正された状態に対して次に適用されます。
- カテゴリーのハードフィルター「チョコレート」、「ミルクチョコレート」を追加しようとしましたが、クリスマスのポリシーですでにこのフィールドに
on_conflict: override設定されているため、「安いチョコレート」カテゴリーは削除されます。 - 「2ドル」の価格フィルターを追加しようとしましたが、クリスマスのポリシー設定は価格に対して
on_conflict: restrictに設定されており、2ドルは 7ドルよりも制限が厳しいので、2ドルが優先されます。 - 検索文字列から「cheap」を削除します。
ステップ4:Elasticsearchクエリを作成する。制御プレーンは、商品カタログに対する単一のElasticsearchクエリとして実行計画を組み立てます。
元の検索文字列は「cheap chocolate」でした。商品カタログに到達するクエリは、ガバナンスを備え、意図を考慮した検索計画です。「安い」という単語は消費され、価格制約に変換され、結果はクリスマスシーズンのカテゴリーに限定され、アドベントカレンダー商品はランキングが上がり、価格上限は優先度の低いポリシーによるより厳しい値を反映しています。すべての変換は決定論的であり、追跡可能であり、説明可能です。
これらの乗数が基本のBM25スコアとどのように相互作用するかについての概要については、関連PRISM動画の8:45をご覧ください。ここでは、乗算ブーストについて簡単に説明しています。
これがスケールする理由
パーコレーターはこのユースケースにおいて効率的です。なぜなら、非対称性が存在するからです。企業向けeコマースシステムには数百万の商品が存在しても、ポリシーは数百から数千件しかないかもしれません。パーコレーターは、入力された検索文字列を、保存されているポリシーパターンのセットと照合するだけであり、商品カタログ全体をスキャンするわけではありません。コストはポリシーの数に比例し、Elasticsearchは内部最適化(格納されたクエリパターンから用語をインデキシングし、ブールロジックを短絡させる)を適用してマッチングを高速化します。
新しいポリシーの追加は、単に新しいドキュメントをインデキシングすることです。いずれかを無効にすると、フィールドが更新されます。コードの変更も、デプロイも、再起動も一切不要です。
検索からガバナンスを備えた検索へ
パーコレーターは、パート3の制御プレーンアーキテクチャを大規模なスケールで実用的にする高速逆マッチングプリミティブを提供します。ポリシーとは、保存およびインデックス化され、入力された検索文字列と効率的に照合されるデータのことです。制御プレーンは、パート3で説明されているカスケード変換とフィールドごとの競合解決を通じて、マッチングポリシーをガバナンスに基づく実行計画へと統合します。そして、検索エンジンは商品カタログに対してガバナンスに基づく実行計画を実行します。
結果として、マーチャンダイザーがアプリケーションコードに触れることなく新しいポリシーを作成し、代表的なクエリに対してテストし、本番環境に導入し、即座に効果を確認できるシステムが実現します。パーコレーターはポリシーの検索を高速化し、制御プレーンはポリシーの構成を決定論的にし、ガバナンスを備えたワークフローはプロセス全体を安全にします。
このシリーズの次回作
このシリーズの次の投稿では、ガバナンス制御プレーンを新たな領域へと拡張します。多層検索アーキテクチャを紹介し、安定したページネーションとファセットを維持しながら、厳密な検索、緩やかな検索、セマンティックな検索をどのように連携させるかを説明します。
ガバナンスを備えたeコマース検索を実践
本稿で説明するパーコレーターベースの制御プレーンは、インデックスマッピングや境界マーカーから、ハイライト駆動型のフレーズ追跡、カスケード型ポリシー構成に至るまで、Elastic Services Engineeringが当社の再利用可能なeコマース検索アクセラレーターの一部として構築したものです。ここに示されているすべてのクエリ例とポリシー構造は、企業規模の商品カタログに対し、検証済みの実稼働しているシステムから得られたものです。
Elasticsearch上にガバナンスを備えたポリシー駆動型の制御プレーンを実装したい場合、 Elastic Services を利用すれば、より迅速に実現できます。Elastic Professional Servicesにお問い合わせください。
議論に参加
検索ガバナンス、検索戦略、またはeコマース検索アーキテクチャについてご質問がありますか?より広範なElasticコミュニティの議論に参加しましょう。




