İçeriğe geç →

Elasticsearch Serisi 04 – ASP.NET Core’da Completion Suggester ile Autocomplete API Tasarlamak

Özellikle AmazonNetflix, eBay gibi commercial siteler başta olmak üzere bir çok popüler websitelerine baktığımızda, autocomplete(search suggestion) kutularına büyük bir önem verildiğini açıkça görebiliriz sanırım.

Biliyoruz ki iyi bir arama sonucu, son kullanıcı için oldukça büyük bir önem taşımaktadır. Commercial siteler açısından ise son kullanıcıyı hızlı bir şekilde doğru bir ürüne veya kategoriye yönlendirebilmek, satış oranlarını pozitif bir şekilde etkileyecektir.

Henüz biz daha bir şeyler yazarken ilgili sonuçların bize gösterilmesi, çok harika değil mi?

Bende son dönemlerde farklı kişilerden almış olduğum e-postalar doğrultusunda, autosuggest özelliğini Elasticsearch – Completion Suggester ve .NET Core kullanarak nasıl gerçekleştirebiliriz konusu hakkında bir şeyler yazmaya karar verdim.

Elasticsearch içerisinde autocomplete/suggest özelliğini implemente edebilmenin “ngrams”, “prefix queries” ve “completion suggester” gibi bir kaç farklı yolu vardır. Farklı implementasyon yollarının ise, resulting ve indexing hızları, document size’ları gibi farklı tradeoff’ları da bulunmaktadır. Ben bu makale kapsamında “autocomplete/search-as-you-type” fonksiyonalitesi için, en performanslı çözüm olan(bence) “completion suggester” özelliğini kullanarak autocomplete’i nasıl implemente edebiliriz konusuna değinmeye çalışacağım.

Completion Suggester

Sanırım söz konusu son kullanıcıya instant feedback verebilmek olduğunda, iyi bir suggestion sonucu ile birlikte resulting hızı da büyük bir önem taşımaktadır. Bu noktada, completion suggester autocomplete konusunda diğer alternatif yollardan farklı olarak çalışmaktadır. Suggest edilmek istenen tüm kombinasyonlar, “completion” type’ına sahip bir mapping ile elasticsearch üzerine indexlenmesi gerekmektedir. Completion suggester’ın fast lookup işlemini gerçekleştirebilmesi için ise, FST(Finite-state transducer) adı verilen in-memory data structure’ı kullanmaktadır. Bu sayede prefix lookup işlemini diğer term-based query’lere göre daha hızlı bir şekilde gerçekleştirebilmektedir.

Çalışma mantığını daha iyi anlayabilmemiz için elastic engineering blog’u üzerinde bulunan örneğe bir bakalım. FST üzerinde “hotel“, “marriot“, “mercure“, “munchen” ve “munich” kelimelerinin olduğunu varsayalım.

Suggester, yukarıdaki in-memory graph üzerinde kullanıcı tarafından girilen text input’a göre soldan sağa doğru bir matching işlemi gerçekleştirmektedir. Örneğin kullanıcı input olarak “h” text’ini girdiğinde, match olacak tek olasılık “hotel” olduğu için bu kelimeyi anında tamamlamaktadır. Eğer kullanıcı “m” text’ini girerse, bu sefer suggester “m” ile başlayan tüm kelimeleri listeleyecektir.

Completion suggester’ın dezavantajı ise, yukarıda olduğu gibi matching işlemi her zaman soldan sağa doğru başlamaktadır. Örneğin “Sam” text input’u “Samsung Note 8” ile match olacaktır “Note 8 Samsung” ile değil. Bu tarz durumlarda ise term-based query’ler daha fazla ön plana çıkmaktadır. Ancak biraz öncede bahsettiğim gibi, suggest edilmek istenen tüm kombinasyonları completion suggester ile tek bir suggest output’u için index’lersek, “Note 8 Samsung” kelimesinin match işlemini de gerçekleştirebiliriz. Buna örneğimizde değineceğim.

Ayrıca completion suggester ile birlikte “Fuzzy Matching” ve scoring işlemleri için “Weights” belirtebilmek de mümkündür.

