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; } = "";
}
使用弹性 "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; } = "";
}