ブログ

Elasticsearch ES|QLがインデックス化されていないデータへの全文検索を可能に

MATCHとTO_TEXTはインデックスを作成していないデータに全文検索を行います。ES|QLで計算列、マッピングされていないフィールド、フェデレーテッドソースを検索します。

Get hands-on with Elasticsearch: Dive into our sample notebooks in the Elasticsearch Labs repo, start a free cloud trial, or try Elastic on your local machine now.

ES|QL MATCHは、インデックスを作成していないデータに対して全文検索を実行するようになりました。計算された列、マッピングされていないフィールド、その場で組み立てられた文字列、さらにはS3に保存されているフェデレーションデータ。新しいTO_TEXT関数は、任意の文字列を解析可能なテキストとして扱うようにES|QLに指示するため、MATCHはクエリの存続期間中にのみ存在する値をトークン化し、大文字・小文字の正規化や用語の一致を行うことができます。これは、多くのクエリエンジンがインデックスなし文字列に対して提供しているLIKEやRLIKEパターンマッチングを超えた真の解析です。Elastic Cloud Serverlessで現在利用可能で、Elasticsearch 9.5ではテクニカルプレビューとして提供されています。

MATCHとTO_TEXTが任意のES|QL式で全文検索を可能にする方法

まずは、Elasticsearch 9.4では不可能だったクエリから始めます。このクエリは EVALコマンドを使用します。

FROM cooking_blog
| EVAL summary = TO_TEXT(CONCAT(title, description))
| WHERE MATCH(summary, "pancakes")
| KEEP title, author

この例では、要約にはマッピングやアナライザーの設定がありません。また、逆インデックスとも関連付けられていません。このクエリの有効期間中のみ存在しますが、それでも検索可能です。これを機能させるには、2つの追加要素が必要です。

まず、MATCHは、マップされたフィールドだけでなく、すべての式を最初の引数として受け入れるようになりました。これにはEVALで生成されたカラムや、インラインで使われる関数結果も含まれます。また、元のドキュメントから直接読み込む未マッピングフィールドも含まれます。さらに、この新しいユースケースでは、MATCHで通常受け入れられるすべてのデータタイプがサポートされます。

2つ目の要素は新しいTO_TEXT関数であり、テキスト型の出力を生成する最初のES|QL変換関数です。これまで、テキスト列はインデックス付きマッピングフィールドからのみ取得でき、ES|QL式によって生成される文字列はすべてテキストではなくキーワード値でした。MATCHは2つを異なる方法で扱うため、区別が重要です。テキスト値は分析され、キーワード値は正確に比較されます。これは、インデックス付きキーワードフィールドのMATCHクエリが用語クエリに書き換えられる方法を反映しています。TO_TEXT(x) は、ES|QLに対して、「この文字列を全文として扱う」ように指示する方法です。

これはElasticsearch 9.5のテクニカルプレビューとして提供されるため、いくつかの制限があります。

  • 現在はフィルタリング機能のみを提供しています。式に対するMATCHは、現時点では関連性スコアには影響しません。インデックス付きフィールドに対する一致のみがスコアに影響します。

  • 式を照合する際、あいまい検索などのクエリオプションはまだサポートされていません。

  • 実行時テキストは標準アナライザーで解析されます。これはまだ設定変更できません。

これらの制約に対処するための取り組みが進められています。

ES|QLでLIKEやRLIKEの代わりに全文検索を使うのはなぜですか?

ES|QLには、インデックスなしで文字列を検索するLIKE(ワイルドカードパターン)とRLIKE(正規表現)という2つの方法がすでにありました。どちらもどんな文字列式でも動作するので、MATCHが何を追加するのか疑問に思うのは当然です。答えは分析です。これは、語幹抽出や同義語などの技術を用いる、より高度な検索方法です。また、ストップワード処理も行います。

LIKEは文字列を構成する単語を理解せずに行う単純な部分文字列マッチングです。例えば、キツネ(fox)に関するログメッセージを探しているとします。

FROM app_logs
| WHERE message LIKE "*fox*"

これは大文字表記の「Fox spotted near the henhouse(鶏小屋の近くでキツネが目撃された)」とは一致しませんが、キツネとは全く関係のない「Outfoxed by the competition(競争相手に出し抜かれた)」とは一致します。大文字小文字の区別に関しては偽陰性、他の単語の中に埋もれた部分文字列に関しては偽陽性と、どちらの方向にも問題があります。

正則表現は格の問題を補うことができますが、単語境界の問題はすぐに複雑化してしまいます。例えばこんな感じです。

FROM app_logs
| WHERE message RLIKE "(.* )?[Ff][Oo][Xx]([ ,.:;].*)?"

