NEST: проблема производительности эластичного поиска из-за пустого поиска - PullRequest
0 голосов
/ 17 января 2019

Использование Elastic "число": "6.3.1" "lucene_version": "7.3.1" Гнездо: 6.1.0

Попытка перевести поиск ниже. По сути, message1, message2 может иметь пустые строковые значения. если search1Value или search2Value - пустая строка, я не хочу, чтобы какие-либо записи возвращались для той части условия OR, где есть пустая строка.

Эта часть является частью большого поиска с другими критериями ... НО эта часть заставляет Query to ES быть чрезвычайно МЕДЛЕННЫМ. Я создаю поля RAW в дополнение к исходным полям при создании индекса, чтобы иметь возможность поиска по НЕ ПУСТО. Ничто другое, что я пробовал, не позволяло мне делать этот поиск правильно Есть ли другой способ сделать это? Как уже упоминалось, производительность запроса ужасна. Более 2 сек. В данном индексе около 600 тыс. Документов. Логика работает, хотя. Он возвращает правильные документы.

Заранее благодарю за любую помощь !!

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"))))
                                                    )
                                                 )
                                            )
                                        )
                                    )
                                )
                            )
               )
            );
 }

1 Ответ

0 голосов
/ 18 января 2019

Отображение и запрос выглядят некорректно для желаемого результата. Давайте разберемся с этим

Я создаю поля RAW в дополнение к исходным полям при создании индекса, чтобы иметь возможность поиска по НЕ ПУСТО. Ничто другое, что я пробовал, не позволяло мне делать этот поиск правильно Есть ли другой способ сделать это?

Отображение

В качестве примера, у вас есть отображение

.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. Здесь, я думаю, вы хотите .IgnoreAbove(0), если вы хотите использовать это мультиполе, чтобы иметь возможность искать документы с пустой строкой для Message1. Я хотел бы, однако, спросить, действительно ли полезно иметь возможность поиска по документам с пустыми Message1; вы сможете определить документы, которые имеют значение (даже пустую строку), с помощью запроса exists, и если вы действительно хотите искать документы с пустыми сообщениями, вы можете сделать это с помощью запроса сценария.

В конечном счете, я думаю, что если поиск пустых сообщений является обычным явлением, то использование этого многопольного поля "keyword" было бы полезно; Я был бы склонен назвать это "empty", хотя вместо этого, чтобы лучше соответствовать намерению.

Поисковый запрос

.Index("MessageTest")

Имя индекса должно быть в нижнем регистре, чтобы быть действительным.

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

Внешнее предложение bool query 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"))))
        )
     )
)

Запрос term в предложении must_not выглядит для меня излишним, поскольку ввод пустой строки для запроса 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 ввод запроса с нулевой или пустой строкой делает запрос безусловным . Использование .Verbatim() отменяет это безусловное поведение, заставляя 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)
                )
            )
        )
    )
);

, что можно еще больше упростить с помощью перегрузки оператора на запросы до

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)
        )
    )
);

, который возвращает только документ с идентификатором 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; } = "";
}
...