Elasticsearch Autocomplete

Uzun zamandır teknik bir yazı yazmıyordum. Şimdi bu yazımda sizlere elasticsearch üzerinde index oluşturma, mapping oluşturma ve kayıt ekleme ve bu kayıtlar arasında autocomplete nasıl yapılır, nasıl çalışır bunları anlatacağım.

Baştan söyleyeyim, bu yazımda sıfırdan elasticsearch nedir falan anlatmayacağım. Bu yazım sadece geliştiriciler içindir.

Şimdi index isimlerini baştan belirliyorum. index_ismi: cities, mapping_tipi: citymap olacak şekilde ayarladım.

Autocomplete işlemini yapmadan önce aşağıdaki işlemleri sırasıyla gerçekleştirmemiz gerekiyor:

  1. Index oluşturulur
  2. Mapping oluşturulur
  3. Completion için indexing yapılır
  4. Query zamanı!!!

Normalde bu işlemleri yapmak için CURL PUT veya CURL POST kullanıyoruz fakat ben genelde servisleri test etmek için Postman kullanıyorum. Size de tavsiye ederim.

Ayrıca elasticsearch localhost:9200'de çalışmaktadır. Bu portu ve adresi tabi değiştirebilirsiniz. Belki bir sunucu üzerinde çalışmak istersiniz.

Sırasıyla başlayalım

Index Oluşturma

Aşağıdaki kodu PUT,yapıyoruz:

PUT: localhost:9200/{index_ismi}

localhost:9200/cities

{
"settings" : {
"index" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
}
}

settings scope içerisinde index’in alacağı ayarları belirledik. Yani ayarlar>index için gibi bir ibare kullandık. Shard ve replica değerlerini default olanlarda bıraktım. (Shard değeri en az 4 olabilir). Tabi bunları yüke göre, yapmak istediğiniz işleme göre sizler belirliyorsunuz.

İşlem bittikten sonra alacağınız çıktı şöyle olacaktır:

{
"acknowledged": true
}

Index başarıyla oluşturuldu. Şimdi sıra mapping oluşturmada.

Mapping Oluşturma

Elasticsearch’ün hangi alanda hangi veri tipinin saklandığını bilmesi gerekiyor. Bunun için de mapping oluşturmamız lazım. Ancak unutmayın, index oluşturmadan mapping oluşturamazsınız. Mapping ve index’i tek seferde oluşturan komutlar var fakat bu şekilde yaparsanız sizin için okuması ve anlaması daha kolay olacaktır.

PUT: localhost:9200/{index_ismi}/_mapping/{mapping_ismi}

localhost:9200/cities/_mapping/citymap

{
"properties" : {
"id" : { "type" : "string", "index" : "not_analyzed" },
"cityName" : { "type" : "string", "index" : "not_analyzed" },
"cityIsoCode" : { "type" : "string", "index" : "not_analyzed" },
"countryAlpha2Code" : { "type" : "string", "index" : "not_analyzed" },
"suggest" : { "type" : "completion", "analyzer" : "simple", "search_analyzer" : "simple", "payloads" : true }
}
}

Burada mapping içerisine properties atıyoruz. id, isim, iso kodu gibi. Burada atadığımız property’nin tipini belirtiyoruz ve index alırken analiz edilip edilmeyeceğini söylüyoruz. Yani not_analyzed “olduğu gibi getir” anlamına geliyor. Bu işin biraz daha derin kısmı, konu dışına çıkmayalım.

Ayrıca “suggest” isminde bir property daha gözünüze çarpmıştır alt tarafta. Öneriyi bunun üzerinden yapacağız. Dikkat ettiyseniz tipi “completion”. Ayrıca analyzer, search_analyzer gibi parametreleri de belirtiyoruz. Eğer bir payload durumu varsa, “payloads” parametresini “true” olarak yazıyoruz. Mapping üzerine indexing yaparken payload eklemek için bu bölümün “true” olması gerekmekte. Birazdan indexing yaparken payload örneği de vereceğim. Payload üzerinde “doküman id’si” gibi değerler tutabiliriz örneğin (ekstradan).

Sonuç başarılı olduğu takdirde şöyle bir şey göreceğiz:

{
"acknowledged": true
}

Indexing

Bu bölümde index’e kayıt ekliyoruz fakat bazı noktalarda dikkatli olmak gerekiyor yoksa üzerine yazma veya hiç yazamama gibi durumlar olabiliyor.

PUT: localhost:9200/{index_ismi}/{map_ismi}/{eleman_no}?refresh=true

PUT: localhost:9200/cities/citymap/1?refresh=true

{
"name" : "istanbul",
"suggest" : {
"input": [ "istanbul", "metropol", "kalabalik" ],
"output": "istanbul TR-34",
"payload" : { "cityId" : 34 },
"weight" : 34
}
}

Sonuç başarılıysa:

{
"_index": "cities",
"_type": "citymap",
"_id": "1",
"_version": 1,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"created": true
}

PUT: localhost:9200/cities/citymap/2?refresh=true

{
"name" : "ankara",
"suggest" : {
"input": [ "ankara", "baskent", "turkiyenin baskenti" ],
"output": "ankara TR-06",
"payload" : { "cityId" : 6 },
"weight" : 34
}
}

Sonuç başarılıysa:

{
"_index": "cities",
"_type": "citymap",
"_id": "2",
"_version": 1,
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}

PUT: localhost:9200/cities/citymap/3?refresh=true

{
"name" : "adana",
"suggest" : {
"input": [ "adana", "sicak" ],
"output": "adana TR-01",
"payload" : { "cityId" : 1 },
"weight" : 34
}
}

Sonuç başarılıysa:

{
"_index": "cities",
"_type": "citymap",
"_id": "3",
"_version": 1,
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}

Şimdi burada dikkat ettiyseniz, baştaki yazış biçiminde bahsettiğim {eleman_no} her şehir için arttırdım. Eğer bunu yapmamış olsaydım çakışma, üzerine yazma gibi durumlar olacaktı. Index’e bu kayıtları eklemiş olduk.

Ayrıca kayıtlarda “weight” diye bir parametre daha var. Bu da önerilerde hangisinin daha öne çıkacağını gösterir. Örneğin iki tarafa da input olarak “kalabalik” da verdiniz fakat İstanbul’un weight’ını daha fazla verdik, bu durumda birinci sırada İstanbul çıkacaktır.

Ayrıca bir önceki aşamada bahsettiğim payload’u da kullanmış olduk. Tabi kullanmama imkanımız da vardı. Şehirler için bir doküman id’si verdim aynı zamanda. İki şehrin de kendilerine özel plaka kodunu kullandım bunu yaparken. Bir output için birden fazla input girebiliyoruz gördüğünüz gibi. Input bir array olarak çalışıyor.

Query

Şimdi bir sorgu atarak bunu doğrulayalım. Query’leri doğrudan index üzerine atıyoruz.

POST: localhost:9200/{index_ismi}/_suggest?pretty

POST: localhost:9200/cities/_suggest?pretty

{
"suggest" : {
"text" : "baskent",
"completion" : {
"field" : "suggest"
}
}
}

Örneğin başkenti getirelim. Ankara gelmesi lazım. Sonuç şöyle olacak:

{
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"suggest": [
{
"text": "baskent",
"offset": 0,
"length": 7,
"options": [
{
"text": "ankara TR-06",
"score": 34,
"payload": {
"cityId": 6
}
}
]
}
]
}

Örneğin İstanbul’u aramak için “is” yazmam yeterli.

İstek:

{
"suggest" : {
"text" : "is",
"completion" : {
"field" : "suggest"
}
}
}

Cevap:

{
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"suggest": [
{
"text": "is",
"offset": 0,
"length": 2,
"options": [
{
"text": "istanbul TR-34",
"score": 34,
"payload": {
"cityId": 34
}
}
]
}
]
}

Tamamlama özelliğinin devreye girmiş olduğunu da gördük. Örneğin “a” harfi ile başlayan 2 adet şehrimiz var eklediğimiz. Biri Adana, öbürü Ankara. Şimdi sadece “a” yazalım. Her ikisinin de gelmesi gerekiyor.

İstek:

{
"suggest" : {
"text" : "a",
"completion" : {
"field" : "suggest"
}
}
}

Cevap:

{
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"suggest": [
{
"text": "a",
"offset": 0,
"length": 1,
"options": [
{
"text": "adana TR-01",
"score": 34,
"payload": {
"cityId": 1
}
},
{
"text": "ankara TR-06",
"score": 34,
"payload": {
"cityId": 6
}
}
]
}
]
}

Görüldüğü gibi. Buradaki “score” olayı eklerken verdiğimiz “weight” ile alakalı. Eğer bu weight değerini örneğin ankara için daha yüksek verseydik, Ankara önerilerde daha önce çıkacaktı. Örneğin İzmir, İstanbul, Ankara en kalabalık 3 şehrimiz. Bu şehirlerimizin hepsine “input” değeri olarak “kalabalik” versek. Fakat weight değerlerini en kalabalıktan daha az kalabalığa doğru versek mesela, kalabalık yazdığımızda değeri en yüksek olandan düşük olana doğru bir sıralama olacaktır.

Ayrıca belirteyim, bir mapping’de hata falan yaparsanız sonradan modifiye etmek mümkün değil. Index’i silip, yeniden tanımlamak gerekiyor. En azından yeni sürümde böyle. Belki ilerleyen zamanlarda buna benzer bir özellik getirirler.

Umarım yardımcı olabilmişimdir. İlerleyen zamanlarda aynı yöntemin Java ile nasıl yapılacağını göstermeyi planlıyorum. Umarım hatırlarım :)