Elasticsearch: Özelleştirilmiş Sıralama Algoritması

Geçtiğimiz günlerde Kariyer.net, iş bulma uygulamaları konusunda markete yeni bir fikir ile iddialı bir giriş yaptı hepimizin malumu. “İşin Olsun” adlı uygulama “abi Beylikdüzü’nden Ümraniye’ye kim gidecek, olsa şuralara yakın bir iş de biz de trafikte ömür tüketmesek!” veryansınına çare bulacak gibi. Takdire şayan bir iş.

Kariyet.net teknik ekibi, uygulamanın arkaplanında nasıl bir teknoloji, yazılım, sistem kullanıyor; hiç bir fikrim yok açıkçası. Benim de bu yazıdaki derdim, böyle bir ihtiyaca nasıl bir çözüm üretirdim? sorusuna kendimce cevaplar aramak.

O halde beyin fırtınasına başlayalım.

Kaynak: http://www.innercirclelabs.com/wp-content/uploads/2016/04/brainstorming.png

Hangi arama altyapısını tercih ederdim?

Bu sorunun cevabı benim için çok zor olmuyor elbette. Daha önceki yazılarımda da bol bol güzelleme yaptığım Elasticsearch ürününün bu iş için biçilmiş kaftan olduğunu düşünüyorum.

Yakınımda iş ararken nelere dikkat ederim?

  • Bana ne kadar yakın?
  • Şirketin bana biçtiği rol nedir?
  • Sağladığı maaş aralığı beklentilerimle uyuşuyor mu? (Ne yazık ki ülkemiz bu parametreyi iş ilanlarına alacak olgunluğa henüz ulaşamadı)
  • Servisi var mı?
  • Özel sağlık sigortası, yemekhane hizmeti gibi yan haklar nelerdir?

Bu filtreler temel anlamda ilk filtreleme için hepimizin ortak noktası sanıyorum. Üstelik bu filtrelerden geçen her firmaya eşit mesafe ile yaklaşmak da değil niyetim. Örneğin bir firmanın servisi varsa ve yan hakların tamamını sağlıyorsa, bu şartları tam sağlamayan ama en yakın olandan daha önceliklidir. Ya da servisi yok ama yan hakları varsa, servisi olan ancak yan hakların tamamını sağlamayandan daha önceliklidir.

Teknik Analiz

İlk anda dikkatimizi çektiği üzere klasik bir arama sonuç sıralama algoritması (A-Z, Z-A) böyle bir uygulama için yeterli olmayacaktır. Filtreden seçili değerlere göre sonuç setine dahil olacak kayıtların ağırlıklı puanlarını özelleştirilmiş olan hesaplayan bir algoritma uygulamanın kalbinde yer alıyor.

Gelelim asıl soruya, Elasticsearch bu karmaşık hesaplama konusunda biz geliştiricilere ne sağlıyor?

Kaynak: http://www.vector-eps.com/wp-content/gallery/math-formula-vectors/mechanics-formula-vectors.jpg

Elasticsearch’te Sıralama

Elasticsearch’ün kayıtları sıralarken varsayılan davranışı, kayıtları “_score” değerlerine göre sıralamasıdır. Bu değer hesaplanırken de yine varsayılan bir formül üzerinden işlem yürütülmekte.

Örneğin kayıt modelinizde “Title” ve “Description” adlı iki alan (field) olduğunu varsayalım. “iş” diye bir kelimeyi de bu alanlarda arıyorsunuz. En basit haliyle aradığınız kelime ilgili alanlarda ne kadar sıklıkla geçiyorsa o kaydın _score değeri o kadar yüksek olacaktır.

Ancak özelleştirilmiş bir ağırlıklı puanlama algoritması oluşturduğunuzda bu varsayılan davranış size yetmez hale gelecektir. Yazının temel çıkış noktası da bu yetmeme durumuna bir çözüm senaryosu geliştirmek üzerine.

Function Score Sorgusu

Bu sorgu tipi ile, sorgunuzun sonuç setine dahil olan kayıtların skorlamalarını özelleştirebilirsiniz. Elasticsearch’ün resmi dokümantasyonundan (1) an itibariyle kullanıma sunulmuş 5 farklı fonksiyonun detaylarına da erişmeniz mümkün. Bunlardan önemli gördüğüm ve kullanmaktan büyük keyif aldığım decay fonksiyonlarından gauss tipinin bizim sorunumuza çare üretebileceğini düşünüyorum.

Decay Fonksiyonları — Gauss

Aralık (range) tipine benzer bir işleve sahip bu fonksiyonlar ile, kullanıcının vermiş olduğu değer aralıklarındaki kayıtların skorları özelleştirilebilir. Range filtresi ile en büyük farkı, keskin çizgilerle sorguyu filtrelemek yerine daha yumuşak bir geçişle sorguyu filtreler.

UYARI: Elasticsearch’ün kısıtlarına göre, bu fonksiyonlara gönderebileceğiniz veri tipleri sadece date, numeric ve geo point tipinde olabilir.

Her bir decay fonksiyonunun kendine ait bir hesaplama formülü bulunmakta. Tam bir matematik fırtınası kopuyor aslında içeride. Örneğin, Gauss tipindeki hesaplama için kullanılan formül aşağıdaki gibi:

