Elasticsearch & Spring Data
Elasticsearch büyük veri yığınları içerisinde tam metin (full-text) aramada ve bu büyük veri yığınlarının analiz edilmesinde kullanılan Apache Lucene tabanlı, Java ile yazılmış açık kaynak kodlu bir arama ve analitik motorudur.
Indexler üzerinden arama yaparak çok hızlı bir şekilde sonuç üretebilmektedir. Dolayısıyla big data kavramının geçtiği yerlerde adından sıkça söz ettirmektedir.
Spring Data Elasticsearch
Spring Data, geliştiricileri veritabanı işlemleri için yazılan boilerplate kodlardan kurtarmaktadır. Repository
arayüzünü extend edip, önceden tanımlanmış formatlarda metotlar tanımlayarak veritabanı işlemleri gerçekleştirilebilmektedir.
Demo
Bu demoda Elasticsearch için gerekli konfigürasyonları yaptıktan sonra Spring Data Elasticsearch API’ı kullanarak nasıl tam metin arama yapabileceğimize, arama yaparken yazım yanlışlarına rağmen nasıl sonuç üretebileceğimize, nasıl custom analyzer oluşturabileceğimize ve ayrıca birkaç genel kullanım senaryosuna daha değineceğiz.
Öncelikle 9200 portunu dinleyen bir Elasticsearch instance’i başlatalım.
docker run -d --name es791 -p 9200:9200 -e “discovery.type=single-node” elasticsearch:7.9.1
Daha sonra Elasticsearch Java API’ı kullanabilmek için ilgili bağımlılığı ekleyelim.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>
ElasticsearchConfig.java
Elasticsearch instance’ına nasıl bağlanılacağını tanımlamak için Elasticsearch API tarafından sağlanan RestHighLevelClient’ı
kullanıyoruz.
Ayrıca oluşturulacak olan repository’lerin istenilen işlevleri sağlayabilmesi için uygulamanın ayağa kalktığı sınıf @EnableElasticsearchRepositories
anotasyonu ile işaretlenip parametre olarak repository’nin yer aldığı paketin yolu verilir. Böylelikle Spring tarafından gerekli konfigürasyonlar yapılacaktır.
Company.java
Elasticsearch’teki her bir kayıt doküman olarak geçmektedir. Bunu belirtmek için model, @Document
anotasyonu ile işaretlenip kaydedileceği index’i belirtmek için ise indexName
parametresi veriliyor.
Ayrıca employees
koleksiyonu FieldType.Nested
olarak işaretleniyor. Bu Elasticsearch’te bir Company
dokümanının içerisinde Employee
alanlarının bulundurulmasına olanak sağlıyor.
CompanyRepository.java
Şimdi ise önceden belirttiğimiz repository interface’ini oluşturalım. Bunun için ElasticSearchRepository
interface’ini extend etmemiz gerekiyor. Generic type’ları ilgili doküman ve bu dokümanın primary key tipi olarak belirtiyoruz. Bu adımlar sonucunda Elasticsearch üzerinde CRUD işlemleri yapabilir düzeye geliniyor.
Ayrıca Employee
ismine göre Company
arama işlevinin nasıl sağlanabileceğini bir query method bir de custom query oluşturarak görelim. Elasticsearch’te işlemler JSON üzerinden gerçekleştiği için custom query’de bu şekilde yazılmaktadır.
CompanyController.java
ElasticSearchRepository
, PagingAndSortingRepository’yi
extend ettiğinden pagination ve sorting işlemleri de yerleşik olarak gelmektedir. Pagination, metoda verilen PageRequest
parametresi ile sağlanmaktadır. Aşağıda 1. sayfa maksimum 20 veri olacak şekilde döndürülmektedir.
Şimdi Company
oluşturabileceğimiz bir POST endpoint’i oluşturuyor ve bu endpointi kullanarak iki farklı Company
kaydı gerçekleştiriyoruz.
ElasticsearchOperations.java
Şimdi ise Company
sınıfının description
field’ına göre arama işlevinin nasıl sağlanabileceğine bakalım. Bunu CompanyRepository
sınıfında bir metot tanımlayarak yapabiliriz. Fakat biz yazının devamında Elasticsearch Java API’ı tarafından sağlanan ElasticsearchOperations
arayüzünü kullanarak -Spring tarafından bu arayüze default olarak ElasticsearchRestTemplate
enjekte edilmektedir- bu işlevi nasıl sağlayabileceğimize bakalım.
Aşağıda matchQuery
metodu kullanılarak bir NativeSearchQuery
nesnesi oluşturuluyor. Daha sonra oluşturulan bu nesne, elasticSearchOperations
nesnesinin search metoduna parametre olarak verilerek arama işlevi sağlanmış oluyor.
Yukarıda oluşturduğumuz NativeSearchQuery
default olarak OR
operatörünü kullanmaktadır. Yani cümlenin içinde geçen kelimelere göre arama yapmaktadır. Oluşturduğumuz endpointe ‘kurumunuza özgü’ parametresini yollayarak istek yolladığımızda üretilen sonuç aşağıdaki gibidir.
‘özgü’ kelimesi her iki Company
nesnesinin description
field’ında geçtiği için her ikisi de arama sonucunda çıkmaktadır fakat ‘kurumunuza’ kelimesi sadece Kod Gemisi şirketinin description’ında geçtiği için arama sonucunda daha yüksek skor ile karşımıza çıkmaktadır.
Belirli bir yüzde üzerinde eşleşen arama sonuçlarını filtrelemek istiyorsak minimumShouldMatch
ile bu filtreleme işlemini gerçekleştirebiliriz.
Full-text Search
Şimdi kelimelere göre değil de full-text arama işlevinin nasıl sağlanabileceğine bakalım. Bunun için yapmamız gereken NativeSearchQuery’yi
build ederken matchQuery
metodunun operatorünü AND
olarak belirlemektir.
Yukarıda oluşturduğumuz endpoint’e ‘kurumunuza özgü’ parametresini yollayarak istek gerçekleştirdiğimizde üretilen sonuç aşağıdaki gibi olacaktır. Bu sefer sadece Kod Gemisi karşımıza çıkmaktadır.
Fuziness
Kullanıcılar arama yaparken yazım hataları yapabilirler. Bu tür bir senaryoyu ele almak için fuziness
kavramı kullanılmaktadır. Böylelikle arama için gönderilen text, kayıtlarla tamamen eşleşmiyorsa bile yine de bir sonuç üretmek mümkün olmaktadır.
Yukarıda NativeSearchQuery
nesnesi oluştururken Fuziness.ONE
ile arama eşleme işleminin bir harf değiştirerek de denenmesi belirtilmektedir. Ayrıca prefixLength(2)
ile kelimelerin ilk iki harfinde bir değişikliklik yapılmaması belirtilmekte ve böylelikle kombinasyon sayısı azaltılmaktadır.
Yukarıda oluşturduğumuz bu endpointe ‘kurumuNuza özgÜ’ yerine ‘kurumuMuza özgĞ” parametresiyle istek yolladığımızda da Kod Gemisi sonucu karşımıza çıkmaktadır.
Phrase Search
Bir başka senaryo full-text arama yaparken kullanıcıların ifadeleri art arda yazmayabileceğidir. Bu gibi bir senaryo için matchPhraseQuery
kullanılabilir. Ayrıca slop
ile bir terimin kaç kere taşınacağı belirtilebilir.
Oluşturduğumuz bu endpoint’e ‘kurumunuza çözümler’ parametresi ile istek yolladığımızda aşağıdaki sonucu elde etmekteyiz. Çünkü burada ‘kurumunuza çözümler’ ile arama yapılırken ‘kurumunuza xxxx çözümler’ gibi girilen ifadelerin bir kelime öteye taşınabileceği belirtilmiştir.
Ek: Controller metotlarında oluşturulan logicler, custom bir repository içerisinde oluşturulup her yerde kullanılabilir hale getirilebilir. Şu yazıda Elasticsearch Java API tarafından sağlanan başka bir özellik custom repository oluşturularak kullanılmıştır.
Custom Analyzer
Yukarıda oluşturulan arama endpoint’lerine ozgu
, cozumler
veya uretir
şeklinde türkçe karakterler olmadan istek yapıldığında herhangi bir eşleşme olmamaktadır. Eşleşme sağlayabilmek için asciifolding
filter’ına sahip custom bir analyzer
oluşturmamız gerekmektedir.
Default olarak tüm String field’lar bir analyzer tarafından işlenmektedir. Default analyzer kaydedilecek cümleyi boşluklar veya noktalama işaretleri gibi genel ayırıcılara göre böler ve her kelimeyi küçük harflere çevirir. Ayrıca yaygın İngilizce kelimeleri (a, the, is…) yok sayar.
Asciifolding ise bir filtre türüdür. Temel Latin Unicode bloğunda yer almayan alfabetik, sayısal ve sembolik karakterleri varsa ASCII eşdeğerlerine dönüştürür. Örneğin ö
harfini o
olarak değiştirmektedir. Bu değişiklik kayıtlara yansımamaktadır, ara bir işlem olarak düşünebiliriz.
Not: Eğer uygulamayı yukarıdaki haliyle ayağa kaldırdıysanız indexleme işlemi önceki ayarlarda gerçekleştiği için aşağıda yapılacak değişiklikler uygulamanıza yansımayacaktır. Dolayısıyla index’i sıfırlamanız gerekmektedir.
Öncelikle aşağıdaki settings.json
dosyası resources
package’i altında oluşturulur. Burada custom_analyzer
adında lowercase
ve asciifolding
filtrelerine sahip bir analyzer tanımı yapılmaktadır.
settings.json
dosyasında yapılan ayarların işlenmesini istediğimiz index modellerine @Setting
anotasyonu ile bu dosyanın yolu verilir. Ayrıca oluşturulan analyzer’ın uygulanmak istenildiği field’ların @Field
anotasyonunun analyzer
parametresi, oluşturulan analyzer ismi olarak belirtilir.
Uygulama ayağa kaldırıldığı zaman indexleme işlemi bu ayarlar da gözetilerek gerçekleşmektedir. localhost:9200/erp/_mappings
adresine istek yapıldığında ilgili field’ların analyzer’ının custom_analyzer
olarak maplendiği görülmektedir.
Oluşturulan custom_analyzer'ı
test etmek için localhost:9200/erp/_analyze
adresine aşağıdaki gibi bir istek gerçekleştirebiliriz. Aşağıda görüldüğü üzere Kurumunuza özgü
text’i kurumunuza
ve ozgu
olarak istenilen şekilde filtrelenmektedir. Böylelikle bu keyword’ler ile arama yapılsa dahi eşleşme sağlanmış olacaktır.
Bu yazıda genel kullanım senaryolarını ele aldık. Gereksinimlerinize uygun sorgular üretebileceğiniz birçok sınıfı org.elasticsearch.index.query paketi altında bulabilirsiniz.
Ayrıca yazıda gerçekleştirilen demoya buradan ulaşabilirsiniz.
Referanslar
- Introduction to Spring Data Elasticsearch
- Elasticsearch Queries with Spring Data
- ASCII Folding Token Filter
- Custom analyzer with Spring Data Elasticsearch
End Of File