NEST:Elastic Search 性能问题,因为空搜索

NEST: Elastic Search Performance Problem Because Of Empty Search

使用弹性 "number":“6.3.1” "lucene_version": "7.3.1" 巢:6.1.0

正在尝试翻译以下搜索。本质上,message1、message2 可以有空字符串值。如果 search1Value 或 search2Value 是空字符串,我不希望为空字符串的 OR 条件部分编辑 return 任何记录。

这篇文章是使用其他标准进行的非常大的搜索的一部分...但是这篇文章导致对 ES 的查询非常缓慢。在创建索引时,除了原始字段之外,我还创建了 RAW 字段,以便能够在 NOT EMPTY 上进行搜索。我试过的其他任何东西都不允许我正确地进行搜索。有没有不同的方法来做到这一点?如前所述,查询的性能很糟糕。超过 2 秒。有问题的索引有大约 60 万个文档。虽然逻辑确实有效。它 return 正确的文档。

提前感谢您的帮助!!

message1 != "" and message1.StartsWith(search1Value)
OR 
message2 != "" and message2.StartsWith(search2Value)

所以如果索引中的可用文档示例...

id, message1, message2
1, "", "abc"
2, "", ""
3, "def", ""
4, "", "ghi"

如果searchValue1是空串,searchValue2是abc我想取回,只记录1,不记录1、2、4

为了在此条件下正确搜索,索引设置如下:

public class MessageSearch() {
       public string Message1 {get; set;}
       public string Message2 {get; set;}
    }

public class MessageModelIndex() {
   public string Message1 {get; set;} = ""
   public string Message2 {get; set;} = ""
}


