Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

ElasticsearchでLIKE検索のような部分一致検索を高速に実現する方法

この記事は
Elastic Stack (Elasticsearch) - Qiita Advent Calendar 2024 - Qiitaの11日目の記事です。

はじめまして。テクニカルコンサルタントの江見と申します。
普段はElasticsearchに関するコンサルティング業務に携わっております。

業務の中で、RDBMySQLPostgreSQLなど)の検索機能に関する課題として、「LIKE検索の速度が遅い」という声を多くいただきます。
特に、大量のデータを扱うシステムでは、LIKE検索が原因でパフォーマンスが低下し、検索レスポンスの遅延が問題となることが少なくありません。その解決策として、RDBからElasticsearchへの移行を検討されるケースが増えています。

Elasticsearchは、高速で柔軟な全文検索が可能な強力な検索エンジンです。ただし、その性能を十分に引き出すためには、適切なデータ設計やクエリ設計が重要です。

本記事では、「SQLのLIKE検索による部分一致検索をElasticsearchで高速に実現する方法」に焦点を当てて解説します。

1.データ型の違いについて

Elasticsearchで文字列検索をする前に、まず理解しておきたいのはkeyword型とtext型の違いです。
Elasticsearchに文字列型フィールドを登録する際にはどちらの型として登録するかを事前に設定する必要があります。

keyword型:正確な文字列一致向け

keyword型は文字列をそのままの形でインデックスに登録する型です。
完全一致検索やソート、集計といった操作を高速に行うことができるため、IDや商品カテゴリのような短い文字列などはkeyword型で登録します。

text型:フルテキスト検索向け

text型は自然言語処理やフルテキスト検索を得意とする型です。
この型に設定されたフィールドはインデックスに登録時に、Analyzerによって文章を単語やフレーズごとに分割され登録されます。

どのような規則で文章をトークン化するかはAnalyzerによって決まり、検索要件に応じて適切なAnalyzer設計が必要になります。
analyzer | Elasticsearch Guide [8.16] | Elastic

文字列がどのようにトークン化されて登録されるかはAnalyze APIを使って確認ができます。
以下はデフォルトのAnalyzer(standard Analyzer)でトークン化をした例です。

POST _analyze
{
  "text":     "Elasticsearch is powerful"
}

以下のように、「elasticsearch」「is」「powerful」のように分割されているのがわかります。