Senaryo

Bir e-ticaret firmasında çalıştığımızı düşünelim. Search domain’inden sorumlu product owner ise bizden, “brand” ve “product name” field’larına göre real-time‘a yakın bir sonuç veren autocomplete yapmamızı istiyor.

Bunun için ilk olarak suggest edeceğimiz item’ları, elasticsearch üzerine feed yapmamız gerekmektedir.

1) Mapping ve Index’in Oluşturulması

Completion suggester’ı kullanabilmemiz için, öncelikle completion type’ına sahip bir mapping oluşturmamız gerekmektedir. Bunun için öncelikle “Autocomplete.Business.Objects” isminde bir .NET Core class library oluşturalım ve NuGet package manager üzerinden “NEST” library’sini dahil edelim.

Ayrı bir class library yapmamızın sebebi ise, burada tanımlayacak olduğumuz modelleri hem feeder uygulamasında hem de autocomplete API‘ında kullanacak olmamızdır.

İlk olarak “Product” ve “ProductSuggestResponse” model’lerini aşağıdaki gibi tanımlayalım.

Product” model’i içerisindeki “Suggest” property’sini, autocomplete işlemi sırasında suggest etmek istediğimiz text’ler için kullanacağız.

Autocomplete.Business” isminde yeni bir .NET Core class library daha oluşturalım ve “Autocomplete.Business.Objects” ile “NEST” library’sini burayada dahil edelim. Ardından “IAutocompleteService” isminde bir interface tanımlayalım.

ve aşağıdaki gibi implemente edelim.

Yukarıdaki “CreateIndexAsync” method’una bakarsak, burada “Product” model’inin mapping işlemini gerçekleştiriyoruz. Completion alanı olarak “Product” model’inin içerisindeki “Suggest” property’sini belirtiyoruz. Bu noktada analyzer olarak ise default “simple” analyzer kullanılmaktadır. Simple analyzer lower case olarak tüm text’i, terms’lere bölmektedir. Farklı ihtiyaçlar karşısında ise analyzer’ı, completion üzerindeki “Analyzer” method’u ile değiştirebilmek mümkündür.

SuggestAsync” method’unu ise autocomplete işlemi sırasında kullanacağız. Burada basit olarak “Suggest” field’ı üzerinden completion işlemi gerçekleştireceğimizi belirtiyoruz. Kullanıcının gireceği keyword’u ise, “Prefix” method’u ile completion’a set ediyoruz. “Fuzzy” ise autocomplete işlemi sırasında, olmazsa olmazlardandır sanırım. Sonuçta hepimiz bir şeyler yazarken basit typo hataları yapabiliyoruz, değil mi? 🙂

Son olarak “searchResponse” üzerinden gelen suggestion option’larını ise, suggest edilen “Text” ve “Score” bilgileri ile “ProductSuggest” model’ine map’liyoruz.

NOT: Suggest edilen text’in orjinal document’ına, “option” ın “Source” property’si üzerinden erişebilmek mümkündür.

Artık suggestion için kullanacak olduğumuz document’ları, feed edecek olan console application’ı oluşturmaya başlayabiliriz. Bunun için öncelikle “Autocomplete.Feed” isminde bir .NET Core console application projesi oluşturalım ve “Autocomplete.Business.Objects” ile “Autocomplete.Business” library’lerini referans olarak ekleyelim.

Projenin oluşturulmasının ardından, test işlemlerimiz için aşağıdaki komut ile Docker üzerinde bir elasticsearch instance’ı ayağa kaldıralım.

Şimdi “Program.cs” class’ının içeriğini aşağıdaki gibi güncelleyelim.

Yukarıdaki kod bloğuna bakarsak, öncelikle autocomplete’de suggestion işlemi için kullanacak olduğumuz “Product” ları oluşturduk. “Product” oluşturma sırasında ise “CompletionField” property’sine, her bir product için match olmasını istediğimiz input’ları set ettik. Yani kullanıcı “Galaxy Note 8” de yazsa, yada sadece “Note 8” de yazsa bu text’in “Samsung Galaxy Note 8” e match olmasını sağlayabileceğiz.