public override CreateIndexDescriptor DefineIndex(string indexName)
        {
            return new CreateIndexDescriptor(indexName).Settings(s => s
                .NumberOfShards(2)                    
                .Mappings(ms => ms
                    .Map<MessageModelIndex>(m => m
                        .Properties(p => p                                
                            .Text(s => s
                                .Name(x => x.Message1)
                                .Fields(ff => ff
                                    .Text(tt => tt
                                        .Name("raw")
                                    )
                                    .Keyword(k => k
                                        .Name("keyword")
                                        .IgnoreAbove(1)
                                    )
                                )
                            )
                            .Text(s => s
                                .Name(x => x.Message2)
                                .Fields(ff => ff
                                    .Text(tt => tt
                                        .Name("raw")
                                    )
                                    .Keyword(k => k
                                        .Name("keyword")
                                        .IgnoreAbove(1)
                                    )
                                )
                            )
                        )
                    ));
        }

以下搜索用于获取这些值:

public void PerformSearch(MessageSearch search) {
                var result = _client.Search<MessageModelIndex>(x => x
               .Index("MessageTest")
               .Size(1000)
               .Query(q => q
                        .Bool(b => b
                                .Must(bm => bm
                                    .Bool(bb => bb
                                        .Should(bbs => bbs
                                            .Bool(bbb => bbb
                                                .Must(mm => mm
                                                    .Bool(bbbb => bbbb
                                                        .MustNot(bbbmn => bbbmn.Term(t => t.Verbatim().Field(f => f.Message1.Suffix("keyword")).Value(string.Empty)))
                                                    ),
                                                    mm => mm
                                                    .Bool(bbbb => bbbb
                                                        .Must(bbbmn => bbbmn.MatchPhrasePrefix(mmp => mmp.Query(search.Message1.Trim()).Field(f => f.Message1.Suffix("raw"))))
                                                    )
                                                 )
                                            ), bbs => bbs
                                            .Bool(bbb => bbb
                                                .Must(mm => mm
                                                    .Bool(bbbb => bbbb
                                                        .MustNot(bbbmn => bbbmn.Term(t => t.Verbatim().Field(f => f.Message2.Suffix("keyword")).Value(string.Empty)))
                                                    ),
                                                    mm => mm
                                                    .Bool(bbbb => bbbb
                                                        .Must(bbbmn => bbbmn.MatchPhrasePrefix(mmp => mmp.Query(search.Message2.Trim()).Field(f => f.Message2.Suffix("raw"))))
                                                    )
                                                 )
                                            )
                                        )
                                    )
                                )
                            )
               )
            );
 }

对于您想要的结果,映射和查询看起来不正确。让我们分解一下

I create RAW fields in addition to the original fields when creating the index just to be able to search on NOT EMPTY. Nothing else i had tried allowed me to do that search correctly. IS there a different way to do this?

映射

例如,您拥有的映射

.Text(s => s
    .Name(x => x.Message1)
    .Fields(ff => ff
        .Text(tt => tt
            .Name("raw")
        )
        .Keyword(k => k
            .Name("keyword")
            .IgnoreAbove(1)
        )
    )
)

"raw" 字段是多余的,因为它与包含的 text 数据类型映射相同。

"keyword" 多字段将为 Message1 索引单个或更少的字符串。在这里,如果打算使用此多字段能够搜索 Message1 为空字符串的文档,我认为您需要 .IgnoreAbove(0)。但是,我会质疑能够搜索空 Message1 的文档是否真的有价值;您可以使用 exists 查询来确定具有值(甚至是空字符串)的文档,如果您确实想搜索包含空消息的文档,则可以使用脚本查询来实现。

最终,我想如果能够搜索空消息很常见,那么拥有这个 "keyword" 多字段会很有用;不过,我倾向于将其命名为 "empty",以更好地匹配意图。

搜索请求

.Index("MessageTest")

索引名称必须小写才有效。

.Bool(b => b
        .Must(bm => bm
            .Bool(bb => bb
                .Should(bbs => bbs

不需要外部bool查询must子句; should 子句可以移出并在外部 bool 查询中定义。

.Bool(bbb => bbb
    .Must(mm => mm
        .Bool(bbbb => bbbb
            .MustNot(bbbmn => bbbmn.Term(t => t.Verbatim().Field(f => f.Message1.Suffix("keyword")).Value(string.Empty)))
        ),
        mm => mm
        .Bool(bbbb => bbbb
            .Must(bbbmn => bbbmn.MatchPhrasePrefix(mmp => mmp.Query(search.Message1.Trim()).Field(f => f.Message1.Suffix("raw"))))
        )
     )
)

must_not 子句中的 term 查询在我看来是多余的,因为 match_phrase_prefix 查询的空字符串输入不会匹配任何文档。如果您索引了以下文档,您可以自己看到这一点

var bulkResponse = client.Bulk(b => b
    .IndexMany(new [] 
    {
        new MessageModelIndex { Id = 1, Message1 = "", Message2 = "abc" },
        new MessageModelIndex { Id = 2, Message1 = "", Message2 = "" },
        new MessageModelIndex { Id = 3, Message1 = "def", Message2 = "" },
        new MessageModelIndex { Id = 4, Message1 = "", Message2 = "ghi" },
    })
    .Refresh(Refresh.WaitFor)
);

然后运行搜索

var emptyStringInputResponse = client.Search<MessageModelIndex>(x => x
    .Index(defaultIndex)
    .Query(q => q
        .MatchPhrasePrefix(t => t
            .Verbatim()
            .Field(f => f.Message1)
            .Query("")
        )
    )
);

没有返回任何文件。这是因为在索引时对 Message1 字段进行分析,并在查询时针对该字段进行输入。

还要注意这里需要使用.Verbatim(),因为NEST有一个概念叫做无条件查询:如果一个查询被确定为无条件的,那么它就是未包含在序列化请求中 JSON。对于 MatchPhrasePrefix 查询,null 或空字符串查询输入使查询 无条件 。使用 .Verbatim() 覆盖此 conditionless 行为,强制 NEST 按原样序列化查询。

查询可以简化为

var searchResponse = client.Search<MessageModelIndex>(x => x
    .Index(defaultIndex)
    .Size(1000)
    .Query(q => q
        .Bool(bb => bb
            .Should(bbs => bbs
                .MatchPhrasePrefix(mmp => mmp
                    .Query(search.Message1.Trim())
                    .Field(f => f.Message1)
                ), bbs => bbs
                .MatchPhrasePrefix(mmp => mmp
                    .Query(search.Message2.Trim())
                    .Field(f => f.Message2)
                )
            )
        )
    )
);

可以进一步简化为operator overloading on queries

var searchResponse = client.Search<MessageModelIndex>(x => x
    .Index(defaultIndex)
    .Size(1000)
    .Query(q => q
        .MatchPhrasePrefix(mmp => mmp
                .Query(search.Message1.Trim())
                .Field(f => f.Message1)
            ) || q
        .MatchPhrasePrefix(mmp => mmp
            .Query(search.Message2.Trim())
            .Field(f => f.Message2)
        )
    )
);

其中 returns 只有 id 为 1 的文档用于 searchValue1 "" 和 searchValue2 "abc".

这是一个完整的例子

private static void Main()
{
    var defaultIndex = "message-test";
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

    var settings = new ConnectionSettings(pool)
        .DefaultIndex(defaultIndex);

    var client = new ElasticClient(settings);

    if (client.IndexExists(defaultIndex).Exists)
        client.DeleteIndex(defaultIndex);

    client.CreateIndex(defaultIndex, c => c
        .Mappings(m => m
            .Map<MessageModelIndex>(mm => mm
               .Properties(p => p
                    .Text(s => s
                        .Name(x => x.Message1)
                        .Fields(ff => ff
                            .Keyword(k => k
                                .Name("keyword")
                                .IgnoreAbove(0)
                            )
                        )
                    )
                    .Text(s => s
                        .Name(x => x.Message2)
                        .Fields(ff => ff
                            .Keyword(k => k
                                .Name("keyword")
                                .IgnoreAbove(0)
                            )
                        )
                    )
                )
            )
        )
    );

    var bulkResponse = client.Bulk(b => b
        .IndexMany(new [] 
        {
            new MessageModelIndex { Id = 1, Message1 = "", Message2 = "abc" },
            new MessageModelIndex { Id = 2, Message1 = "", Message2 = "" },
            new MessageModelIndex { Id = 3, Message1 = "def", Message2 = "" },
            new MessageModelIndex { Id = 4, Message1 = "", Message2 = "ghi" },
        })
        .Refresh(Refresh.WaitFor)
    );

    var search = new MessageSearch
    {
        Message1 = "",
        Message2 = "abc"
    };

    var searchResponse = client.Search<MessageModelIndex>(x => x
        .Index(defaultIndex)
        .Size(1000)
        .Query(q => q
            .MatchPhrasePrefix(mmp => mmp
                    .Query(search.Message1.Trim())
                    .Field(f => f.Message1)
                ) || q
            .MatchPhrasePrefix(mmp => mmp
                .Query(search.Message2.Trim())
                .Field(f => f.Message2)
            )
        )
    );
}

public class MessageSearch 
{
    public string Message1 { get; set; }
    public string Message2 { get; set; }
}

public class MessageModelIndex 
{
   public int Id { get; set; }
   public string Message1 { get; set; } = "";
   public string Message2 { get; set; } = "";
}