これでさえ、正解ではありません。文末に「!」や「?」が続く場合、「fox」を見落としてしまいます。また、タブ、引用符、括弧については何も言及していません。修正を加えるたびにパターンが長くなり、次にクエリを読む人は、それが実際に何をしているのかをリバースエンジニアリングしなければならなくなります。

MATCHはクエリと値の両方をアナライザーに通し、テキストを小文字の単語にトークン化して、単語同士を照合するため、問題が解消されます。

FROM app_logs
| WHERE MATCH(TO_TEXT(message), "fox")

このクエリは、「The quick brown fox」や「FOX spotted near the henhouse」のような値には一致しますが、「Outfoxed by the competition」や「FOXTROT protocol enabled」には一致しません。単語の周囲の句読点に関係なく一致します。もちろん、これはすべて、MATCH(TO_TEXT(message), "brown fox") のような複数語クエリにも、期待どおりに機能します。

これまでインデックス化やマッピングが行われていなかったデータに対しても自然言語をサポートするため、 36個の専用言語アナライザーを利用できるようにするための作業が進められています。

インデックス化されていないデータやマッピングされていないデータの全文検索のユースケース

上記の例ではマッピングされたフィールドから計算された値を検索しました。ES|QL MATCHを式に適用する際のより興味深いユースケースは、これまで全く検索できなかったデータに関するものです。いくつか見ていきましょう。

マッピングを追加せずにES|QLでマップされていないフィールドを検索する方法

詳細なスタックトレースや生のリクエストペイロードなど、意図的にマッピングからフィールドを除外する場合もあります。デバッグ用のデータブロックを省略することもあります。これらのうちの1つをインデキシングすると、ドキュメントごとにディスクとヒープ容量が消費されます。四半期に1回しかクエリしないようなフィールドのためにインデックスを作成する価値はありません。

この決定は常に最終的なものでした。なぜなら マッピングされていないフィールド はクエリから完全に見えなかったからです。Elasticsearch 9.5では、SET unmapped_fields="load"を使用して、ES|QLにマッピングされていないフィールドをソースドキュメントからキーワードとして直接ロードさせることができます。次に、それをTO_TEXTで囲むと、全文検索を実行できるようになります。

SET unmapped_fields="load";
FROM app_logs
| WHERE MATCH(TO_TEXT(stack_trace), "java.lang.NullPointerException")
| KEEP @timestamp, service.name, message

ここでは、stack_traceはマッピングされていません。すべての値は元の文書から取得され、その場で分析されます。行ごとにマッチングされています。これが実際の作業であり、逆インデックス検索ほど高速になることは決してありません。しかし今では、インデックスを作成しなかったそのフィールドも検索不能ではなくなりました。日常的なケースではマッピングを小さく保ちつつ、重要な場面では四半期に一度の質問にも対応できます。

キーワードフィールドでの全文検索(再インデックスなし)

キーワードフィールドは多くのことを可能にします。正確なマッチング、高速なアグリゲーション、ソート機能を提供するため、多くのフィールドがそのようにマッピングされます。しかし、マッピングはデータが到着した時点で決定されるため、当初の意図とは異なる方法でデータを利用したくなる状況に陥ることがあります。例えば、product_nameがダッシュボードで集約されるためキーワードとしてマッピングされ、1年分の製品データを受け取った後に、誰かがproduct_nameの値内で検索できるようにしたいと考えることもあるでしょう。

以前の解決策は、マッピングをテキストに変更する(または複数フィールドを追加する)ことと、すべてを再インデックスすることでした。これは時間と費用がかかる場合があり、多くの場合、ユーザーはわざわざその手間をかけたくないことが多いです。新しい答えは、1つの関数呼び出しです。

FROM products
| WHERE MATCH(TO_TEXT(product_name), "wireless noise cancelling headphones")
| KEEP product_name, brand, price

TO_TEXTはキーワード値をその場でテキストに変換するため、MATCHはそれらを正確に比較するのではなく、分析します。これにより、マッピングやソースドキュメントの再インデックス作成なしでキーワードフィールドをクエリできます。検索が日常的なクエリになった場合でも、フィールドをテキストとしてインデックスすることが長期的には正しい選択ですが、TO_TEXTを使えば、追加の手間なく今日すぐに結果を得られます。

異なるマッピングを持つインデックス間で同じフィールドを探索

ES|QLは複数のインデックスにまたがることができ、同じフィールドであっても、すべてのインデックスで常に同じ形式である必要はありません。同じフィールドが異なるインデックスで異なる型を持つ場合、ES|QLはそれを共用型として扱い、変換関数によって競合を解決します。今年のインデックステンプレートではメッセージフィールドのタイプがテキストになっているものの、昨年のテンプレートではキーワードだったという例を考えてみましょう。

FROM logs-2025, logs-2026
| EVAL msg = TO_TEXT(message)
| WHERE MATCH(msg, "connection reset")

