Elasticsearchクエリからmilvusへ

Apache Luceneをベースに構築されたElasticsearchは、オープンソースの代表的な検索エンジンです。しかし、高い更新コスト、劣悪なリアルタイムパフォーマンス、非効率的なシャード管理、非クラウドネイティブデザイン、過剰なリソース要求など、最新のAIアプリケーションでは課題に直面しています。Milvusは、クラウドネイティブなベクトルデータベースとして、ストレージとコンピューティングの分離、高次元データの効率的なインデックス作成、最新のインフラとのシームレスな統合により、これらの問題を克服します。AIワークロードに優れたパフォーマンスとスケーラビリティを提供する。

この記事では、ElasticsearchからMilvusへのコードベースの移行を促進することを目的とし、その間にクエリを変換する様々な例を提供します。

概要

Elasticsearch では、クエリコンテキストでの操作は関連性スコアを生成するが、フィルタコンテキストでの操作は関連性スコアを生成しない。同様に、Milvusの検索は類似度スコアを生成しますが、フィルタのようなクエリは生成しません。ElasticsearchからMilvusにコードベースを移行する場合、Elasticsearchのクエリコンテキストで使用されているフィールドをベクトルフィールドに変換し、類似度スコアを生成できるようにすることが重要です。

以下の表はElasticsearchのクエリパターンとMilvusでの対応するクエリパターンの概要です。

Elasticsearch クエリ

Milvusでの同等物

備考

フルテキストクエリ

マッチクエリ

全文検索

どちらも同様の機能を提供する。

用語レベルクエリ

ID

in 演算子

これらの Elasticsearch クエリがフィルタコンテキストで使用される場合、どちらも同じか類似の機能セットを提供します。

プレフィックスクエリ

like 演算子

範囲クエリ

>,<,>=, のような比較演算子<=

タームクエリ

のような比較演算子==

用語クエリ

in 演算子

ワイルドカード・クエリー

like 演算子

ブール演算子

などの論理演算子AND

どちらも、フィルタコンテキストで使用すると、同様の機能を提供します。

ベクトルクエリ

kNNクエリ

検索

Milvusはより高度なベクトル検索機能を提供します。

逆ランクフュージョン

ハイブリッド検索

Milvusは複数の再ランク付け戦略をサポートします。

フルテキストクエリ

Elasticsearch では、フルテキストクエリによってメール本文のような分析されたテキストフィールドを検索することができます。クエリ文字列はインデックス作成時にフィールドに適用されたものと同じアナライザーを使って処理されます。

マッチクエリ

Elasticsearch では、マッチクエリは指定されたテキスト、数値、日付、ブール値にマッチするドキュメントを返します。指定されたテキストはマッチする前に分析されます。

以下はマッチクエリを使った Elasticsearch 検索リクエストの例です。

resp = client.search(
    query={
        "match": {
            "message": {
                "query": "this is a test"
            }
        }
    },
)

milvusでは、全文検索機能により同様の機能を提供しています。上記のElasticsearchクエリをMilvusに変換すると以下のようになります:

res = client.search(
    collection_name="my_collection",
    data=['How is the weather in Jamaica?'],
    anns_field="message_sparse",
    output_fields=["id", "message"]
)

上記の例では、message_sparsemessage という VarChar フィールドから派生したスパースベクトルフィールドです。Milvus は BM25 埋め込みモデルを使用して、message フィールドの値をスパースベクトル埋め込みに変換し、message_sparse フィールドに格納します。検索要求を受信すると、Milvusは同じBM25モデルを使用してプレーンテキストのクエリペイロードを埋め込み、スパースベクトル検索を実行し、output_fields パラメータで指定されたidmessage フィールドを対応する類似度スコアとともに返します。

この機能を使用するには、message フィールドのアナライザを有効にし、そこからmessage_sparse フィールドを導出する関数を定義する必要があります。Milvusでアナライザーを有効にし、派生関数を作成する詳細な手順については、全文検索を参照してください。

タームレベルクエリ

Elasticsearch では、日付範囲、IP アドレス、価格、商品 ID などの構造化データの正確な値に基づいてドキュメントを検索するために、用語レベルクエリが使用されます。このセクションでは、Elasticsearch のタームレベルクエリに相当する Milvus のクエリを紹介します。このセクションの全ての例は、Milvusの機能に合わせてフィルタコンテキスト内で動作するように調整されています。

ID

Elasticsearchでは、以下のようにフィルタコンテキスト内でIDに基づいて文書を検索することができる:

resp = client.search(
    query={
        "bool": {
            "filter": {
                "ids": {
                    "values": [
                        "1",
                        "4",
                        "100"
                    ]
                }            
            }
        }
    },
)

Milvusでも以下のようにIDを元にエンティティを検索することができます:

# Use the filter parameter
res = client.query(
    collection_name="my_collection",
    filter="id in [1, 4, 100]",
    output_fields=["id", "title"]
)

# Use the ids parameter
res = client.query(
    collection_name="my_collection",
    ids=[1, 4, 100],
    output_fields=["id", "title"]
)