Kaynak: https://www.elastic.co/guide/en/elasticsearch/reference/current/images/Gaussian.png

Çok fazla teorik bilgiye dalıp boğulmadan örneğimiz üzerinden bir çalışma gerçekleştirelim. Bunun için de Docker’da bir Elasticsearch instance’ı oluşturalım:

docker pull docker.elastic.co/elasticsearch/elasticsearch:5.4.3
docker run --name elastic -d -p 9200:9200 -p 9300:9300 -e "http.host=0.0.0.0" -e "transport.host=127.0.0.1" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:5.4.3

Bir sonraki adımda career_sample_index adıyla yeni bir index oluşturalım. Bu index’in CompanyName, Position, Salary, Servicing ve Location adlarına sahip 5 adet field’ı olacak. Servicing şirketin servis hizmeti olup olmadığını, Location ise şirketin konumunu belirtiyor.

Bu index için örnek kayıtlar oluşturmadan hemen önce bir harita üzerinde rastgele konumlar seçelim:

Yukarıdaki haritada “mavi” ile işaretlenmiş nokta, evimizin olduğu nokta. Diğerleri ise iş fırsatlarının yer aldığı noktalar. Haritanın açık haline bu linkten ulaşabilirsiniz.

Şimdi de örnek kayıtlarımızı ekleyelim:

Öncelikle yapmak istediğim şey, bulunduğum konuma en yakından en uzağa şeklinde kayıtları sıralı bir şekilde listeleyebilmek. İşte bu noktada gauss fonksiyonu devreye giriyor:

10. satırda kullanacağım fonksiyon tipini (gauss) belirttikten sonra 11. satırda bu fonksiyona hesaplama için vereceğim field’ın adını yazıyorum. Asıl fırtına ise alttaki 4 satırda kopuyor.

  • (ZORUNLU ALAN) Origin: Hesaplama yaparken merkez noktayı belirttiğim (bulunduğum konum) alan.
  • Offset: 1 kilometrelik alan çevresindeki tüm değerlerin katsayısı 1 olarak hesaplanacak. Böylece bu alan içerisindeki kayıtların birbirlerine bir üstünlük sağlamamasını öngörüyorum. Varsayılan değer olarak 0 (sıfır) alır.
  • Decay: Varsayılan değeri 0.5. Scale parametresinde verilen uzaklık değerine denk gelen kayıtların kat sayısıdır.
  • (ZORUNLU ALAN) Scale: Merkez noktaya [offset + scale] mesafesindeki (1.5 kilometre) kayıtların decay parametresiyle hesaplanmasını sağlar.

Peki sorguyu daha özel bir hale getirmek istesem, tıpkı şunun gibi:

Aranan pozisyonun “Software Developer” olmasını istiyorum. Sıralama yaparken servis hizmeti sunan firmalar, sunmayanlardan 2 kat daha değerlidir. Bununla birlikte bana yakın bir mesafede olması da sıralamanın önem katsayına direkt olarak etki eder.

match_phrase sorgusu ile birebir bir “Software Developer” eşleştirmesi istiyorum ki böylece diğer pozisyonlar sonuç setine dahil edilmesin. gauss fonksiyonunu yukarıda açıklamıştık. Buraya ekstra gelen bir field: weight. Bu alanın değeri hesaplanan _score değerine 1.2 kat sayısı ile katkıda bulunacak.

İki filter alanı ile de ifade etmek istediğim nokta; “Servicing” değeri “true” olarak işaretlenmiş bir kaydın _score değeri, “false” olarak işaretlenmiş kayıttan 2 kat değerli olarak hesaplanmalıdır.

score_mode alanı ile bu ağırlıklara göre çıkan ayrı ayrı formül değerlerinin toplanarak bir kaydın _score değerinin hesaplanacağını belirtiyorum. Yine (1) numaralı referans url’inden bu alanın alternatif değerlerini öğrenebilirsiniz. Gelen sonuç seti ise aşağıdaki gibi:

İki sonuç seti ile son seti(sadece mesafeye göre sıralayan — mesafe ve kriterlere göre sıralayan) karşılaştırdığınızda göreceksiniz ki DEF Tech adlı firma bana daha yakın iken servisi olmadığı için, servisi olan ve benden biraz daha uzak ASD Cloud Solutions firmasının altında kalıyor. Bu da filtrelerimin sağlıklı bir şekilde çalıştığını gösteriyor.

Sonuç

Bu fonksiyonlar yardımı ile Elasticsearch’ün varsayılan sıralama algoritmasını pas geçerek tamamen özelleştirilmiş ve ihtiyaçlarınıza hizmet eden algoritmalar üretebilirsiniz. Özellikle “relevance” algoritmaları için bu servis bir harika çalışıyor! İlham aldığım linkler bölümündeki (2) numaralı link yardımı ile tarihsel bir gauss eğrisinin nasıl hareket edeceğini görmeniz mümkün.

Bir sonraki yazıda tekrar buluşana dek arama sonuçlarınızın hızlı olduğu kadar aradığınız derde derman da olması dileğiyle :)

İlham Aldığım Linkler

(1) https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html

(2) https://codepen.io/xyu/full/MyQYjN/

(3) https://jontai.me/blog/2013/01/advanced-scoring-in-elasticsearch/