クエリ実行時には、テキストインデックス由来の値であろうとキーワードインデックス由来の値であろうと、すべての値が分析されます。古いインデックスのキーワード値は、他のすべてと同様にトークン化され、小文字に変換されるため、「connection reset」は、どのインデックスに存在していても「Connection RESET by peer」を検索します。

もう一つ興味深いケースは、フィールドが一方のインデックスにのみマップされているものの、もう一方のインデックスにも存在(かつマップされていない)場合です。

SET unmapped_fields="load";
FROM logs-2025, logs-2026
| WHERE MATCH(TO_TEXT(error_details), "timeout")

ここで指摘しておくべき微妙なニュアンスがあります。error_detailsがlogs-2026にマッピングされていて、logs-2025にはマッピングされていない場合、ElasticsearchはこのクエリをLuceneにプッシュできません。フィールドがマッピングされていないインデックスは、マッチを返さないためです。代わりに、プランナーはフィールドが潜在的に未マッピングであることに気づき、行の出所に関わらず、行ごとにMATCH全体を評価します。どのインデックスにフィールドがマッピングされているかを知る必要はありません。クエリがその質問に答えます。

ES|QLが逆インデックスなしでクエリ時にテキストを分析する方法

ES|QLが式に対してMATCHを計画する場合、クエリ文字列を事前に一度だけ解析して、一連の用語に分解します。各行がどのように評価されるかは、式の型によって異なります。

式の種類

処理

マッチング動作

テキスト(TO_TEXT経由)

アナライザーは値を小文字の用語にトークン化

トークン同士の比較。いずれかのトークンがクエリ用語のいずれかと一致する場合、その行は一致(ORセマンティクス)。

キーワード、IP、日付、数字

分析なし。クエリ定数は一度ネイティブ型に変換。

行ごとの正確な比較

どちらの経路もLuceneを完全に迂回し、値を行ごとに評価します。非テキストパスは、これらのフィールドタイプに対してLuceneにプッシュダウンされたマッチクエリの動作と完全に一致するため、クエリがインデックスにヒットするかどうかに関わらず、セマンティクスは一貫しています。

逆インデックス検索は取り込み時に作業を行い、クエリ時に一致しない文書には一切触れません。実行時マッチはクエリ時に、到達するすべての行に対してその分析を行います。一方は既に処理が完了しているため高速であり、もう一方はデータがインデックス化されている必要がないため柔軟性があります。

ES|QL全文検索の今後の展望は

この記事の内容はすべて、ES|QLの検索機能を、事前にインデックスを作成したデータだけでなく、あらゆるデータに対して動作させるための、より大規模な取り組みの第一弾です。先に挙げた制約事項については積極的に改善に取り組んでおり、ロードマップはさらに次の段階へと進んでいます。

  • スコアリング。実行時の一致結果も_scoreに反映されるため、データがインデックス化されていない場合でも、関連性に基づいてソートできます。

  • 式に対するMATCH_PHRASE。Elastic Cloud Serverlessですでに利用可能で、9.6でElastic Stackに登場します。

  • 設定可能なアナライザー。式に対するMATCHおよびMATCH_PHRASEのアナライザーサポートにより、クエリ実行時に言語アナライザー、ステミング、同義語を利用できるようになります。

  • マッチオプション。実行時マッチングのためのあいまい度や演算子などのオプション。

  • ベクトル検索。行ごとに埋め込みを生成し、実行時にdense_vector式に対してk近傍法(kNN)を実行することで、インデックス化されていないデータにもセマンティック検索を適用できるようにします。

ES|QLの全文検索を今すぐ試す

実行時検索を今すぐお試しください。新しいES|QL機能が最初に実装されるElastic Cloud Serverlessでは現在利用可能で、Elasticsearch 9.5ではテクニカルプレビューとして提供されます。まず検索関数の参照から始め、ES|QLの制限ページで現在の制限を確認してください。これはテクニカルプレビュー版であり、皆様からのフィードバックをお待ちしています。これまでインデックス登録されていなかったものを検索して、良い意味でも悪い意味でも驚いたことがあれば、ぜひお聞かせください

関連記事

プロンプトから1分以内にダッシュボードを作成、コストを5分の1に削減:KibanaのAIダッシュボードとカスタムVega-Liteチャート

Marta Bondyra

LINQ to Elasticsearch ES|QL:C#を記述してElasticsearchをクエリ

Florian Bernd

スイススタイルのハッシュテーブルを使用したより高速なES|QL統計

Chris Hegarty

Google MCP Toolbox for DatabasesにElasticsearchサポートを導入

Enrico Zimuel

ES|QL 9.2:スマートになったLookup Joinと時系列サポート

Tyler Perkins