Elasticsearch のサンプルはこちらのページにあります。Milvusにおけるqueryとgetリクエスト、およびフィルタ式の詳細については、クエリと フィルタリングを参照してください。

プレフィックスクエリ

Elasticsearch では、以下のようにフィルタコンテキストで指定したフィールドに特定の接頭辞を含む文書を検索することができます:

resp = client.search(
    query={
        "bool": {
            "filter": {
                 "prefix": {
                    "user": {
                        "value": "ki"
                    }
                }           
            }
        }
    },
)

Milvus では、以下のように指定したプレフィックスで始まる値を持つエンティティを見つけることができます:

res = client.query(
    collection_name="my_collection",
    filter='user like "ki%"',
    output_fields=["id", "user"]
)

Elasticsearch の例はこのページにあります。Milvus のlike 演算子の詳細については、 パターンマッチングのためのLIKE 使用 を参照してください。

範囲指定クエリ

Elasticsearchでは、以下のように指定した範囲内の用語を含む文書を検索することができます:

resp = client.search(
    query={
        "bool": {
            "filter": {
                "range": {
                    "age": {
                        "gte": 10,
                        "lte": 20
                    }
                }           
            }
        }
    },
)

Milvusでは、以下のように特定のフィールドの値が指定した範囲内にあるエンティティを見つけることができます:

res = client.query(
    collection_name="my_collection",
    filter='10 <= age <= 20',
    output_fields=["id", "user", "age"]
)

Elasticsearchの例はこちらのページにあります。Milvusの比較演算子については、比較演算子を参照してください。

条件クエリ

Elasticsearch では、以下のように指定したフィールドに正確な用語を含む文書を検索することができます:

resp = client.search(
    query={
        "bool": {
            "filter": {
                "term": {
                    "status": {
                        "value": "retired"
                    }
                }            
            }
        }
    },
)

Milvusでは、以下のように指定されたフィールドの値が指定された用語と一致するエンティティを検索することができます:

# use ==
res = client.query(
    collection_name="my_collection",
    filter='status=="retired"',
    output_fields=["id", "user", "status"]
)

# use TEXT_MATCH
res = client.query(
    collection_name="my_collection",
    filter='TEXT_MATCH(status, "retired")',
    output_fields=["id", "user", "status"]
)

Elasticsearchのサンプルはこちらのページにあります。Milvusにおける比較演算子の詳細については、比較演算子を参照してください。

用語クエリ

Elasticsearchでは、以下のように指定したフィールドに1つ以上の正確な用語を含むドキュメントを検索することができます:

resp = client.search(
    query={
        "bool": {
            "filter": {
                "terms": {
                    "degree": [
                        "graduate",
                        "post-graduate"
                    ]
                }        
            }
        }
    }
)

Milvusにはこれと完全に等価なものはありません。しかし、以下のように指定したフィールドの値が指定した用語の一つであるエンティティを見つけることができます:

# use in
res = client.query(
    collection_name="my_collection",
    filter='degree in ["graduate", "post-graduate"]',
    output_fields=["id", "user", "degree"]
)

# use TEXT_MATCH
res = client.query(
    collection_name="my_collection",
    filter='TEXT_MATCH(degree, "graduate post-graduate")',
    output_fields=["id", "user", "degree"]
)

Elasticsearchの例はこちらのページにあります。Milvusの範囲演算子については範囲演算子を参照してください。

ワイルドカードクエリ

Elasticsearchでは、以下のようにワイルドカードパターンにマッチする用語を含む文書を検索することができます:

resp = client.search(
    query={
        "bool": {
            "filter": {
                "wildcard": {
                    "user": {
                        "value": "ki*y"
                    }
                }          
            }
        }
    },
)

Milvusはフィルタリング条件にワイルドカードをサポートしていません。しかし、like 演算子を使うことで以下のように同様の効果を得ることができます:

res = client.query(
    collection_name="my_collection",
    filter='user like "ki%" AND user like "%y"',
    output_fields=["id", "user"]
)

Elasticsearchのサンプルはこちらのページにあります。Milvusの範囲演算子については範囲演算子を参照してください。

ブール型クエリ

Elasticsearch において、ブーリアン・クエリとは、他のクエリのブーリアン・コンビネーションにマッチするドキュメントにマッチするクエリです。

以下の例は、このページの Elasticsearch ドキュメントにある例を引用しています。このクエリは、名前にkimchy を含むユーザーをproduction タグで返します。

resp = client.search(
    query={
        "bool": {
            "filter": {
                "term": {
                    "user": "kimchy"
                }
            },
            "filter": {
                "term": {
                    "tags": "production"
                }
            }
        }
    },
)

Milvusでは、以下のように同様のことができます:

filter = 

res = client.query(
    collection_name="my_collection",
    filter='user like "%kimchy%" AND ARRAY_CONTAINS(tags, "production")',
    output_fields=["id", "user", "age", "tags"]
)

上記の例では、ターゲットコレクションにVarChar型のuser フィールドとArray型のtags フィールドがあると仮定しています。このクエリは、production タグを持つ、名前にkimchy を持つユーザーを返します。