Daha önce oluşturmuş olduğumuz “AutocompleteService” class’ı ile de, index’in oluşturulabilmesini ve product’ların feed edilebilmesini sağladık.

Şimdi feed projesi hazır olduğuna göre, çalıştıralım ve “product_suggest” index’inin oluşturulmasını sağlayalım. Eğer başarılı bir şekilde çalıştırıldı ise, elasticsearch üzerinde aşağıdaki gibi bir mapping’e sahip “product_suggest” index’i oluşturulmuş olacaktır.

GET product_suggest/_mapping

1) Autocomplete API’ın Tasarlanması

Artık tek yapmamız gereken, autocomplete’i dış dünyaya sunabilmek için bir API tasarlamak. Bunun için “Autocomplete.API” isminde bir .NET Core Web API projesi oluşturalım ve “Autocomplete.Business.Objects“, “Autocomplete.Business” ve “NEST” library’lerini referans olarak dahil edelim.

Ardından “ProductSuggests” isminde bir controller ekleyelim ve aşağıdaki gibi kodlayalım.

Get” method’unda basit olarak, “IAutocompleteService” class’ını kullanarak “ProductSuggestResponse” unu dönüyoruz

Son olarak “Startup” class’ı içerisinde servislerin injection işlemini gerçekleştirmemiz gerekmektedir.

Hepsi bu kadar.

Autocomplete özelliğini test edebilmemiz için öncelikle API projesini çalıştıralım ve ardından kullanıcının “iph” text’ini girdiğini varsayalım.

GEThttp://localhost:5000/api/product-suggests?keyword=iph

Gelen response’a bakarsak eğer “iph” text’i için “Iphone 8“, “Iphone X” ve “iPad Pro” gibi ilgili sonuçların geldiğini görebiliriz.

Şimdi ise kullanıcının “iph” yerine “app” text’ini girdiğini düşünürsek:

Bu sefer de “Apple Iphone 8“, “Apple Iphone X” ve “Apple iPad Pro” sonuçları kullanıcıya suggest edilmiştir.

Sonuç

Makalenin girişinde de bahsettiğim gibi, bu autocomplete özelliğini implemente edebilmenin elasticsearch içerisinde tradeoff’ları ile beraber bir kaç farklı yolu bulunmaktadır. Completion suggester yapısı gereği diğer term-based query’lere göre daha performanslı olarak çalışmaktadır. Matching işlemine text’in başlangıcından başlaması ise bir dezavantajıdır. Bunun yanında ayrıca sort order opsiyonları ise kısıtlıdır.

https://github.com/GokGokalp/Elasticsearch-Autocomplete-API-Sample

Referanslar

https://www.elastic.co/blog/you-complete-me

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html

Bu makale toplam (3020) kez okunmuştur.

23
0



Kategori: ASP.NET Core Search Engine

4 Yorum

  1. Kirill Kirill

    Thanks for the great article! You are describing a lot. I only wanted to ask what if we are using some sort of db with a big amount of info in it. How would it be right to create suggesters – I see you inputing them by yourself

    • Hi Kirill, thanks for your interest. Could you please give me a little more information about your question? What you mean with “I see you inputting them by yourself”? Is it about indexing data to the elassticssearch from the db or something else?

      • Kirill Kirill

        Well, you have your own examples in your code. I’ve changed your solution a bit and now I have an opportunity to post some kind of news with fields like Name, Tags, Short Description. I’ve wanted to ask if you know how to connect ES with MS SQL for example(I’ve read smth about logstash). And is it possible to suggest not only Name but Description too? Thanks for your time

        • Hi Kirill, I’m sorry for late reply, these days I a little bit busy with marriage preparing. 🙂 So, yes you can set multiple inputs to one suggestion item. E.g : Input = new[] { “Name”, “Tags”, “Description” } If these fields match any search term, you can change will be displayed text what you want. Is the same thing with my example. Thanks.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.