{
  "tokens": [
    {
      "token": "elasticsearch",
      "start_offset": 0,
      "end_offset": 13,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "is",
      "start_offset": 14,
      "end_offset": 16,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "powerful",
      "start_offset": 17,
      "end_offset": 25,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

部分一致検索の対象となるフィールドはtext型で登録することが基本となります。

2.部分一致検索を実現する方法と特徴

keyword型での部分一致検索

keyword型のフィールドでもwildcardクエリを使用することで部分一致での検索は可能です。
wildcardクエリはLIKE検索と同様に特定のパターンに一致する文字列を検索するクエリでSQLのLIKE検索と直感的に近い検索方法です。
ただし検索時の計算コストが非常に高いためインデックスサイズが大きくなればなるほど、検索システムに悪影響を及ぼす可能性が高くなります。

wildcardクエリの例(”Elasticsearch”から始まる文字列を検索する場合)

GET test_index/_search
{
  "query": {
    "wildcard": {
      "message": {
        "value": "Elasticsearch*"
      }
    }
  }
}

text型での部分一致検索

text型で部分一致検索を行う場合はmatchクエリまたはmatch_phraseクエリを使用するのが一般的です。
text型のフィールドがトークン化されてインデックスに登録されるのと同じく、
検索文字列もトークン分割し、検索対象フィールドと検索文字列それぞれのトークン同士が一致していればヒットするクエリです。
matchクエリは単語検索、match_phraseクエリはフレーズ検索に適しています。

matchクエリの例(”Elasticsearch”から始まる文字列を検索する場合)

GET test_index/_search
{
  "query": {
    "match": {
      "message": "Elasticsearch"
    }
  }
}

3.適切なAnalyzerの設計

Elasticsearchで文字列検索を行う場合はmatchやmatch_phraseクエリを使うのが基本ですが、Analyzerを適切に設定していない場合想定している結果が返ってこない、逆に多くの検索結果が返ってきてしまうなどの問題が発生します。

例えば1の例で登録したtest_indexに対して、”power"という文字列で検索した場合はヒットしません。

”Elastic”で検索したクエリ

GET test_index/_search
{
  "query": {
    "match": {
      "message": "power"
    }
  }
}

検索結果

{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  }
}

ヒットしないのはインデックスに登録されているのは「elasticsearch」「is」「powerful」のトークンであり、
「power」というトークンは登録されていないためです。
そのため意図した検索結果を実現するためには

  1. データ登録時のAnalyzer
  2. 検索文字列のAnalyzer

をそれぞれ適切に設計する必要があります。

4.text型フィールドに対して、LIKE句と似た検索をする方法

LIKE句のような、特定のパターンの文字列が含まれる文字列を検索をtext型フィールドに対して実現するためには、
N-gram tokenizerを含むAnalyzerを適用したフィールドに対して、match_phraseをかけることで実現可能です。

N-gram tokenizerを使用すると、登録された文字列を任意の文字列ずつ機械的トークン化します。
例えば「Elasticsearch」という文字列を2文字ずつのN-gram(bi-gram)でトークン化すると
「El」「la」「as」・・・「rc」「ch」のように2文字ずつトークン化されインデックスに登録されます。

以下のようにインデックス作成時にAnalyzerを設定することで、N-gram Tokenizerを含むAnalyzerを特定のフィールドに設定することができます。

PUT test_index
{
  "mappings": {
    "properties": {
      "message": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 2
        }
      }
    }
  }
}


match_phraseで「lastic」と検索した場合でも「la」「as」「st」「ti」「ic」とトークン化され
上記のトークンがすべて一致する文字列のみがヒットします。
これによりLIKE検索に相当する部分一致検索を実現することが可能になります。

GET test_index/_search
{
  "query": {
    "match_phrase": {
      "message": "lastic"
    }
  }
}
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.1507283,
    "hits": [
      {
        "_index": "test_index",
        "_id": "ESDup5MBGJks9_KvUZZQ",
        "_score": 1.1507283,
        "_source": {
          "message": "Elasticsearch is powerful"
        }
      }
    ]
  }
}

5.まとめ

今回はElasticsearchでLIKE検索のような部分一致をさせるための方法を紹介しました。
部分一致検索を実現するだけであればwildcardクエリを使えば可能ですが、
システムの運用時の検索パフォーマンスなどを考えると、事前のAnalyzer設定などの手間が必要となるものの
N-gram × match_phraseクエリを使うのが基本となります。

もちろん検索要件によってより細かなAnalyzeやクエリの設計が必要となります。
特に、検索パフォーマンスと精度のバランスをどう取るかは、Elasticsearchを活用する上で避けて通れないポイントです。
今回の記事が、Elasticsearchにおける検索設計の参考になれば幸いです。

アクロクエストでは幅広いユースケースに対してElastic Cloud/Elastic Stackをご利用頂く際の導入検討から運用まで幅広くサービスを提供しています。
Elaticsearchコンサルティングサービス

実際にElastic Cloudのご利用を検討される場合は是非お気軽にお問い合わせください。
ENdoSnipe/Elasticsearchお問い合わせフォーム

Acroquest Technologyでは、キャリア採用を行っています。

  • Azure OpenAI/Amazon Bedrock等を使った生成AIソリューションの開発
  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • マイクロサービス、DevOps、最新のOSSクラウドサービスを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 

少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

www.wantedly.com