手把手教你如何把代码库从 ES 迁到 Milvus

2025-03-20

By Zilliz

手把手教你如何把代码库从 ES 迁到 Milvus

作为过去十年中最具影响力的开源搜索引擎之一,Elasticsearch 以其高性能、高扩展性和分布式架构而在搜索、分析中广受欢迎,常常被用于如电商平台的商品搜索、日志分析、外卖平台餐厅推荐、金融交易行为识别等等场景之中。

然而,ES虽好,在生产环境中却常常面临以下挑战:

1.数据更新与索引代价高:Elasticsearch 在处理写操作时开销较大,尤其是在大批量数据更新场景中。其写入、索引构建和查询未能完全解耦,导致写操作会显著消耗 CPU 和 I/O 资源,影响查询性能。

2.数据实时性不足:Elasticsearch 是“近实时”搜索引擎,数据可见性存在一定延迟,难以满足Agent等高频交互或动态决策的需求。

3.分片维护困难:Elasticsearch 的分片机制在小数据量场景下可能导致性能不足,而在大数据量场景下则限制扩展性,容易出现数据分布不均衡。

4.架构非云原生:Elasticsearch 的存储与计算紧密耦合,缺乏与公有云和 Kubernetes 的深度整合,扩展资源时灵活性较差。

5.向量检索性能低:尽管 Elasticsearch 8.0 引入了向量 ANN 检索功能,但仍难以满足大规模向量检索需求。

6.资源消耗过高:Elasticsearch 对内存和 CPU 的需求极高,依赖 JVM,需要频繁调整堆内存大小和垃圾回收策略,降低了内存使用效率。

因此,在混合检索场景中,越来越多的开发者,开始从ES转向以Milvus 为代表的向量数据库。相比ES,Milvus 在关键词搜索方面具有显著优势:

1.算法灵活性: Milvus 可以将相似度计算转化为向量距离计算,进而支持更复杂的查询和语料库距离分析。

2.成本优势:Milvus 通过稀疏向量实现词法搜索,支持倒排索引压缩和密集嵌入的有损压缩,通过对长尾词进行剪枝和向量量化,性能提升超过 3 倍,并在召回率下降不到 1% 的前提下内存占用减少 50%以上。

因此,如何将代码库从 Elasticsearch 迁移至 Milvus,已成为众多开发者密切关注的焦点,并且频繁出现在社区的讨论中。

基于这一背景,本文将为你带来从 Elasticsearch 迁移至 Milvus的手把手教程,同时提供多种查询转换的例子。

01

概述

在 Elasticsearch 中,查询上下文(query context)中的操作会生成相关性分数,而过滤上下文(filter context)中的操作则不会。同样地,Milvus 的搜索会产生相似度分数,而其类似过滤的查询则不会。

当把代码库从 Elasticsearch 迁移到 Milvus 时,关键是将 Elasticsearch 查询上下文时使用的字段转换为 Milvus 中的向量字段,用于生成相似性得分。

以下表格总结了 Elasticsearch 查询模式及其在 Milvus 中的对应:

Elasticsearch 查询语句Milvus 对应实现说明
Full-text queries
Match query全文检索两者提供相同或相似的功能。
Non-scoring match query文本匹配Milvus 提供了一个不参与相似性得分的文本匹配能力。该能力与 Elasticsearch 的 match 子句能力相当。注意,Elasticsearch 中所有的 match 子句均会产生相似性得分。因此,Elasticsearch 并不存在 Milvus 中不输出相似性得分仅进行文本匹配的能力。
Term-level queries
IDsin operator当在 Elasticsearch 的过滤器上下文中使用左侧相关子句时,与 Milvus 提供提供相同或相似的能力。
Prefix querylike operator
Range queryComparison operators like >, <, >=, and <=
Term queryComparison operators like ==
Terms queryin operator
Wildcard querylike operator
Boolean queryLogical operators like AND当在 Elasticsearch 的过滤器上下文中使用左侧相关子句时,与 Milvus 提供提供相同或相似的能力。
Vector queries
kNN querySearchMilvus 提供更加高级的向量检索能力。
Reciprocal rank fusionHybrid SearchMilvus 提供更多结果重排策略。

02

全文查询(Full-text queries)

在 Elasticsearch 中,全文查询功能允许我们搜索那些经过分词处理的文本字段。例如,一个邮件中,其字段的内容可能在索引时被拆分成多个单词或短语,这些拆分后的单元会被存储在倒排索引中,方便后续的搜索操作。当执行全文查询时,输入的查询字符串会使用与该字段在索引阶段相同的分词器进行处理。这样,查询字符串会被拆分成与索引时相同的单元,从而确保查询能够准确地匹配到经过分词的文本内容。这种机制使得全文查询能够高效地处理复杂的文本数据,找到与用户输入最相关的文档。

Match 查询

在 Elasticsearch 中,Match 查询会返回与提供的文本、数字、日期或布尔值匹配的文档。提供的文本在匹配之前会经过分析处理。

以下是一个使用 Match 查询的 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_sparse 是一个从名称为 message 的 VarChar 字段,派生出的稀疏向量字段。Milvus 使用 BM25 embedding 模型将 message 字段中的值,转换为稀疏向量 embedding,并将其存储在 message_sparse 字段中。当接收到检索请求时,Milvus 使用相同的 BM25 模型,对纯文本查询进行 embedding,并执行稀疏向量检索,同时返回 output_fields 参数中指定的 id 和 message 字段,以及相应的相似度分数。

