Amazon Web Services ブログ

OpenSearch のクエリパターンに合わせたインデックス戦略の選択

本記事は 2026 年 2 月 12 日 に公開された「Matching your Ingestion Strategy with your OpenSearch Query Patterns」を翻訳したものです。

Amazon OpenSearch Service クラスターで適切なインデックス戦略を選択すると、効率性を維持しながら低レイテンシーで正確な結果を得られます。アクセスパターンに複雑なクエリが必要な場合は、インデックス戦略を見直すことをお勧めします。

本記事では、OpenSearch でカスタムインデックスアナライザーを作成し、Edge n-gram トークナイザーを使ってワイルドカードを使わずにプレフィックスクエリをマッチさせ、オートコンプリート機能を効率的に実装する方法を紹介します。

インデックスアナライザーとは

インデックスアナライザーは、ドキュメントの取り込み時にテキストフィールドを解析します。アナライザーが出力するトークンを使ってクエリをマッチングします。

デフォルトでは、OpenSearch は標準インデックスアナライザーでデータをインデックスします。標準インデックスアナライザーは、スペースでトークンを分割し、小文字に変換し、ほとんどの句読点を除去します。ログ分析などのユースケースでは、標準インデックスアナライザーだけで十分な場合もあります。

標準インデックスアナライザー

標準インデックスアナライザーの動作を見てみましょう。_analyze API を使って、標準インデックスアナライザーが「Standard Index Analyzer.」という文をどのようにトークン化するかテストします。

注意: 本記事のコマンドはすべて、OpenSearch DashboardDevTools で実行できます。

GET /_analyze
{
  "analyzer": "standard",
  "text": "Standard Index Analyzer."
}
#========
#Results
#========
{
  "tokens": [
    {
      "token": "standard",
      "start_offset": 0,
      "end_offset": 8,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "index",
      "start_offset": 9,
      "end_offset": 14,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "analyzer",
      "start_offset": 15,
      "end_offset": 23,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

各単語が小文字に変換され、ピリオド (句読点) が除去されていることがわかります。

独自のインデックスアナライザーを作成する

OpenSearch には、さまざまなアクセスパターンに対応する多数の組み込みアナライザーが用意されています。また、特定の検索ニーズに合わせたカスタムアナライザーも構築できます。次の例では、住所リストに対して部分一致を返すカスタムアナライザーを設定します。このアナライザーはオートコンプリート機能向けに設計されており、ユーザーが住所全体を入力しなくても素早く住所を見つけられます。オートコンプリートにより、マッチしたプレフィックスに基づいて検索語を補完できます。

まず、standard_index_test というインデックスを作成します。

PUT standard_index_test
{
  "mappings": {
    "properties": {
      "text_entry": {
        "type": "text",
        "analyzer": "standard"
      }
    }
  }
}

標準アナライザーはデフォルトのアナライザーであるため、analyzer に standard を指定する必要はありません。

テストのために、作成した standard_index_test にデータを一括追加します。

POST _bulk
{"index":{"_index":"standard_index_test"}} 
{"text_entry": "123 Amazon Street Seattle, Wa 12345 "} 
{"index":{"_index":"standard_index_test"}}
{"text_entry": "456 OpenSearch Drive Anytown, Ny 78910"}
{"index":{"_index":"standard_index_test"}}
{"text_entry": "789 Palm way Ocean Ave, Ca 33345"}
{"index":{"_index":"standard_index_test"}}
{"text_entry": "987 Openworld Street, Tx 48981"}

「ope」というテキストでデータをクエリします。

GET standard_index_test/_search
{
  "query": {
    "match": {
      "text_entry": {
        "query": "ope"
      }
    }
  }
}
#========
#Results
#========
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    "max_score": null,
    "hits": [] # No matches 
  }
}

「ope」で検索しても結果が返りません。理由を確認するために、標準インデックスアナライザーがテキストをどのようにトークン化しているか詳しく見てみましょう。標準インデックスアナライザーで住所「456 OpenSearch Drive Anytown, Ny 78910」をテストします。

POST standard_index_test/_analyze
{
  "analyzer": "standard",
  "text": "456 OpenSearch Drive Anytown, Ny 78910"
}
#========
#Results
#========
  "tokens":
      "456" 
      "opensearch" 
      "drive" 
      "anytown"
      "ny" 
      "78910"

標準インデックスアナライザーは住所を 456opensearchdrive などの個別のトークンに分割しています。つまり、個別のトークン (456opensearch など) を検索しない限り、oopope、さらには open でも結果は返りません。1 つの方法として、インデックスには標準インデックスアナライザーを使いつつ、ワイルドカードを使う方法があります。

GET standard_index_test/_search
{
  "query": {
    "wildcard": {
      "text_entry": "ope*"
    }
  }
}

ワイルドカードクエリは「456 OpenSearch Drive Anytown, Ny 78910」にマッチしますが、ワイルドカードクエリはリソース消費が大きく低速になる可能性があります。OpenSearch で ope* をクエリすると、転置インデックスのルックアップ最適化をバイパスして、インデックス内の各トークンを反復処理します。メモリ使用量が増加し、パフォーマンスが低下します。クエリと検索のパフォーマンスを向上させるには、アクセスパターンに適したインデックスアナライザーを使用します。

Edge n-gram

Edge n-gram トークナイザーは、単語のプレフィックスをトークン化することで、ワイルドカードを使わずに部分一致を実現します。たとえば、入力語 coffeeccocof などすべてのプレフィックスに展開されます。最小長 (min_gram) と最大長 (max_gram) の間のプレフィックスに制限できます。min_gram=3max_gram=5 の場合、「coffee」は cofcoffcoffe に展開されます。

Edge n-gram を使用するカスタムインデックスアナライザーで custom_index という新しいインデックスを作成します。最小トークン長 (min_gram) を 3 文字、最大トークン長 (max_gram) を 20 文字に設定します。min_grammax_gram はそれぞれ返されるトークンの最小長と最大長を設定します。アクセスパターンに基づいて min_grammax_gram を選択してください。この例では「ope」という語で検索するため、oop のような語は検索しないので最小長を 3 未満にする必要はありません。min_gram を低く設定しすぎるとレイテンシーが高くなる可能性があります。同様に、個別のトークンが 20 文字を超えることはないため、最大長を 20 より大きくする必要はありません。最大長を 20 に設定することで、将来より長いトークン長の住所を取り込む場合にも余裕を持たせています。なお、ここで作成するインデックスはオートコンプリート機能に特化したものであり、一般的な検索インデックスには不要な場合があります。

PUT custom_index
{
  "mappings": {
    "properties": {
      "text_entry": {
        "type": "text",
        "analyzer": "autocomplete",         
        "search_analyzer": "standard"       
      }
    }
  },
  "settings": {
    "analysis": {
      "filter": {
        "edge_ngram_filter": {
          "type": "edge_ngram",
          "min_gram": 3,
          "max_gram": 20
        }
      },
      "analyzer": {
        "autocomplete": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "edge_ngram_filter"
          ]
        }
      }
    }
  }
}

