Elasticsearch: Dile Özgü Sıralama Yöntemi

Elasticsearch’ün en temel sorunlara ürettiği nokta atış çözümler üzerine, bazı yazılarda yine nokta atış değinme şansına sahip olmuştuk. Bu satırların nihai amacı da yine temel bir sorun üzerine kafa yorup, Elasticsearch’ün ürettiği çözümü incelemek olacak. Lafı çok fazla uzatmadan dilerseniz sorunu irdelemeye çalışalım.

Sorun yaratalım

Kaynak: https://www.fluentin3months.com/learn-russian-cyrillic-alphabet/

Elimizde Türkiye’nin şehir isimlerinden kurulu bir veri kümesi bulunmakta.Toplu halde bu veri kümesini Elasticsearch’e aktardık, alfabetik olarak sıralayıp döndüren bir sorgu ile de ekrana yazdırmak istiyoruz.

Bu da sorun mu!? dediğinizi duyar gibiyim. Haklısınız, her programlama dili için oldukça basit bir işlemden bahsediyoruz. Gerçekten sorun olup olmadığını bu zamana kadar kullandığımız Elasticsearch yöntemleri ile irdeleyelim. Sorunun içerisinde boğulmak üzere ortam olarak Docker üzerine kurulu bir Elasticsearch instance’ında çalışacağız. Bunu sağlayabilmek için;

docker container run --name "elastic" -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.2.2

komutunu çalıştırıyoruz ve container’ın çalışır vaziyete gelmesini bekliyoruz. Her şey yolunda gitti ise, hemen bir index oluşturalım.

6. — 16. satırlar arasında default adını verdiğimiz bir analyzer oluşturuyoruz. Daha önceki yazılardan da aşina olduğumuz üzere bu analyzer’ı kullanarak indexlenecek olan veri öncelikle küçük harfe çevrilecek (lowercase filter) sonrasında ise ascii karşılıkları oluşturulacak (asciifolding filter).

18. — 33. satırlar arasında ise mapping ayarlarımız mevcut. Buna göre, city tipindeki nesnelerin name adında bir property’si olacak. Bu property indexlenirken yukarıdaki aşamalardan geçecek. Bir de sub-field olarak sortable adını verdiğimiz bir property’si mevcut. Bu alanın not_analyzed olarak kabul görmesi için tipine keyword diyoruz. Yine önemli noktalardan bir tanesi, bu alanın sadece sıralama amacıyla kullanılmasını istediğimiz için index özelliğine false değerini veriyoruz. Böylece aranılabilirlik— searchable — özelliğini kapatmış olduk.

Devam etmeden önce akla takıldığını düşündüğüm ilk soruya yanıt vermeye çalışayım: “Sub-field oluşturmasak name property’si ile sıralama yapamaz mıydık?” Tabii ki yapabilirdik. Ancak analiz edilmiş (analyzed) bir field üzerinde sorting, aggregation gibi yöntemler Elasticsearch tarafından memory kullanımını arttırdığı için önerilmiyor (1). Bu uyarıya rağmen yine de ilgili alan üzerinden arama gerçekleştirmek istersek 23. satırdan hemen sonra fielddata isimli bir property eklemek ve değer olarak true atamak yeterli olacaktır (2).

Lafı çok uzattım farkındayım ama yukarıdaki bilginin olası performans sorunları yaratmaması adına önemli olduğunu düşünüyorum. Index başarıyla oluşturulduysa sırada toplu halde veri ekleme işlemi var:

Hadi gelin sıralama içeren sorgumuzu Elasticsearch servisine iletelim:

Doğal olarak sıralamanın şöyle gelmesini bekliyoruz;

Bursa, Çanakkale, Hatay, İstanbul ve Şanlıurfa

Ama cevap şöyle geliyor:

{
"hits": {
"hits": [
{
"_source": {
"name": "Bursa"
}
},
{
"_source": {
"name": "Hatay"
}
},
{
"_source": {
"name": "Çanakkale"
}
},
{
"_source": {
"name": "İstanbul"
}
},
{
"_source": {
"name": "Şanlıurfa"
}
}
]
}
}

Sonuç, Türkçe diline hakim bir kişi için şaşırtıcı ve hatalı ancak bilgisayar dili için tam da olması gerektiği gibi!

Elasticsearch varsayılan olarak sıralama işlemini ASCII değerlere göre yapıyor. Her şehrin ilk harfinin ASCII değerlerine bakarsak, sıralamanın neden böyle olduğunu daha net anlayacağız.