要使用此功能,必须在 message 字段上启用分析器(analyzer),并定义一个函数,从中派生出 message_sparse 字段。

在Milvus 中等效实现非评分 Match 查询

Elasticsearch 不支持非评分的 Match 查询,所有的 Match 查询都会生成相关性得分。然而,在 Milvus 中,您可以通过结合使用 TEXT_MATCH 操作符,来实现精确的关键词匹配过滤,从而增强向量检索的效果。这种方法可以确保检索结果包含特定词项,从而提高召回率。

# Filter entities whose message value contains the exact terms `Jamaica`filter = "TEXT_MATCH(message, 'Jamaica')"
# Assuming 'message_vector' is the vector field and 'message' is the VARCHAR fieldresult = MilvusClient.search(    collection_name="my_collection",     anns_field="message_vector",     data=[[1, -2.5, 3]], # vector embeddings of the phrase `How is the weather in Jamaica?`     filter=filter,    search_params={"params": {"nprobe": 10}},    limit=10,     output_fields=["id", "message"] )

和在 Match 查询中使用 Milvus 生成稀疏向量 embedding 到派生字段不同,上述案例将 message 字段中的值转换为稠密向量,并使用 TEXT_MATCH 过滤器,来确保检索结果与“Jamaica”密切相关。具体来说,Milvus 首先执行文本匹配,分析和过滤 message 字段中包含“Jamaica”一词的 Entity,然后在该过滤后的子集中执行向量检索,以提高检索的召回率。

03

词项级查询(Term-level queries)

在 Elasticsearch 中,词项级查询(Term-Level Queries)是根据结构化数据中的精确值查找文档,例如日期范围、IP 地址、价格或产品 ID。本节将介绍在 Milvus 中实现一些 Elasticsearch 词项级查询的等价方法。本节中的所有示例都经过调整,以适应过滤器上下文的操作,以便与 Milvus 的功能保持一致。

IDs

在 Elasticsearch 中,可以在过滤上下文中,基于文档 ID 查找文档,如下所示:

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

在 Milvus 中,您也可以根据 Entity 的 ID 进行查找,具体方法如下:

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

前缀查询(Prefix Query)

在 Elasticsearch 中,您可以在过滤上下文中,查找包含特定前缀的指定字段的文档,方法如下:

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

在 Milvus 中,您可以查找以指定前缀开头的值的 Entity,方法如下:

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

范围查询(Range query)

在 Elasticsearch 中,您可以在指定范围内,查找包含指定词项的文档,方法如下:

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

在 Milvus 中,您可以在指定范围内,查找有特定字段的值的 Entity,方法如下:

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

词项查询(Term query)

在 Elasticsearch 中,您可以在指定字段中,查找含有一个精确术语的文档,方法如下:

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

在 Milvus 中,您可以查找指定字段的值完全匹配指定词项的 Entity,方法如下:

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

多词项查询(Terms query)

在 Elasticsearch 中,您可以在指定字段中,查找一个或多个精确词项的文档,方法如下:

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

Milvus 并没有完全等效的功能。不过,您可以查找在指定字段里的值,属于一个指定词项的 Entity,方法如下:

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

通配符查询(Wildcard query)

在 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"])

布尔查询(Boolean query)

在 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 字段。该查询将返回一个名称中包含 kimchy 且带有 production 标签的回复。

04

向量查询(Vector queries)

在 Elasticsearch 中,向量查询是一种专门用于向量字段的查询,可以高效执行语义检索。

kNN 查询

Elasticsearch 支持近似 kNN 查询和精确的暴力 kNN 查询。您可以根据相似度度量,来找到最近邻的向量 kk,方法如下:

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 更快的性能,且更节省资源。

在 Milvus 中,与上述向量查询类似的操作如下:

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

倒数排名融合(Reciprocal Rank Fusion)

Elasticsearch 提供了倒数排名融合(Reciprocal Rank Fusion, RRF)功能,可以将具有不同相关性指标的多个结果集,合并为一个排序结果集。

以下例子展示了如何将传统的基于词项的检索,与 k 近邻(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 结合了两个检索器的结果:

  • 一个标准的基于词项的检索,用于查找在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",        "params": {"drop_ratio_search": 0.2}    },    "limit": 100}
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.稠密向量检索(Dense vector search):在vector字段上使用内积(IP)度量,并将 nprobe 设置为 10,以执行近似最近邻(ANN)检索。

2.稀疏向量检索(Sparse vector search):在 text_sparse 字段上使用 BM25 相似度度量,并将 drop_ratio_search 参数设置为 0.2。

这些检索的结果被分别执行后,通过倒数排序融合(RRF)排序器进行组合和重排。混合检索返回重排列表中的前 10 个 Entity。

与 Elasticsearch 的 RRF 排序(基于标准文本查询和 kNN 检索的结合)不同,Milvus 结合了稀疏向量和稠密向量检索,提供了一种专门优化多模态数据的独特混合检索方法。

      准备好开始了吗?

      立刻创建 Zilliz Cloud 集群,存储和检索您的向量。

      免费试用 Zilliz Cloud