上記のコードでは、autocomplete というカスタムアナライザーを持つ custom_index というインデックスを作成しました。このアナライザーは以下の処理を行います。

  • 標準トークナイザーでテキストをトークンに分割する
  • lowercase フィルターですべてのトークンを小文字に変換する
  • edge_ngram の最小値と最大値に基づいてトークンをさらに小さなチャンクに分割する

検索アナライザーには標準アナライザーを設定し、検索時のクエリ処理を軽減しています。取り込み時にカスタムアナライザーでテキストを分割済みのため、検索時に同じ処理を繰り返す必要はありません。カスタムアナライザーが「Lexington Avenue」というテキストをどのように解析するかテストします。

GET custom_index/_analyze
{
  "analyzer": "autocomplete",
  "text": "Lexington Avenue"
}
#========
#Results
#========
# Minimum token length is 3 so we won't see l, or le
    "tokens": 
        "lex"  
        "lexi"  
        "lexin"  
        "lexing" 
        "lexingt" 
        "lexingto"    
        "lexington" 
        "ave"        
        "aven" 
        "avenu" 
        "avenue"

トークンが小文字に変換され、部分一致に対応していることがわかります。アナライザーがテキストをどのようにトークン化するか確認できたので、データを一括追加します。

POST _bulk
{"index":{"_index":"custom_index"}} 
{"text_entry": "123 Amazon Street Seattle, Wa 12345 "} 
{"index":{"_index":"custom_index"}}
{"text_entry": "456 OpenSearch Drive Anytown, Ny 78910"}
{"index":{"_index":"custom_index"}}
{"text_entry": "789 Palm way Ocean Ave, Ca 33345"}
{"index":{"_index":"custom_index"}}
{"text_entry": "987 Openworld Street, Tx 48981"}

テストしてみましょう。

GET custom_index/_search
{
  "query": {
    "match": {
      "text_entry": {
        "query": "ope" 
      }
    }
  }
}
#========
#Results
#========
 "hits": [
      {
        "_index": "custom_index",
        "_id": "aYCEIJgB4vgFQw3LmByc",
        "_score": 0.9733556,
        "_source": {
          "text_entry": "456 OpenSearch Drive Anytown, Ny 78910"
        }
      },
      {
        "_index": "custom_index",
        "_id": "a4CEIJgB4vgFQw3LmByc",
        "_score": 0.4095239,
        "_source": {
          "text_entry": "987 Openworld Street, Tx 48981"
        }
      }
    ]

カスタム n-gram アナライザーを設定して、住所リスト内の部分一致を実現できました。

なお、非標準のインデックスアナライザーの使用と計算コストの高いクエリの記述にはトレードオフがあります。アナライザーは、特に非効率に使用した場合、インデックスのスループットに影響を与え、全体のインデックスサイズを増加させる可能性があります。たとえば、custom_index の作成時に検索アナライザーを標準アナライザーに設定しました。取り込み時と検索時の両方で n-gram を使用していれば、クラスターのパフォーマンスに不要な負荷がかかっていたでしょう。さらに、min_grammax_gram をアクセスパターンに合った値に設定し、検索ユースケースに必要以上の n-gram を作成しないようにしました。適切な設定により、取り込みスループットに影響を与えず、検索の最適化によるメリットを得られました。

まとめ

本記事では、オートコンプリートクエリを簡素化し高速化するために、OpenSearch のデータインデックス方法を変更しました。今回のケースでは、Edge n-gram を使用することで、ワイルドカードクエリによるクラスターパフォーマンスへの影響なしに、住所の一部をマッチさせて正確な結果を得られました。

本番環境にデプロイする前に、必ずクラスターをテストしてください。インデックスと検索の両面からクラスターを最適化するには、アクセスパターンの理解が不可欠です。本記事のガイドラインを出発点として活用してください。インデックスを作成する前にアクセスパターンを確認し、テスト環境でさまざまなインデックスアナライザーを試して、クエリの簡素化やクラスター全体のパフォーマンス向上に役立つか確認してください。OpenSearch クラスターの一般的な最適化手法については、Get started with Amazon OpenSearch Service: T-shirt-size your domain の記事を参照してください。

著者について

Rakan Kandah

Rakan Kandah

Rakan は、AWS のソリューションアーキテクトです。余暇にはギターの演奏や読書を楽しんでいます。


この記事は Kiro が翻訳を担当し、Solutions Architect の 榎本 貴之 がレビューしました。