C# ile karakterlerin ASCII değerlerini alıyoruz

Sorunu çözelim

Sinek küçük ancak mide bulandırıcı, öyle değil mi? Elasticsearch’ün ürettiği çözüm ise bana kalırsa tek kelime ile şahane!

Çözümü gerçekleştirmek adına Elasticsearch, analysis-icu isimli bir plugin sunuyor. ICU’nun açılımı; International Components for Unicode. Detaylarına adresinden ulaşabileceğiniz bu yöntem ile; C, C++, Java gibi dillerde hazırlanmış kütüphaneler kullanılarak yazılımlara globalization desteği sağlanıyor. Elasticsearch de Java tabanlı bir motor üzerine (Lucene) kurulu olduğu için biz de rahatlıkla bu kütüphaneyi kullanabiliyoruz.

Eklentiyi kurabilmek için Docker container’ının içerisine girmemiz gerekli. Bunun için öncelikle aşağıdaki komutu çalıştırıp container içerisinde komut istemcisini açıyoruz:

docker exec -it elastic bash

Container’ın içerisinde aşağıdaki komutu çalıştırıyoruz:

elasticsearch-plugin install analysis-icu

Plugin başarıyla kurulduktan sonra exit komutunu yazıp container içerisinden çıkıyoruz ve container’ı — yani Elasticsearch’ü — tekrar başlatıyoruz:

# Asagidaki komut ile container'in ID'sini aliyoruz.
docker container ls
# Asagidaki komut ile container'i tekrar baslatiyoruz.
docker container restart [CONTAINER_ID]
Plugin’in başarıyla yüklenip yüklenmediğini kontrol etmek isterseniz yine container içerisine girip elasticsearch-plugin list komutunu çalıştırmanız yeterli olacaktır.

Şimdi index oluşturma aşamasına geri dönelim;

Farklı olan kısım yalnızca sortable isimli sub-field’ımız. Tipi artık keyword değil, icu_collation_keyword.

icu_collation_keyword, Elasticsearch 5.5 versiyonu ve üstünde çalışmaktadır.

Bununla birlikte iki yeni property daha hayatımıza giriyor. language ve country. Bu property’ler yardımıyla ilgili alanın tr-TR kodlu kültür bilgisine göre değerlendirilmesini istiyoruz.

Evet yapmamız gereken tek şey bu. (3) numaralı kaynaktan detaylı kullanımına erişmeniz mümkün. Biz, yazının başında da söylediğimiz gibi nokta atış bir çözüm üzerine odaklanalım.

Yukarıdaki toplu kayıt sorgusunu ve sıralama içeren sorguyu aynen kullanacağımız için kalabalık yaratmıyorum. Index ismini değiştirerek o sorguları çalıştırmanız yeterli olacaktır.

Sorgudan gelen cevabı görelim;

{
"hits": {
"hits": [
{
"_source": {
"name": "Bursa"
},
"sort": [
"ᖨ勓┠႐໠ \u0001"
]
},
{
"_source": {
"name": "Çanakkale"
},
"sort": [
"ᜁ੐攧叒䧹䐄ᨃ䘌\u0000\u0001"
]
},
{
"_source": {
"name": "Hatay"
},
"sort": [
"ᮔ及⬠႐໠ \u0001"
]
},
{
"_source": {
"name": "İstanbul"
},
"sort": [
"Ღ及⡥㔓砈〇ఖ\u0000\u0000"
]
},
{
"_source": {
"name": "Şanlıurfa"
},
"sort": [
"✁੐柧ƕਖ਼䲤Țdž\u0600\u0000\u0000"
]
}
]
}
}

İşte şimdi tam da istediğimiz gibi oldu, Türkçe’ye hakim bir bilgisayar.

Sonuç

Daha önceki Elasticsearch yazılarımda da değindiğim üzere, “arama” denilen kavram artık bir mühendislik işi. Bu mühendisliğin gerektirdiği dizaynı yaparken de tek’e ve doğru bilinen’e takılıp kaldığımızda sunulan olanakları fazlaca gözardı edebiliyoruz. Bu özellik de yine gözardı edilen özelliklerden bir tanesi diye düşünüyorum.

Bir sonraki yazıya dek, mühendis ellerinizin dert görmemesi dileğiyle :)

Kaynakça

(1) https://www.elastic.co/guide/en/elasticsearch/guide/current/multi-fields.html

(2) https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html

(3) https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu-collation-keyword-field.html