ベクタークエリ

Elasticsearch では、ベクタークエリとはベクターフィールドに特化したクエリで、セマンティック検索を効率的に行うことができます。

Knn クエリ

Elasticsearch は近似 kNN クエリと厳密なブルートフォース kNN クエリの両方をサポートしています。どちらの方法でも、クエリベクトルに最も近いk 個のベクトルを、類似度メトリックによって以下のように求めることができます:

resp = client.search(
    index="my-image-index",
    size=3,
    query={
        "knn": {
            "field": "image-vector",
            "query_vector": [
                -5,
                9,
                -12
            ],
            "k": 10
        }
    },
)

Milvusは特殊なベクトルデータベースとして、ベクトル検索を最適化するためにインデックスタイプを使用します。通常、高次元のベクトルデータに対しては近似最近傍(ANN)検索を優先する。FLATインデックスタイプによるブルートフォースkNN検索は正確な結果をもたらすが、時間とリソースを消費する。対照的に、AUTOINDEXや他のインデックスタイプを使用したANN検索は、スピードと精度のバランスをとり、kNNよりも大幅に高速でリソース効率に優れたパフォーマンスを提供する。

Mlivusで上記のベクトルクエリと似たような等価性は次のようになります:

res = client.search(
    collection_name="my_collection",
    anns_field="image-vector"
    data=[[-5, 9, -12]],
    limit=10
)

Elasticsearch の例はこちらのページにあります。MilvusにおけるANN検索の詳細については、Basic ANN Searchを参照してください。

相互ランクフュージョン

Elasticsearch は異なる関連性指標を持つ複数の検索結果セットを1つのランク付けされた検索結果セットに統合する Reciprocal Rank Fusion (RRF) を提供しています。

以下の例では、従来の用語ベースの検索とk-nearest neighbors (kNN) ベクトル検索を組み合わせることで、検索の関連性を向上させています:

client.search(
    index="my_index",
    size=10,
    query={
        "retriever": {
            "rrf": {
                "retrievers": [
                    {
                        "standard": {
                            "query": {
                                "term": {
                                    "text": "shoes"
                                }
                            }
                        }
                    },
                    {
                        "knn": {
                            "field": "vector",
                            "query_vector": [1.25, 2, 3.5],  # Example vector; replace with your actual query vector
                            "k": 50,
                            "num_candidates": 100
                        }
                    }
                ],
                "rank_window_size": 50,
                "rank_constant": 20
            }
        }
    }
)

この例では、RRFは2つの検索からの結果を組み合わせている:

  • text フィールドに"shoes" という用語を含む文書を検索する標準的な用語ベース検索。

  • 提供されたクエリベクトルを使った、vector フィールドに対するkNN検索。

各検索エンジンは最大50のトップマッチを提供し、それらはRRFによって再ランク付けされ、最終的にトップ10の結果が返される。

Milvusでは、複数のベクターフィールドの検索を組み合わせ、再ランク付け戦略を適用し、組み合わせたリストからトップKの結果を取得することで、同様のハイブリッド検索を実現することができます。MilvusはRRFと重み付きリランカー戦略の両方をサポートしています。詳細はリランキングを参照。

以下は上記のElasticsearchの例をMilvusで非厳格に等価化したものです。

search_params_dense = {
    "data": [[1.25, 2, 3.5]],
    "anns_field": "vector",
    "param": {
        "metric_type": "IP",
        "params": {"nprobe": 10},
    },
    "limit": 100
}

req_dense = ANNSearchRequest(**search_params_dense)

search_params_sparse = {
    "data": ["shoes"],
    "anns_field": "text_sparse",
    "param": {
        "metric_type": "BM25",
    }
}

req_sparse = ANNSearchRequest(**search_params_sparse)

res = client.hybrid_search(
    collection_name="my_collection",
    reqs=[req_dense, req_sparse],
    reranker=RRFRanker(),
    limit=10
)

この例では、Milvusにおけるハイブリッド検索を示します:

  1. 密なベクトル検索vector のフィールドで近似最近傍(ANN)検索を行うためにnprobe を 10 に設定した内積(IP)メトリックを使用する。

  2. 疎ベクトル検索text_sparse 、BM25類似度メトリックを用いる。

これらの検索結果は別々に実行され、組み合わされ、RRF(Reciprocal Rank Fusion)ランカーを使用して再ランク付けされる。ハイブリッド検索は再ランク付けされたリストから上位10エンティティを返します。

Elasticsearch の RRF ランキングが標準的なテキストベースのクエリと kNN 検索の結果を結合するのとは異なり、Milvus はスパース検索とデンスベクトル検索の結果を結合し、マルチモーダルデータに最適化された独自のハイブリッド検索機能を提供します。

まとめ

この記事では、タームレベルクエリ、ブーリアンクエリ、フルテキストクエリ、ベクトルクエリなど、典型的なElasticsearchクエリをMilvusクエリに変換する方法について説明しました。その他の Elasticsearch クエリの変換についてご質問がありましたら、お気軽にお問い合わせください。