Elasticsearch Notlarım
Daha çok benim işime yarayan hızlı yardım ve hata giderme notlarımı tutuyorum. Bir düzen içinde adım adım anlatan bir makale olmayacak. Hızlıca söz dizimlerini görmek için https://elasticsearch-cheatsheet.jolicode.com/
- Güvenlik ve Şifre İşlemleri
- Elasticsearch Şifresini Değiştirme
- Kibananın Şifresini Değiştirme
- Elasticsearch TemelleriVeri Tipleri
- Veri Tipleri
- Inverted Index
- Haritalama (mapping)
- Elle mapping Oluşturalım
- dynamic: false > Yeni belgelerin tanımsız alanları mapping içinde oluşturulmaz!
- dynamic: strict İle yeni alanların güncellenmesinde hata fırlatılır
- Sadece mapping içindeki alanlara sahip belgelerin kayıt edilmesi
- Analizörler (standard, whitespace, simple)
- Analizör Nasıl Seçilir?
- Kibana İle Konsol’dan İşlemleri Yapmak
PUT
vePOST
Farkı- Tüm İndeksleri Listelemek
_cat/indices?v
- Yeni İndeks Oluşturmak
- Yeni Kayıt Oluşturmak
- Belgenin Tümünü veya Bir Kısmını Çekmek
_source
İle Sonuçlarda Belgenin Alanlarını Süzmekfilter_path
filter_path
ve_source
Birlikte Kullanıldığında_source
&filter_path
’in URL de Kullanımı- Belgeyi Programlayarak Güncelleyelim
- Belge ve Endeks Silmek
bulk
ile Toplu Kayıt Girişimget
İle Birden Fazla Kayıt Çekmek- Tüm komutların
gist
hali - Tüm Endeksin Haritasını Çekmek
- Bir Alanın Haritasını Çekmek (
<endeks>/_mapping/field/<alan>
) - Filtered Query
- QUERY
match
ile aramamatch_phrase
ile aramamatch_all
İle Her Şeyi,match_none
İle Hiçbir Belgeyi Eşleştirmek- TERM Seviyesi Sorgular
- term Sorguları
- terms Sorguları
- null_value Boş değerli alanların endekslenmesi ve aranabilir olması
- exists İle bol olmayan alanların belgelerini çekmek
- must_not İle şartın (condition) tersini (değilini) sorgulayalım
- Küme İşlemleri (
aggregation
) - x İsimli alanda kaç farklı değer var? (
aggs-<y>-terms-field:<x>
) - Tire (hypen, dash) olunca neden kümelenmiyor?
- Dinamik haritalama ve
Standard Analyzer
Kullanıldığını Görelim - Elle haritalama ve Analizör olarak
whitespace
Kullanalım _reindex
İle Eski Endeksten Yenisine Veri Aktarmak{“statusCode”:502,”error”:”Bad Gateway”,”message”:”Client request timeout”}
- Metrik Kümeleme
- Sadece erkeklerin ortalama yaşını bulalım
- Tüm istatistik çıktıları görmek için “
stats
” - Dizi tipinde bir alanın değerlerinde veya basit bir alanın alabileceği değerlere göre süzmek
field_data
İle Gruplama- size İle 0–10 Arasında Gelecek Sonuçların Sayısı Getirme
- Hiç Sonuç Getirme, Verinin Verisini (
metadata
) Getir - Komut Çalıştırma (SCRIPT)
- Sıralama (Sorting)
- Sonuçların Sıralanması (sorting hits)
- Kümeleme Sonuçlarının Sıralanması (sorting aggregation results)
Güvenlik ve Şifre İşlemleri
Elasticsearch Şifresini Değiştirme
Kibananın Şifresini Değiştirme
Elasticsearch için tanımladığınız şifreyi Kibana için de kullanmak isterseniz basitçe şunu yapmalısınız:
C:\> cd C:\kibana-7.9.3-windows-x86_64\binC:\...\bin>kibana-keystore add elasticsearch.username
Enter value for elasticsearch.username: *******C:\...\bin>kibana-keystore add elasticsearch.password
Enter value for elasticsearch.password: *******C:\...\bin>kibana-keystore list
elasticsearch.username
elasticsearch.password
Ref: https://www.elastic.co/guide/en/kibana/7.10/secure-settings.html
Elasticsearch Temelleri
Veri Tipleri
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
Inverted Index
Elasticsearch içine basılan tüm belgelerin içindeki kelimeler (kelimeleri ayrıştırmak için tokenizer kullanılır) bir liste içinde tekrarsız şekilde tutulur. Hangi kelimenin hangi belgelerde geçtiği işaretlenir. Böylece inverted index oluşturulmuş olur. Bir sorgu yapıldığında, sorguda geçen kelimeler hangi belgelerde geçmişse, geçme sıklığına ve kelimelerin arama cümlesindeki yakınlıklarına en yakın sonuçlar üst sırada gelecek şekilde puanlanır. Bu fikir adamların aklına gelince oturup bunu programlamışlar ve karşımıza bizim aramalarımızı kolaylaştıran ve sonuçların keskinliğini arttıran elasticsearch çıkmış. Peki belgelerin analizi nasıl gerçekleşiyor ona bakalım.
Haritalama (mapping)
Hiç mapping oluşturmadan, doğrudan kayıtları sadece endeks alanını tayin ederek girdiğinizde “dinamik haritalama” yapılır Elasticsearch tarafından. Dinamik haritlamada;
- girilen verilerin tiplerini, endekslenip endekslenmeyeceklerini (ki elasticsearch varsayılan olarak her alanı endeksler),
- hangi sorguya göre aranabileceğini (tam metin araması -
full text search
-, terim seviyesi -term level
- vs.) otomatik olarak yapar.
Ancak elle bu ayarları oluşturmamız da mümkün ;)
Endeks bir kez oluştuktan sonra tekrar oluşturulması ve verilerin yeni endekse göre yapılandırılması mümkün değildir (bazı güncellemeleri yapabilirsiniz ama örneğin kayıt geldiyse endekse, alanın tipini değiştiremezsiniz). Tüm endeks silinip, yeniden yaratılıp, kayıtların tekrar girişi yapılabilir. Veya yeni endeks yaratılıp eskisinden _reindex
ile verilerin kopyalanmasını sağlayabilirsiniz (timeout alırsanız paniklemeyin!).
Önce dinamik olarak mapping oluşacak şekilde kayıt girişi yapalım ve oluşan haritalamayı görelim:
DELETE logsPOST logs/_doc
{
"Log_Time" : "2020/12/08 09:00:06",
"Level" : "ALARM_NORMAL",
"ID" : "140589228087040",
"File" : "Service.cpp:169->OnDatabaseStatusNotification"
}GET logs/_mapping# Aslında aşağıda dönen bir şey yok. Azıcık Elasticsearch'e alışınca tipik mapping elemanları olarak aşina olacaksınız. Gözden geçirin ama şifre çözecekmiş hissine kapılıp takılıp kalmayın. Ekstra alanlar var ama dikkatinizi çekmesini istemediğim yerleri ... ile maskeledim#{
#"logs" : {
# "mappings" : {
# "properties" : {
# "File" : {
# "type" : "text",
# "fields" : {..}
# },
# "ID" : {
# "type" : "text",
# "fields" : {..}
# },
# "Level" : {
# "type" : "text",
# "fields" : {..}
# },
# "Log_Time" : {
# "type" : "date",
# "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
# }
# }
# }
#}
#}
Elle mapping Oluşturalım
Önce endeksi silelim ki tekrar oluşturabilelim.
DELETE logs
File
alanını haritalamayalım ve kayıt eklerken oluştuğunu görelim:
PUT logs
{
"mappings": {
"properties": {
"ID": {
"type": "text"
},
"Level": {
"type": "text"
},
"Log_Time": {
"type": "date",
"format": "yyyy/MM/dd hh:mm:ss"
}
}
}
}
Şimdi _mapping
ile oluşan belge haritasına bakalım ve kayıt ekledikten sonra File
alanının haritaya eklendiğini görelim:
{
"logs" : {
"mappings" : {
"properties" : {
"ID" : {
"type" : "text"
},
"Level" : {
"type" : "text"
},
"Log_Time" : {
"type" : "date",
"format" : "yyyy/MM/dd hh:mm:ss"
}
}
}
}
}
Şimdi kayıt ekleyelim ve File alanının oluştuğunu _mapping ile görelim:
POST logs/_doc
{
"Log_Time" : "2020/12/08 09:00:06",
"Level" : "ALARM_NORMAL",
"ID" : "140589228087040",
"File" : "Service.cpp:169->OnDatabaseStatusNotification"
}# Kendi kafasından bir ID değeri oluşturdu
# {
# "_index" : "logs",
# "_type" : "_doc",
# "_id" : "bmMfaHYBDOf7SY4fE4Di",
# "_version" : 1,
# "result" : "created",
# "_shards" : {
# "total" : 2,
# "successful" : 1,
# "failed" : 0
# },
# "_seq_no" : 0,
# "_primary_term" : 1
# }GET logs/_search#{
# "took" : 610,
# "timed_out" : false,
# "_shards" : {...},
# "hits" : {
# "total" : {
# "value" : 1,
# "relation" : "eq"
# },
# "max_score" : 1.0,
# "hits" : [
# {
# "_index" : "logs",
# "_type" : "_doc",
# "_id" : "bmMfaHYBDOf7SY4fE4Di",
# "_score" : 1.0,
# "_source" : {
# "Log_Time" : "2020/12/08 09:00:06",
# "Level" : "ALARM_NORMAL",
# "ID" : "140589228087040",
# "File" : "Service.cpp:169->OnDatabaseStatusNotification"
# }
# }
# ]
# }
#}# Haritaya bakalım:
GET logs/_mapping# File alanı dinamik olarak oluşturuldu ve elle oluşturduklarımızdan biraz daha detaylı olmuş:
#{
# "logs" : {
# "mappings" : {
# "properties" : {
# "File" : {
# "type" : "text",
# "fields" : {
# "keyword" : {
# "type" : "keyword",
# "ignore_above" : 256
# }
# }
# },
# "ID" : {
# "type" : "text"
# },
# "Level" : {
# "type" : "text"
# },
# "Log_Time" : {
# "type" : "date",
# "format" : "yyyy/MM/dd hh:mm:ss"
# }
# }
# }
# }
#}
dynamic: false > Yeni belgelerin tanımsız alanları mapping içinde oluşturulmaz!
Buraya kadar dinamik alan oluşturulmasını engellememiştik haydi şimdi engelleyerek bakalım ve mapping içinde yeni bir File
alanının oluşmadığını görelim:
DELETE logs
PUT logs
{
"mappings": {
"dynamic": false,
"properties": {
"ID": {
"type": "text"
},
"Level": {
"type": "text"
},
"Log_Time": {
"type": "date",
"format": "yyyy/MM/dd hh:mm:ss"
}
}
}
}GET logs/_mapping
# dynamic özelliği false olarak geldi:
#{
# "logs" : {
# "mappings" : {
# "dynamic" : "false",
# "properties" : {
# "ID" : {
# "type" : "text"
# },
# "Level" : {
# "type" : "text"
# },
# "Log_Time" : {
# "type" : "date",
# "format" : "yyyy/MM/dd hh:mm:ss"
# }
# }
# }
# }
# Şimdi kayıt olmadığını _count ile görelim ve bir kayıt ekleyerek mapping içinde File oluştmadığını görelim (dynamic:false):GET logs/_count
#{
# "count" : 0,
# "_shards" : {
# "total" : 1,
# "successful" : 1,
# "skipped" : 0,
# "failed" : 0
# }
#}
POST logs/_doc
{
"Log_Time" : "2020/12/08 09:00:06",
"Level" : "ALARM_NORMAL",
"ID" : "140589228087040",
"File" : "Service.cpp:169->OnDatabaseStatusNotification"
}GET logs/_mapping
#{
# "logs" : {
# "mappings" : {
# "dynamic" : "false",
# "properties" : {
# "ID" : {
# "type" : "text"
# },
# "Level" : {
# "type" : "text"
# },
# "Log_Time" : {
# "type" : "date",
# "format" : "yyyy/MM/dd hh:mm:ss"
# }
# }
# }
# }
#}
dynamic: strict İle yeni alanların güncellenmesinde hata fırlatılır
Strict olarak işaretlendiğinde mapping
içinde olmayan yeni bir alan ile mapping
güncellenmek isteniyor ama Elasticsearch hata fırlatıyor:
DELETE logs
PUT logs
{
"mappings": {
"dynamic": "strict",
"properties": {
"ID": {
"type": "text"
},
"Level": {
"type": "text"
},
"Log_Time": {
"type": "date",
"format": "yyyy/MM/dd hh:mm:ss"
}
}
}
}POST logs/_doc
{
"Log_Time" : "2020/12/08 09:00:06",
"Level" : "ALARM_NORMAL",
"ID" : "140589228087040",
"File" : "Service.cpp:169->OnDatabaseStatusNotification"
}# File alanı mapping içinde tanımlı değil ancak
# belgenin kayıt edilmesi sırasında dynamic: strict olarak
# işaretlendiği için mapping güncellenmek isteniyor ancak
# hata fırlatılıyor.#{
# "error" : {
# "root_cause" : [
# {
# "type" : "strict_dynamic_mapping_exception",
# "reason" : "mapping set to strict, dynamic introduction of [File] within [_doc] is not allowed"
# }
# ],
# "type" : "strict_dynamic_mapping_exception",
# "reason" : "mapping set to strict, dynamic introduction of [File] within [_doc] is not allowed"
# },
# "
Sadece mapping içindeki alanlara sahip belgelerin kayıt edilmesi
Yeni belge eklenirken şemamızda olmayan alanların girişini engellemek istiyorsak dynamic: strict
kullanarak hata fırlatır ve sadece _mapping
de tanımlı alanlara sahip belgelerin endekse kaydına müsade etmiş oluruz!
{“statusCode”:502,”error”:”Bad Gateway”,”message”:”Client request timeout”}
Elimdeki endeksin içinde 313 bin kayıt var ve bu kayıtları, tiplerini daha düzgün tanımladığım (daha iyi bir mapping
’e sahip) yeni bir endekse kaydederek daha düzgün sorgular yazmak istedim. Ben Kibanadan bu isteği gönderdim ama iş uzun zaman aldığı için Kibananın beklediği zamanı aştı (timeout) yanıt alamadı ve bu hatayı verdi:
{“statusCode”:502,”error”:”Bad Gateway”,”message”:”Client request timeout”}
Aktif bir _reindex sürecinin durumunu görebilmek için şu komutu bir çok defa çalıştırıp, kaç kayıttan kaçının kopyalandığını görebilirsiniz:
GET _tasks?actions=*reindex&detailed
Analizörler
Benim anlamsız !!! gözyaşlarım ve ilkokul 3 aşkım :) ile anılarım.
Cümlesini içeren bir belgeyi Elasticsearch analiz edecek ki endekslesin ama nasıl analiz edecek ona bakalım:
- Karakter süzgecinden geçirecek. Sayıları yazıya çevirecek (3 yerine üç), gereksiz karakterleri yok edecek (!!! veya :) veya html etiketleri vs.)
- Tokenizer ile cümlede geçen kelimeleri ayrıştıracak. Cümlenin diline göre bazen tire işaretiyle (orta tire diye bir şey yok!) kelimeler bir araya geliyor (örn. use-case veya ‘Ankara-Kars tren yolu..’) veya kelimeler boşluk karakteriyle ayrılıyor olabilir. Kelimeleri ayrıştırırken hangi tokenizer kullanılacağını seçmemiz veya kendimize göre analizör yazmamız gerekir.
- Token süzgeciyle kelimeleri ayrıştırdıktan sonra tek başına anlamları olmayan (stop words) kelimeleri atmamız gerekir. Örneğin ile, ve, de, ke, and, the, with gibi.
Elasticsearch beraberinde çeşitli analizörler ile gelir.
standard analizör ile bir çok dilde genel amaçlı kullanılabilen, noktalama işaretlerini yok sayıp(mesela parantez işaretleri kaldırıldıktan sonra kelimeleri ayrıştırır), küçük harfler halinde terimlere ayrıştırabiliriz.
Whitespace analizörü ise boşluk karakterine göre cümleyi analiz ederek, işaretleme karakterlerini ihmal etmeden, tire ile birleşen kelimeler ayrıştırılmadan, kelimelerin büyük ya da küçük harfe dönüştürülmeden ayrıştırır.
Bir GUID değeri standart analizör ile ayrıştırdığımızda tire karakterlerinden ayırarak her bir bloğu ALPHANUM olarak ayrıştırırken whitespace analizörü sayesinde bir bütün olarak tokenlaştırabilirsiniz.
a0fa3cb5-ddfd-4d31-bab7–79f35f7a8d62
simple Analizörüyse sayı olmayan tüm metinleri küçük harf ile ayrıştırır.
Analizör Nasıl Seçilir?
Bir alanın hangi tip analizör ile ayrıştırılacağını haritalama sırasında verebiliriz:
Tarih bilgisini formatlı olarak parçalamak istersek format alanını haritalamada kullanabiliriz. Aşağıdaki örnekte tarih olarak parse edilecek post_date
alanının hangi formata göre ayrıştırılacağı format: YYYY-MM-DD
ile verilmiş:
Format bilgisi dışında gelen belge kaydı hata üretecektir:
Hata:
_source Bilgisini Elasticsearch Üstünden SaklaMAMAK
Verileri saklamamak öncelikle size ne kaybettirir ona bakalım:
- Update, update_by_query ve reindex API’lerini kullanamazsınız.
- Anında vurgulayarak çıktıları çekmek (highlight).
- Eşlemeleri (mappings) veya analizi değiştirmek veya bir dizini yeni bir ana sürüme yükseltmek için bir Elasticsearch indeksinden diğerine yeniden indeksleme yeteneği (reindex).
- Dizin zamanında kullanılan orijinal belgeyi görüntüleyerek sorgularda veya toplamalarda hata ayıklama yeteneği.
- Potansiyel olarak gelecekte, dizin bozulmasını otomatik olarak onarma yeteneği.
Disk alanından kaygılanıyorsanız bize sıkıştırma seviyesini değiştirerek verileri saklamamız salık veriliyor Elasticsearch tarafından.
Şimdi gelelim neden yapmak isteriz ve nasıl yapabiliriz kısmına.
Elastic varsayılan olarak gelen belgeyi endekslerken (tersine dizinlenmiş olarak kelimelerin sıklığı ve belgede geçişine göre endeks oluştururken) aynı zamanda _source
alanı içinde belgenin kendisini de tutar. Eğer veriyi hem harici bir veritabanında tutup hem endekslenmesi için Elasticsearch içine gönderdiyseniz veriyi ikinci kez depoluyor olacaksınız. Buda endeksinizin şişmesine neden olacak. Dilerseniz _source
alanını elastic üstünde tutmayarak sadece orjinal verilerinizin olduğu veritabanında erişebilecek ID
alanını Elasticsearch üstünde tutmak isterseniz aşağıdaki gibi _source.enabled: false
ve ilgili ID alanı için store: true
değerlerini haritalamanızda belirtebilirsiniz.
Varsayılan olarak _source
alanı tutulmazken özellikle şu şekilde bir aramayla /{belge_id}?field=user_id
depolanmış veriye ulaşabilirsiniz.
_aliases İle Birden Fazla Endeksi Birbirine Bağlı Şekilde Sorgulamak
En genel haliyle günlük alınan logların kaydını Elasticsearch üstünde tarih bilgisi son ekiyle oluşturuyoruz.
Yukarıdaki ekran çıktısında Ağustos ayının ilk gününe ait günlük kayıtları bir endeks üstünde oluşmuşken, sonraki günün kayıtları eventlog-2014–08–02 isimli endeks üstünde oluşuyor. Bu endekslerin her birinin içinde sorgu çalıştırıp sonuçlarını birleştirmek yerine _aliases ile bir takma ad altında tüm endeskleri birleştirerek sorgumuzu takma ad üstünde gerçekleştirebiliriz.
POST /_aliases
{
"actions" : [
{ "add": { "index" : "eventlog-2014–08–01", "alias": "gunluk" } },
{ "add": { "index" : "eventlog-2014–08–02", "alias": "gunluk" } },
{ "add": { "index" : "eventlog-2014–08–03", "alias": "gunluk" } }...
]
}
Veya
POST /_aliases
{
"actions" : [
{ "add": { "indices" : [
"eventlog-2014–08–01",
"eventlog-2014–08–02",
"eventlog-2014–08–03"
], "alias": "gunluk" } }
]
}
Veya
POST /_aliases
{
"actions" : [
{ "add": { "index" : "eventlog*", "alias": "gunluk" } }
]
}
Böylece sorgumuzu takma ad üstünde çalıştırabiliriz:
GET gunluk/_search
Kibana İle Konsol’dan İşlemleri Yapmak
Kibana kurulumuyla ilgili çeşitli makaleler bulabilirsiniz.
PUT ve POST Farkı
PUT ile yeni kayıt yaratıp güncelleyebilirsiniz,
POST ile sadece güncelleyebilirsiniz.
Tüm İndeksleri Listelemek
http://localhost:9200/_cat/indices
_cat/indices
_cat/indices?v
pri: shard sayısı
rep: replika sayısı
Her indeks ön tanımlı olarak 5 shard ve 1 replika ile gelir.
docs. count: İndeks içinde kaç kayıt olduğunu gösterir
health: rep sayısı 0 olanlar için health değeri green!
Çünkü ekstra bir düğüm (node) üstünde saklanmaları gerekmediğini rep:0 ile vermişiz.
Ancak health değeri yellow olanların replika sayıları 1 olduğundan ve sadece bir elasticsearch düğümü (node) çalıştırdığımızdan dolayı yedeklenmediklerini ve bu yüzden sarı işaretlendiğini görüyoruz. Yani tüm istekler sadece birincil shard üstünde çalıştırılacak demektir.
store. size: saklanan verilerin indeks içindeki kapladığı alanı verir. Elasticsearch belge temellidir (document base) ve içinde hiç belge olmayan indekslerin 280 byte olduğunu görüyoruz. Oysa .kibana_1 indeksinin yaklaşık 21 megabyte yer kapladığını ve içinde 32 belge olduğunu görüyoruz
Yeni İndeks Oluşturmak
PUT metoduyla ister sadece indeks adı isterseniz veri yapısını (mapping) da girebilirsiniz
Yeni Kayıt Oluşturmak
Elasticsearch 6 öncesindeki yapıların İlişkisel Veri tabanı Yönetim Sistemlerindeki (RDBMS) karşılıkları aşağıdaki gibiYDİ.
Aşağıdaki hatayı görünce “hani type bilgisi” diye sordum ve izi takip edince gördüm ki; ARTIK TYPE YOK!
Yukarıdaki ekran çıktısında iki şey dikkatinizi çekecektir:
- Kırmızıyla belirttiği artık geçersiz olan belgenin ID bilgisinin şu şekilde girmemizi istemesi:
yeni_index/_doc/id_değerimiz
veyayeni_index/_create/id_değerimiz
. Type bilgisi nereye gitti diye okumak isterseniz (google çevirisi de epeyce anlaşılır) bu adrese alayım sizi.
PUT yeni_index/yeni_tablo/1?pretty
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz"
}veya curl ifadesiyle:curl -XPUT "http://localhost:9200/yeni_index/yeni_tablo/1" -H 'Content-Type: application/json' -d'
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz"
}'
Peki komutu bu şekilde çalıştırınca ne oluyor arkada?
{
"yeni_index" : {
"aliases" : { },
"mappings" : {
"properties" : {
"email" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"username" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"settings" : {
"index" : {
"creation_date" : "1605429709442",
"number_of_shards" : "1",
"number_of_replicas" : "1",
"uuid" : "2blrcK6vTo6qfzplep0a6w",
"version" : {
"created" : "7090399"
},
"provided_name" : "yeni_index"
}
}
}
}
yeni_tablo
bilgisi ihmal ediliyor ve her şey yeni_index
’e bağlanıyor. Özetle tavsiyesinde diyor ki:
type
artık yok çünkü ilişkisel veri tabanlarıyla benzeşim yaparak 7. sürüme kadar geldiğimiz bu noktada her şeyiindex
içine bağlamak daha doğru olacaktı. Yok siz illatype
kullanmak isterseniz, kaydedeceğiniz belgelere birtype
bilgisi ekleyin ve hangi tablonun kaydı olduğunu orada belirtin. Sorgu çaktığınızda filter kısmına zaten endekslediğimiztype
alanını yazın:
GET twitter/_search
{
"query": {
"bool": {
"must": {
"match": {
"user_name": "kimchy"
}
},
"filter": {
"match": {
"type": "tweet"
}
}
}
}
}
Buna göre belge yaratmanın Elasticsearch 7 sürümüne göre:
_type
kısmına artık _doc
yazıyor. _id
bilgisini biz 1 olarak girdiğimiz için 1 oldu, eğer /yeni_index/_doc
deseydik kendi bir değer üretecekti. ID değeri 1 olan belgeyi yeni oluşturduğumuz için result
bilgisi created
değeri alıyor. Ve yine ilk kez yaratıldığı için _versiyon
bilgisi 1. Eğer güncellersek bu değer her güncellemede 1 artacak.
_shards olarak 1 adet primary ve 1 adet replica olmak üzere 2 shard tanımlıydı ancak sadece 1 Elasticsearch düğümü çalıştığı için health bilgisinde yellow yazıyordu. Şimdi bu kaydı girdiğimizde kaç shard bu kayıtla ilgili olmalıydı ve kaçı başarılı kaçı başarısız olarak bu işlemden etkilendi bilgisini görüyoruz:
curl -XPUT "http://localhost:9200/yeni_index/_doc/1?pretty" \
-H 'Content-Type: application/json' \
-d'{ \
"id": 1, \
"name": "Leanne Graham", \
"username": "Bret", \
"email": "Sincere@april.biz" \
}'
Oluşan kayıtların tümünü listelemek için:
Belgenin Tümünü veya Bir Kısmını Çekmek
_type: 7 ile artık _doc
olarak kaydediyoruz
_id: belgenin eşsiz değeri
_version: 1, ilk kez eklenmiş ve güncellenmemiş
found: belge bulunduysa true
döner
_source: belgenin son halini tam olarak görüyoruz
Olmayan ID’li kayıt sorgulandığında:
Olmayan type
sorgulandığında (artık type
kullanma be kardeşim):
Belgeyi değil, belgenin meta datasını (belgenin üst verisini) getirmek için sorgu sonuçlarındaki belgeleri ihmal etmek için _source=false
yazıyoruz. Zaten sonuç JSON
verisine bakarsanız _source
kısmında kayıtları görürsünüz ve false
dediğiniz zaman kayıtlara dair hiçbir şeyi görüntülemezsiniz.
_source İle Sonuçlarda Belgenin Alanlarını Süzmek
Belgenin bazı özelliklerini getirmek için _source=alan1,alan3…
curl -XGET "http://localhost:9200/yeni_index/_doc/1?_source=id,email"
_source
hem URL üstünde hem de isteğin gövdesinde yer alabilir (not: filter_path
sadece URL üstünde yer alır).
filter_path
*** Tüm REST API’leri, Elasticsearch tarafından döndürülen yanıtı azaltmak için kullanılabilecek bir
filter_path
parametresi kabul eder. Bu parametre, nokta gösterimi ile ifade edilen virgülle ayrılmış bir filtre listesi alır. ** ile iç içe her şeyi, * ile bulunduğu hizada her şeyi, kelime* ile başında “kelime” olan her şeyi seçer.
filter_path ve _source Birlikte Kullanıldığında
filter_path
ile _source
birlikte kullanıldığında önce filter_path
çalışır ve ardından eğer _source
içinde belirtilen alanlar varsa görüntülenir. Eğer filter_path
sonucunda bir çok alan dönüyor ve _source
içindekiler ile daha da süzülüyorsa bu kez sadece _source
içindeki alanlar görüntülenir.
Bir filter_path
Örneği Daha
GET kibana_sample_data_ecommerce/_search?filter_path=hits.total,**.customer_*,**.products*.p*
{
"query": {
"match": {
"customer_full_name": "Eddie Underwood"
}
}
}
Eğer sadece sonuç kayıtları görmek üst veriyi görmemek isterseniz
Sadece sonuç kayıtlarını çekelim:
GET test/_search?filter_path=hits.hits._source
_source & filter_path’in URL de Kullanımı
_source
isteğin gövdesinde de kullanılabilirken filter_path
sadece URL üstünde tanımlanabilir. Burada hem _source
hem de filter_path
’in URL de kullanımını göreceğiz. Sadece sonuç kayıtlarında bazı alanları ve metadata
içindeki istediğimiz bilgileri çekelim:
GET test/_search?filter_path=took,hits.total.relation,hits.hits._source&_source=age
Tüm Belgeyi veya Bir Kısmını Güncellemek
Tüm Belgeyi Güncelleme
# Belgenin tüm alanlarını güncellemek
PUT yeni_index/_doc/1
{
"id" : 1,
"name" : "Cem Topkaya",
"username" : "cemmmm",
"email" : "ben@cemtopkaya.com"
}
veya
curl -XPUT "http://localhost:9200/yeni_index/_doc/1" -H 'Content-Type: application/json' -d'{
"id" : 1,
"name" : "Cem Topkaya",
"username" : "cemmmm",
"email" : "ben@cemtopkaya.com"
}'
Sonuçta güncellemenin başarılı olduğu (result
) shard sayısı (_shard.successful
) ve _version
bilgisi 1 artmış olacak.
Kısmi Güncelleme
Kısmi güncellemede POST
metodunu kullanacağız.
Belge oluştururken create
kullanılırken güncellemede update
öneriliyor.
curl -XPOST "http://localhost:9200/yeni_index/_update/1" -H 'Content-Type: application/json' -d'
{
"doc": {
"name" : "Canan Topkaya",
"username" : "cano"
}
}'
veya
# Belgenin bazı alanlarını güncelleyelim
POST yeni_index/_update/1
{
"doc": {
"name" : "Canan Topkaya",
"username" : "cano"
}
}
Belgeyi Programlayarak Güncelleyelim
İsim bilgisini anonim olarak değiştirip eposta adresini yıldız karakteriyle gizleyelim
# Belgeyi programlayarak güncelleyelim
POST yeni_index/_update/1
{
"script": "ctx._source.name = 'anonim'; ctx._source.email = '***'"
}veyacurl -XPOST "http://localhost:9200/yeni_index/_update/1"
-H 'Content-Type: application/json'
-d '{
"script": "ctx._source.name = '\''anonim'\''; ctx._source.email = '\''***'\''"
}'
Belge ve Endeks Silmek
Belge Silmek
ID değeri bilinen bir belgeyi silmek için DELETE
metodu kullanılır.
DELETE yeni_index/_doc/1
curl -XDELETE "http://localhost:9200/yeni_index/_doc/1"
Silerken bile versiyonunu 1 arttırdığına dikkat
Sildiğimiz belgenin olmadığını GET ile sorgulayarak görebiliriz:
Bu sorgu curl ile curl -XGET "http://localhost:9200/yeni_index/_doc/1"
yazılabilirken curl -XHEAD "http://localhost:9200/yeni_index/_doc/1"
ile de yapılır ve 404 hatası alınarak görülebilir:
Aynı şekilde eğer kayıt varsa curl sonucu HTTP 200
durum koduyla dönecekti:
Endeks Silmek
Önce silmek için yaratalım
Endeksleri listeleyelim
Ve silinecek_endeks
’i silelim
Endeksi görüntülemek istediğimizde “böyle bir endeks yoktur” mesajını alalım:
# ENDEKS silmek için yaratalım
PUT silinecek_endeks# Endeksleri listeleyelim
GET _cat/indices?v# Endeksi Silelim
DELETE silinecek_endeks# Endeksi görüntüleyelim
GET silinecek_endeks
bulk ile Toplu Kayıt Girişi
JSON olarak birden fazla satırı bir kerede girmek, güncellemek, silmek için bulk
kullanılır.
POST _bulk
{"index":{"_index":"calisan","_id":"1"}}
{"name":"Ervin Howell","username":"Antonette","email":"Shanna@melissa.tv"}
{"index":{"_index":"adres","_id":"1"}}
{"street":"Kulas Light","suite":"Apt. 556","city":"Gwenborough","zipcode":"92998-3874","geo":{"lat":"-37.3159","lng":"81.1496"}}
{"index":{"_index":"calisan","_id":"2"}}
{"name":"Clementine Bauch","username":"Samantha","email":"Nathan@yesenia.net"}
{"index":{"_index":"adres","_id":"2"}}
{"street":"Douglas Extension","suite":"Suite 847","city":"McKenziehaven","zipcode":"59590-4157","geo":{"lat":"-68.6102","lng":"-47.0653"}}
{"index":{"_index":"calisan","_id":"3"}}
{"name":"Ervin Howell","username":"Antonette","email":"Shanna@melissa.tv"}
{"index":{"_index":"adres","_id":"3"}}
{"street":"Victor Plains","suite":"Suite 879","city":"Wisokyburgh","zipcode":"90566-7771","geo":{"lat":"-43.9509","lng":"-34.4618"}}
curl ifadesinde bir dosyadan okuyarak çalıştırmak daha yerinde olacaktır.
curl -H "Content-type: application/x-ndjson"
-XPOST "http://localhost:9200/_bulk"
--data-binary @"toplu_data.json"
mget İle Birden Fazla Kayıt Çekmek
Birden fazla ID ile belgelerin çekilmesi için mget kullanılır.
GET /_mget
{
"docs": [
{
"_index": "calisan",
"_id": "1"
},
{
"_index": "calisan",
"_id": "2"
},
{
"_index": "adres",
"_id": "1"
},
{
"_index": "adres",
"_id": "2"
}
]
}
Sadece _source
bilgilerini çekmek için
GET /_mget?filter_path=docs._source
{
"docs": [
{
"_index": "calisan",
"_id": "1"
},
{
"_index": "calisan",
"_id": "2"
},
{
"_index": "adres",
"_id": "1"
},
{
"_index": "adres",
"_id": "2"
}
]
}
Tüm komutların gist hali:
_mapping İle Haritalamak
Tüm Endeksin Haritasını Çekmek
GET vtoalarm/_mapping
Bir Alanın Haritasını Çekmek
GET vtoalarm/_mapping/field/Level
Filtered Query
Artık filtered
sorguları yok! query
ile aynı hizada yer alan filtered
sorgularının işlevini artık query
içindeki bool
sorgularda yaşatıyoruz. Yani filtered artık yok!
QUERY
match
ile arama
customer_full_name
metin alanında tanımlı analizöre göre değer bilgisi token halinde ters endekslenerek belgeler kayıt edilmiştir. “Eddie Underwood” kelimelerinin bir arada olanları en yüksek puanla (score) sonuç listesinde en yukarıda yer alırken, sadece “Eddie” ve sadece “Underwood” kelimelerini bulduğu sonuçlar daha düşük puanla alt sıralarda yer alır.
match_phrase ile arama
Bu kez tam olarak geçmesi halinde sonuç listesinde görüntülenecektir aranan kelimeler. Yani Eddie Underwood kelimeleri yan yana olmak zorunda ancak bir cümlenin içinde olabilir.
Örnek 2:
GET kibana_sample_data_ecommerce/_search?filter_path=hits.total.value,hits.**._source
{
"_source": "products.product_name",
"query": {
"match_phrase": {
"products.product_name": "Midnight Blue"
}
}
}
match_all İle Her Şeyi, match_none İle Hiçbir Belgeyi Eşleştirmek
TERM Seviyesi Sorgular
Yapılandırılmış verilerdeki kesin değerlere dayalı belgeleri bulmak için terim düzeyinde sorguları kullanabilirsiniz. Yapılandırılmış veri örnekleri arasında tarih aralıkları, IP adresleri, fiyatlar veya ürün kimlikleri (id
) bulunur. Tam metin sorguların (full text search
) aksine, terim düzeyindeki sorgularda arama terimlerini analiz etmez. Bunun yerine, terim düzeyindeki sorgular, bir alanda depolanan terimlerle tam olarak eşleşir.
Uygulama ile gösterelim. Level
alanımız metin tipinde (text
) ve bu alan içinde terim seviyesinde süzgeçten geçirdiğimizde sonuçların gelmediğini görürüz çünkü terim seviyesi sorgular, metin tanımlı alanlar için KULLANILAMAZ!
Önce Level
alanının metin tipinde olduğunu görelim:
GET alarm_yeni/_mapping/field/Level
# {
# "alarm_yeni" : {
# "mappings" : {
# "Level" : {
# "full_name" : "Level",
# "mapping" : {
# "Level" : {
# "type" : "text",
# "fields" : {
# "keyword" : {
# "type" : "keyword",
# "ignore_above" : 256
# }
# },
# "fielddata" : true
# }
# }
# }
# }
# }
# }
Şimdi sorgumuz ve sonucun gelmediğini görelim:
GET alarm_yeni/_search
{
"size": 1,
"query": {
"bool": {
"filter": [
{
"term": {
"Level": "ALARM_MAJOR"
}
}
]
}
}
}# Bu sorguya dönecek sonuçları Level alanına göre gruplandırmak için cardinality kümeleme tipini de uygulayabiliriz ve sonuçların içinde Level alanında kaç farklı değerin geldiğini kova (bucket) içinde görebiliriz:GET alarm_yeni/_search
{
"size": 1,
"query": {
"bool": {
"filter": [
{
"term": {
"Level": "ALARM_MAJOR"
}
}
]
}
},
"aggs": {
"cardi_level": {
"cardinality": {
"field": "Level"
}
}
}
}# Sonuç gelmeyeceği için kümeleme işi de boş dönecektir. Çünkü Level alanı METİN TİPİNDE olduğu için TERM seviyesi sorgu işe yaramayacak!# {
# "took" : 2,
# "timed_out" : false,
# "_shards" : {...},
# "hits" : {
# "total" : {
# "value" : 0, <------- Hiç sonuç dönmedi
# "relation" : "eq"
# },
# "max_score" : null,
# "hits" : [ ] <------- Dönmedi diyoruz yahu
# },
# "aggregations" : {
# "cardi_level" : {
# "value" : 0 <------- Olmayan sonuçları nasıl kümelesin!
# }
# }
# }
Şimdi terim seviyesi sorguyu olması gerektiği gibi ID, rakam, anahtar kelime tipinde bir alana yapalım. Bunun için günlük seviyesi (log level
) veya bilgisayar adı (hostname
) alanları yani hep aynı değerleri alacak, metin alanı olmayan alanları anahtar kelime (keyword
) ile işaretleyerek yeni bir endeks oluşturup kayıtları bu endekse gönderelim (_reindex
başlığında örneği mevcut). Sonra sorgumuzu tekrar yeni endekste çalıştıralım:
# kümeleme sadece cardinality olsaydı kaç farklı değerin olduğunu
# rakamla göreceğiz
#
# Ancak terms ile kümeleme(aggs) yaparsak farklı değerlerin distinct
# halini yani tüm farklı değerleri görebileceğizGET alarm_yeni_2/_search
{
"size": 1,
"query": {
"bool": {
"filter": [
{ "term": { "Level": "ALARM_MAJOR" } }
]
}
},
"aggs": {
"card_level": {
"cardinality": { "field": "Level" }
},
"terms_level": {
"terms": {
"field": "Level",
"size": 10
}
}
}
}# Sonuçta 1 tane Level değeri küme içinde gelecek o da: ALARM_MAJOR
2602 Belgenin Level
değeri ALARM_MAJOR
ve sadece ALARM_MAJOR
getirildiği için kümeleme içinde 1 tane Level
alanı değeri gelecek.
term Sorguları
Önce sku
alanının metin (text
) tipinde olmadığından emin olmak için alan temelli mapping bilgisini çekelim:
GET kibana_sample_data_ecommerce/_mapping/field/sku
Tipinin date, id, keyword, rakam olduğunu görelim:
{
"kibana_sample_data_ecommerce" : {
"mappings" : {
"sku" : {
"full_name" : "sku",
"mapping" : {
"sku" : {
"type" : "keyword"
}
}
}
}
}
}
Şimdi belirli bir değer için sku
alanını sorgulayalım:
GET kibana_sample_data_ecommerce/_search
{
"query": {
"term": {
"sku": {
"value": "ZO0549605496"
}
}
}
}
Sonuçta iki belgenin döndüğünü göreceğiz:
terms Sorguları
Şimdi ise Level
alanına birden fazla değer için sorgu yazalım:
GET alarm_yeni_2/_search
{
"size": 1,
"query": {
"bool": {
"filter": [
{
"terms": {
"Level": [
"ALARM_CRITICAL",
"ALARM_MAJOR"
]
}
}
]
}
},
"aggs": {
"card_level": {
"cardinality": {
"field": "Level"
}
},
"terms_level": {
"terms": {
"field": "Level",
"size": 10
}
}
}
}
Zaten sorgumuz içinde sadece iki değeri süzerek sonuçları dönüyoruz. Dönen sonuçların içinde kümeleme yapınca doğal olarak bu iki değer (ALARM_MAJOR
ve ALARM_CRITICAL
) gözükecek.
null_value Boş değerli alanların endekslenmesi ve aranabilir olması
Boş bir değer (
null_value
) dizine eklenemez veya aranamaz. Bir alannull
olarak ayarlandığında (veya boş bir dizi veya bir boş değerler dizisi), o alanın hiç değeri yokmuş gibi değerlendirilir.
null_value
parametresi,null
değerleri belirtilen değerle değiştirmenize izin verir, böylece dizine eklenebilir (endekslenebilir) ve aranabilir._source içindeki bu değişimden etkilenmez sadece endeksleme ve arama yaparken değiştirmek için kullanacağımız değeri kullanabiliriz
null_value
değeri ile alanın değeri aynı veri türünde olmalıdır. Örneğin, text
türünde bir alanın null_value
değeri sayı olmamalı veya long
türünde bir alanın null_value
değerinin metin (string
) olmaması gibi.
Bir örnek daha:
# Önce varsa örneğimizdeki endeksi bir silelim
DELETE null_value_endeksi# Şimdi kod alanının null gelmesi halinde aranabilir
# ve endekslenebilir olması için null_value parametresiyle
# mapping'i değiştirelimPUT null_value_endeksi
{
"mappings": {
"properties": {
"adi": { "type": "text" },
"kod": {
"type" : "keyword",
"null_value": "CEMO"
}
}
}
}# Bir kayıt ekleyelim ve kod alanı boş olsunPUT null_value_endeksi/_doc/1
{
"adi":"Cem",
"kod": null
}# Sadece _source'a bakalım ve kod alanının değerinin değişmediğini görelimGET null_value_endeksi/_search?filter_path=**._source# kod alanı daima null gelir çünkü
# null_value ile _source'ta yer alan belgenin içindeki alanın
# değeri değişmez sadece endeksleme ve arama yapılabilir
# hale getirilir
#{
# "hits" : {
# "hits" : [
# {
# "_source" : {
# "adi" : "Cem",
# "kod" : null
# }
# }
# ]
# }
#}GET null_value_endeksi/_search
{
"query": {
"term": {
"kod": {
"value": "CEMO"
}
}
}
}# kod Hâlâ null ancak sorgumuzda kod alanı CEMO olanı getir dedik
# yani null_value da belirttiğimiz değeri kullanarak sorgulayabildik
#{
# "took" : 0,
# "timed_out" : false,
# "_shards" : {
# "total" : 1,
# "successful" : 1,
# "skipped" : 0,
# "failed" : 0
# },
# "hits" : {
# "total" : {
# "value" : 1,
# "relation" : "eq"
# },
# "max_score" : 0.2876821,
# "hits" : [
# {
# "_index" : "null_value_endeksi",
# "_type" : "_doc",
# "_id" : "1",
# "_score" : 0.2876821,
# "_source" : {
# "adi" : "Cem",
# "kod" : null
# }
# }
# ]
# }
#}
exists İle bol olmayan alanların belgelerini çekmek
null_value parametreli mapping belgeleri exists ile arayabiliriz
DELETE null_value_endeksiPUT null_value_endeksi
{
"mappings": {
"properties": {
"adi": {
"type": "text"
},
"soyadi": {
"type": "text"
},
"tcno": {
"type": "keyword"
},
"kod": {
"type": "keyword",
"null_value": "NULL-KOD_ALAN_DEGERI"
}
}
}
}GET null_value_endeksi/_mapping# kod Alanı boş gelirse NULL-KOD_ALAN_DEGERI değeriyle endeksler
# ve kod alanını NULL-KOD_ALAN_DEGERI değeriyle arayabiliriz
#{
# "null_value_endeksi" : {
# "mappings" : {
# "properties" : {
# "adi" : {
# "type" : "text"
# },
# "kod" : {
# "type" : "keyword",
# "null_value" : "NULL-KOD_ALAN_DEGERI"
# },
# "soyadi" : {
# "type" : "text"
# },
# "tcno" : {
# "type" : "keyword"
# }
# }
# }
# }
#}2 Belgeyi kaydedelimGET null_value_endeksi/_search
{
"query": {"match_all": {}}
}
# kod alanı daima null gelir çünkü
# null_value ile _source'ta yer alan belgenin içindeki alanın değeri değişmez
# sadece endeksleme ve arama yapılabilir hale getirilir
#
#...
# "hits" : [
# {
# "_index" : "null_value_endeksi",
# "_type" : "_doc",
# "_id" : "1",
# "_score" : 1.0,
# "_source" : {
# "adi" : "Cem",
# "kod" : null
# }
# },
# {
# "_index" : "null_value_endeksi",
# "_type" : "_doc",
# "_id" : "2",
# "_score" : 1.0,
# "_source" : {
# "adi" : "Cengo",
# "kod" : "CG007",
# "tcno" : "11111111111"
# }
# }
# ]
#...# Şimdi exists ile tcno alanında değer olan belgeleri getirelimGET null_value_endeksi/_search?filter_path=hits.hits._source
{
"query": {
"exists": {
"field": "tcno"
}
}
}
# Sadece Cengo belgesi gelecek çünkü Cem için tcno alanı boş (null)
# Ancak null_value parametresi verilmeden map edildiği için
# exists bize Cem belgesini dönmeyecekti
#...
# "hits" : [
# {
# "_source" : {
# "adi" : "Cengo",
# "kod" : "CG007",
# "tcno" : "11111111111"
# }
# }
# ]
#...# exists ile bu kez null_value parametreli kod alanını arayalımGET null_value_endeksi/_search?filter_path=hits.hits._source
{
"query": {
"exists": {
"field": "kod"
}
}
}
# kod Alanı null gelse bile null_value ile endekslendiği için
# exist bize Cem belgesini de dönecek
#...
# "hits" : [
# {
# "_source" : {
# "adi" : "Cem",
# "kod" : null
# }
# },
# {
# "_source" : {
# "adi" : "Cengo",
# "kod" : "CG007",
# "tcno" : "11111111111"
# }
# }
# ]
#...
must_not İle şartın (condition) tersini (değilini) sorgulayalım
exists
ve must_not
ile tcno
alanının değeri boş (null
) olmayan yani değeri olan belgeleri arayalım:
GET null_value_endeksi/_search
{
"query": {
"bool": {
"must_not": [
{
"exists": {
"field": "tcno"
}
}
]
}
}
}
# Sonuçta tcno alanı boş değer alabildiği için must_not+exists bize
# Cem belgesini dönecektir:
#...
# "hits" : [
# {
# "_index" : "null_value_endeksi",
# "_type" : "_doc",
# "_id" : "1",
# "_score" : 0.0,
# "_source" : {
# "adi" : "Cem",
# "kod" : null
# }
# }
# ]
#...
Küme İşlemleri (aggregation)
Metrik kümeleme işlemleri bize toplam, ortalama, en küçük, en büyük gibi istatistiki bilgileri verecektir. Birazdan metrik kümelemeyi göreceğiz ancak önce bir alanın aldığı farklı değerleri görelim (SQL’de distinct
gibi)
x İsimli alanda kaç farklı değer var? (aggs-<y>-terms-field:<x>
)
Kayıtlarımız içinde JSON nesnesi ve içinde severity
adlı bir alan mevcut. Bir hatanın ayrıntılarını Detail
nesnesi, hatanın şiddetini severity
alanı tutuyor. Metin tipinde değerleri olan alanlar için fielddata
tanımlanması gerekiyor. Birazdan daha ayrıntılı anlatılacak ama bir alıntıyla hafızamıza yazalım:
Metin alanları varsayılan olarak aranabilir (searchable) ancak varsayılan olarak toplamalar (aggregations), sıralama veya
script
ile erişilebilir durumda değillerdir. Metin alanındaki bir komut dosyasından (script
ile ) değerleri sıralamayı, toplamayı veya bunlara erişmeyi denerseniz, şu istisnayı görürsünüz:Fielddata is disabled on text fields by default. Set `fielddata=true` on
[`your_field_name`] in order to load fielddata in memory by uninverting the
inverted index. Note that this can however use significant memory.
Çözüm fielddata
ile bu metin alanlarını işaretlemek. Şimdi tekrar bir alanda kaç farklı değerin olduğunu bulmaya devam edelim:
PUT stoalarm/_mapping
{
"properties": {
"Detail.severity": {
"type": "text",
"fielddata": true
}
}
}
Şimdi sorgumuzu ve cevabını görelim:
GET stoalarm/_search?size=0&filter_path=aggregations
{
"aggs": {
"distinct_gibi_birsey": {
"terms": {
"field": "Detail.severity"
}
}
}
}# Metadata olmaksızın aggregations'ı görelim
# {
# "aggregations" : {
# "distinct_gibi_birsey" : {
# "doc_count_error_upper_bound" : 0,
# "sum_other_doc_count" : 0,
# "buckets" : [
# {
# "key" : "normal",
# "doc_count" : 10946
# },
# {
# "key" : "cleared",
# "doc_count" : 10263
# },
# {
# "key" : "major",
# "doc_count" : 20
# }
# ]
# }
# }
# }# GET stoalarm/_search?filter_path=aggregations.**.buckets.key
# Sadece terimleri görelim
#
# {
# "aggregations" : {
# "distinct_gibi_birsey" : {
# "buckets" : [
# {
# "key" : "normal"
# },
# {
# "key" : "cleared"
# },
# {
# "key" : "major"
# }
# ]
# }
# }
# }
Biraz ileri gideceğiz ama ileride tekrar değindiğimizde perçinlenecek. Aşağıdaki GET isteği; bir sorgu (QUERY
) ve bir kümeleme (AGGREGATION
) den oluşuyor. Kümeleme ise iki parçadan oluşuyor:
Level
alanında geçen farklı değerleri getiremterms aggregation
- Sonuçlar içinde
filters
ileALARM_MAJOR
veALARM_CRITICAL
ile eşleşenLevel
alanı değerlerinin belge sayısını getirir
Sorguda Detail
içindeki nfType
alanı AMF
değeri içerenleri getir diyoruz ve sadece iki sonucu görüntülüyoruz. Dikkat edin ALARM_NORMAL
ve ALARM_MAJOR
değeri olan iki sonuç geldi. Birazdan kümeleme kısmında ise filters ile ALARM_MAJOR
ve ALARM_CRITICAL
için istatistik işlemler yapabileceğiz ama sadece 7 belgenin ALARM_CRITICAL
ve 767 belgenin ALARM_MAJOR
içerdiğini göreceğiz.
Yani; aggregations
içindeki filter
ile sonuç kümesini !!!süzmüyor!!!, query
ile süzülen sonuçların içinde belirtilen alanın (field
) beklenen değeri içeren belge sayılarını görüyoruz.
Tire (hypen, dash) olunca neden kümelenmiyor?
Verimiz Detail
içinde bir GUID
değer içeren nfInstanceId
isimli alan olsun. Bu alanda veri tireli tutuluyor olsun:
Önce başarısız olacak sorgumuzu yapalım ve sonuçları görelim:
GET stoalarm/_search?filter_path=aggregations.**.buckets.key
{
"aggs": {
"distinct_gibi_birsey": {
"terms": {
"field": "Detail.nfInstanceId"
}
}
}
}
Sonuçlar, standart analizörün “tireler arasındaki her şeyi” metin gibi algılamasındandır. Standart analizör zannediyorki bu tireler aslında kelimeleri bölüyor: use-case
, computer-aided
, sugar-free
gibi.
{
"aggregations" : {
"distinct_gibi_birsey" : {
"buckets" : [
{ "key" : "44bf"
}, { "key" : "49a4534c"
}, { "key" : "7750556739aa"
}, { "key" : "81b7"
}, { "key" : "90bb"
}, { "key" : "3fd52bef206c"
}, { "key" : "4a0e"
}, { "key" : "6db2ef70"
}, { "key" : "82b5"
}, { "key" : "ad34"
}
]
}
}
}
Bakın standard
analizör bizim değeri nasıl anlıyor?
POST _analyze
{
"analyzer": "standard",
"text": "49a4534c-90bb-44bf-81b7-7750556739aa"
}# Tire karakterleriyle kelimelerin ayrıldığını düşünüyor...
# Ayrıca her kelime diye ayrıştırdığı token
# hem alpha hem nümerik veri içeriyor diye ALPHANUM tipini atıyor
#{
# "tokens" : [
# {
# "token" : "49a4534c",
# "start_offset" : 0,
# "end_offset" : 8,
# "type" : "<ALPHANUM>",
# "position" : 0
# },
# {
# "token" : "90bb",
# "start_offset" : 9,
# "end_offset" : 13,
# "type" : "<ALPHANUM>",
# "position" : 1
# },
# {
# "token" : "44bf",
# "start_offset" : 14,
# "end_offset" : 18,
# "type" : "<ALPHANUM>",
# "position" : 2
# },
# {
# "token" : "81b7",
# "start_offset" : 19,
# "end_offset" : 23,
# "type" : "<ALPHANUM>",
# "position" : 3
# },
# {
# "token" : "7750556739aa",
# "start_offset" : 24,
# "end_offset" : 36,
# "type" : "<ALPHANUM>",
# "position" : 4
# }
# ]
#}
Şimdide whitespace
analizörüyle incelemeden önce dinamik haritalanması için bir kayıt girelim :
Dinamik haritalama ve Standard Analizör Kullanıldığını Görelim
PUT test_alarm/_doc/1
{
"Detail": {
"alarmId": "68c569d6-bfb3-420d-8b8c-0657af4883f4"
}
}
GET test_alarm/_mapping# Dinamik haritalandı ve işte sonuç:
#{
# "test_alarm" : {...............
# "mappings" : { :
# "properties" : { :
# "Detail" : {............:
# "properties" : { :
# "alarmId" : {.......: test_alarm/Detail.alarmId
# "type" : "text",
# "fields" : {
# "keyword" : {
# "type" : "keyword",
# "ignore_above" : 256
# }
Şimdi standard analizör ile yukarıdaki örnekte tireden dolayı yaşadıklarımızı bir daha görelim.
Önce fielddata: true
olmadığı için şu hatayı alacağız:
Düzeltmek için inverted index ile endekslenen bir alan olmaktan çıkaralım alarmId
alanını:
PUT test_alarm/_mapping
{
"properties": {
"Detail.alarmId": {
"type": "text",
"fielddata": true,
### "analyzer": "whitespace" > ancak index yaratılırken ayarlanır!!
}
}
}
İşte tirelerin arasındaki her bir token sanki kelime gibi algılanıyor:
Tamam olmayacaktı çünkü dinamik haritalama standard analyzer
kullanıyordu. Şimdi elle haritalayarak veri girelim çünkü daha önceden yaratılmış bir endeks için analyzer
güncellenemez !
Elle haritalama ve Analizör olarak whitespace Kullanalım
Analizör biraz teferreuatlı ve elasticsearch’ün nasıl çalıştığına giren bir başlık ama burada kısaca “ayrıştırma sürecinin yönetilmesi” diyerek devam edebiliriz.
whitespace
Analizörü tireli metnimizi nasıl parçalıyor ona bir bakalım:
POST _analyze
{
"analyzer": "whitespace",
"text": "49a4534c-90bb-44bf-81b7-7750556739aa"
}# Sonuçta tireler değil boşluk karakterleri ile kelimeler bölünüyor
#{
# "tokens" : [
# {
# "token" : "49a4534c-90bb-44bf-81b7-7750556739aa",
# "start_offset" : 0,
# "end_offset" : 36,
# "type" : "word",
# "position" : 0
# }
# ]
#}
Artık indeksimizi oluştururken analizörü de verelim:
PUT test_alarm
{
"mappings": {
"properties": {
"Detail": {
"properties": {
"alarmId": {
"type": "text",
"analyzer": "whitespace",
"fielddata": true,
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}GET test_alarm/_mapping# Elle ayarlarını yapıyoruz ama bu kez analyzer olarak whitespace
# ataması yapıyoruz
#
#{
# "test_alarm" : {
# "mappings" : {
# "properties" : {
# "Detail" : {
# "properties" : {
# "alarmId" : {
# "type" : "text",
# "fields" : {
# "keyword" : {
# "type" : "keyword",
# "ignore_above" : 256
# }
# },
# "analyzer" : "whitespace",
# "fielddata" : true
# }
Endeksimiz hazır ve bir kayıt ekleyelim:
PUT test_alarm/_doc/1
{
"Detail": {
"alarmId": "68c569d6-bfb3-420d-8b8c-0657af4883f4"
}
}
Artık alarmId alanında kümelemeyi çalıştırabiliriz:
GET test_alarm/_search?filter_path=aggregations.**.buckets
{
"aggs": {
"distinct_gibi_birsey": {
"terms": {
"field": "Detail.alarmId"
}
}
}
}
Çıktımızda tire karakterlerini (hyphen) değil artık boşluk (whitespace) karakterini ayrıştırmak için kullandığımızı göreceğiz:
{
"aggregations" : {
"distinct_gibi_birsey" : {
"buckets" : [
{
"key" : "68c569d6-bfb3-420d-8b8c-0657af4883f4",
"doc_count" : 1
}
]
}
}
}
Tekrar hatırlayalım: Analizörü whitespace
olarak değiştirmemiz gerekiyor ancak verinin olduğu bir endeks üstünde bunu gerçekleştiremeyiz! Bu yüzden yeni bir endeks yaratıp _reindex
ile veriyi aktarabiliriz.
_reindex İle Eski Endeksten Yenisine Veri Aktarmak
Verilerimizin kaynağından sadece Detail.alarmId
alanının değerini taşımak istiyoruz.
Sonuçlarda tüm _source
yerine belirli alanların olmasını istiyorsak ya querystring
olarak URL
içinde belirtiyor ya da isteğin gövdesine _source
alanına dizinin elemanları olarak yazıyoruz:
GET stoalarm/_search?size=100
{
"_source": [ "Detail.alarmId", "Level" ]
}
Tüm kayıtları çekmek zorunda değilsek size
ile istediğimiz sayıda, query
arama sonuçlarımızı kaynaktan çekebiliriz.
POST _reindex
{
"source": {
"index": "stoalarm",
"size": 10,
"_source": [ "Detail.alarmId" ],
"query": {
"match_all": {}
}
},
"dest": {
"index": "test_alarm"
}
}
Bir örnek daha:
Önce ID olan alanları veya sürekli aynı değerleri alacak alanları keyword olarak tanımlayalım ve bu kez yeni endeks adımız alarm_yeni_2 olsun
PUT alarm_yeni_2
{
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"Detail": {
"properties": {
"alarmClearedTime": {
"type": "date"
},
"alarmId": {
"type": "keyword"
},
"alarmNature": {
"type": "text"
},
"alarmRaisedTime": {
"type": "date"
},
"alarmType": {
"type": "keyword"
},
"cause": {
"type": "text"
},
"dateTime": {
"type": "date"
},
"eventType": {
"type": "text"
},
"info": {
"type": "text"
},
"nfInstanceId": {
"type": "keyword"
},
"nfType": {
"type": "keyword"
},
"parameters": {
"type": "text"
},
"perceivedSeverity": {
"type": "text"
},
"probableCause": {
"type": "text"
},
"serviceInstanceId": {
"type": "keyword"
},
"serviceName": {
"type": "text"
},
"serviceinstanceid": {
"type": "keyword"
},
"severity": {
"type": "text",
"fielddata": true
},
"snssai": {
"properties": {
"sd": {
"type": "text"
},
"sst": {
"type": "long"
}
}
}
}
},
"File": {
"type": "text"
},
"ID": {
"type": "text"
},
"Level": {
"type": "keyword"
},
"Log_Time": {
"type": "text"
},
"Messages": {
"type": "text"
},
"hostname": {
"type": "keyword"
}
}
}
}
Ve tekrar _reindex ile önceki endeksin verilerini yeni endekse taşıyalım
POST _reindex
{
"source": {
"index": "alarm_yeni",
"size": 10,
"query": {
"match_all": {}
}
},
"dest": {
"index": "alarm_yeni_2"
}
}
Metrik Kümeleme
Şimdi biraz aggs
nasıl yazılıyor, metrik sonuçları nasıl alabiliyoruz görelim:
GET /<endeks adı>/_search[?ekparametreler]
{
"aggs": {
"<küme_adını_ne_isterseniz>": {
"<avg|sum|count|min...>": {
"field": "<hangi alan üstünde uygulansın>"
}
}
}
}
Yapı yukarıdaki gibi. “aggS”
Adına dikkat ederseniz birden fazla tanımladığınız kümeyi alabileceğini ancak bir dizi değil, objenin yeni alanı olarak yazabileceğimizi devamında görüyoruz.
Aşağıdaki sorguda sonuçlardan keyfi olarak 3'ünü (size=3) ve bu 3 sonucun {adi,yasi,cinsiyeti}
bilgileriyle birlikte “ortalama kümesinin sonucu” getireceğiz.
GET /ogrenci/_search?size=3&_source=adi,yasi,cinsiyeti
{
"aggs": {
"ortalama-yas": {
"avg": {
"field": "yas"
}
}
}
}
Eğer yaş alanı üstünde sadece avg
değil tüm istatistiki sonuçları (avg
, min
, max
, sum
) sonuçlarını bir kerede almak istersek:
GET /ogrenci/_search?size=3&_source=adi,yasi,cinsiyeti
{
"aggs": {
"tum-istatistikler-yas": {
"stats": {
"field": "yas"
}
}
}
}
Bu sorguya ek olarak dilerseniz cinsiyeti kadın olan öğrencileri süzerek ortalamayı getirebiliriz:
GET /ogrenci/_search?size=3&_source=adi,yasi,cinsiyeti
{
"aggs": {
"ortalama-yas": {
"avg": {
"field": "yas"
}
}
}
}
Örnek verimiz aşağıdaki gibi olsun ve sorgularımızı çalıştıralım:
POST _bulk
{"index":{"_index":"test","_id":"1"}}
{"index":0,"guid":"ba145b3d-8e78-43eb-9359-3c2b15777888","isActive":true,"balance":"$1,578.32","picture":"http://placehold.it/32x32","age":27,"eyeColor":"brown","name":"Alice Reeves","gender":"female","company":"UTARIAN","email":"alicereeves@utarian.com","phone":"+1 (876) 570-2846","address":"532 Vernon Avenue, Brewster, Minnesota, 9098","about":"Enim ullamco nostrud incididunt laboris exercitation nostrud commodo do id mollit. Cillum labore pariatur magna labore veniam anim. Consectetur id magna ullamco excepteur id mollit excepteur eu sunt Lorem velit tempor consequat id. Minim minim exercitation irure esse.\r\n","registered":"2019-02-22T07:19:54 -03:00","latitude":-37.041271,"longitude":-87.401533,"tags":["ad","occaecat","enim","sint","reprehenderit","aute","incididunt"],"friends":[{"id":0,"name":"Keller Blair"},{"id":1,"name":"Austin Murphy"},{"id":2,"name":"Levine Goodman"}]}
{"index":{"_index":"test","_id":"2"}}
{"index":1,"guid":"bb2653e9-411e-49b4-b6f7-9f60d6042a83","isActive":true,"balance":"$3,670.41","picture":"http://placehold.it/32x32","age":29,"eyeColor":"blue","name":"Riddle Aguilar","gender":"male","company":"FOSSIEL","email":"riddleaguilar@fossiel.com","phone":"+1 (848) 600-3335","address":"948 Battery Avenue, Chilton, Pennsylvania, 9114","about":"Laborum ut commodo consequat occaecat mollit adipisicing sint ipsum. In incididunt amet irure do. Ex voluptate id veniam proident. Labore aliquip aute voluptate ad deserunt. In mollit nostrud ad aute consequat. Occaecat dolore sunt elit deserunt velit labore eiusmod in commodo culpa quis cillum.\r\n","registered":"2015-03-01T07:17:12 -02:00","latitude":9.120384,"longitude":-110.077147,"tags":["excepteur","sunt","enim","magna","pariatur","incididunt","excepteur"],"friends":[{"id":0,"name":"Hudson Frye"},{"id":1,"name":"Bowen Jarvis"},{"id":2,"name":"Ingrid Lloyd"}]}
{"index":{"_index":"test","_id":"3"}}
{"index":2,"guid":"fd436495-28b9-4273-a67b-fb57c9e821e3","isActive":false,"balance":"$3,752.27","picture":"http://placehold.it/32x32","age":40,"eyeColor":"brown","name":"Huber Sanders","gender":"male","company":"AUTOMON","email":"hubersanders@automon.com","phone":"+1 (911) 442-2789","address":"472 Stone Avenue, Remington, West Virginia, 7795","about":"Qui consectetur non pariatur amet. Cupidatat dolore sint nostrud est velit anim ullamco consectetur magna. Deserunt dolor excepteur eiusmod cillum.\r\n","registered":"2019-10-19T01:38:39 -03:00","latitude":11.114887,"longitude":117.706685,"tags":["tempor","pariatur","aute","Lorem","sit","veniam","elit"],"friends":[{"id":0,"name":"James Carter"},{"id":1,"name":"Casandra Suarez"},{"id":2,"name":"Nguyen Kelley"}]}
{"index":{"_index":"test","_id":"4"}}
{"index":3,"guid":"2d954589-cdd4-4bdb-b8c5-d875c76a1c8d","isActive":false,"balance":"$1,561.78","picture":"http://placehold.it/32x32","age":37,"eyeColor":"brown","name":"Mercado Hurst","gender":"male","company":"CUJO","email":"mercadohurst@cujo.com","phone":"+1 (969) 517-2509","address":"377 Junius Street, Chamizal, Maryland, 3699","about":"Id ut cupidatat sunt dolor sint eiusmod ea consectetur. Do cillum magna magna velit non velit ipsum veniam sint ullamco consectetur est. Mollit elit cillum labore non magna duis eiusmod esse minim pariatur ex deserunt.\r\n","registered":"2016-09-01T03:00:38 -03:00","latitude":-2.577622,"longitude":174.042788,"tags":["proident","dolore","excepteur","sit","voluptate","minim","magna"],"friends":[{"id":0,"name":"Mari Bass"},{"id":1,"name":"Andrea Mcintyre"},{"id":2,"name":"Cardenas Mcconnell"}]}
{"index":{"_index":"test","_id":"5"}}
{"index":4,"guid":"423439f1-494e-4bb6-abd2-110f0a44b913","isActive":true,"balance":"$1,219.04","picture":"http://placehold.it/32x32","age":24,"eyeColor":"green","name":"Rosario Strong","gender":"male","company":"NETILITY","email":"rosariostrong@netility.com","phone":"+1 (811) 518-2287","address":"655 McKibbin Street, Konterra, Louisiana, 3997","about":"Do pariatur ipsum deserunt labore. Nostrud commodo in quis incididunt. Velit non tempor tempor exercitation elit adipisicing sit consequat cillum excepteur Lorem eu cillum. Fugiat esse laborum occaecat nostrud irure pariatur consequat nisi eiusmod esse magna voluptate deserunt. Occaecat sunt sit nostrud eu.\r\n","registered":"2018-12-15T11:31:09 -03:00","latitude":20.851648,"longitude":37.538853,"tags":["aliqua","adipisicing","ex","anim","duis","in","fugiat"],"friends":[{"id":0,"name":"Avila Parks"},{"id":1,"name":"Larsen Moran"},{"id":2,"name":"Kennedy Owen"}]}
Tüm kayıtlarda ortalama yaşı bulalım:
GET /test/_search?size=1&_source=age,name
{
"aggs": {
"ortalama-yas": {
"avg": {
"field": "age"
}
}
}
}
Sadece erkeklerin ortalama yaşını bulalım:
Dikkat:
query
içindebool->filter
çıktısını kümelemeye taşıyor olduğumuzuquery
ileaggs
alanlarının aynı hizada
GET /test/_search?size=1&_source=age,name,gender
{
"query": {
"bool": {
"filter": [
{
"term": {
"gender": "male"
}
}
]
}
},
"aggs": {
"ortalama-yas": {
"avg": {
"field": "age"
}
}
}
}
Tüm istatistik çıktıları görmek için “stats”
Hangi alan üstünde görmek istiyorsak stats
ile tüm temel istatistik çıktılarını görebiliriz:
Dizi tipinde bir alanın değerlerinde veya basit bir alanın alabileceği değerlere göre süzmek:
tags
Alanında sit
, excepteur
alanları olan kayıtları süz ve age
alanına göre ortalama al. Bunun için terms
kullanacağız :
GET /test/_search?size=1&_source=age,name,tags
{
"query": {
"terms": {
"tags": [
"excepteur",
"sit"
]
}
},
"aggs": {
"ortalama-yas": {
"avg": {
"field": "age"
}
}
}
}
fielddata İle Gruplama
Arama (search) ve kümeleme (aggregation veya toplama) yaparken aradaki farkı anlamamız gerekiyor.
Elasticsearch, çok hızlı tam metin aramalarını destekleyen, tersine çevrilmiş dizin adı verilen bir veri yapısı kullanır. Tersine çevrilmiş bir dizin, herhangi bir belgede görünen her benzersiz sözcüğü listeler ve her sözcüğün geçtiği tüm belgeleri tanımlar.
Bir dizin, optimize edilmiş bir belge koleksiyonu olarak düşünülebilir ve her belge, verilerinizi içeren anahtar/değer çiftleri olan bir alan koleksiyonudur. Varsayılan olarak, Elasticsearch her alandaki tüm verileri indeksler ve indekslenmiş her alan, özel, optimize edilmiş bir veri yapısına sahiptir. Örneğin, metin alanları ters çevrilmiş endekslerde saklanır ve sayısal ve coğrafi alanlar BKD ağaçlarında depolanır. Elasticsearch’ü bu kadar hızlı yapan şey, arama sonuçlarını bir araya getirmek ve döndürmek için alan başına veri yapılarını kullanma becerisidir. ***
Okumaya devam ettiğinizde (Google Türkçesi de kifayet eder) göreceksiniz ki adamlar “verinizi siz bizden iyi tanıyorsunuz, dinamik olarak verinizi anlamamız yerine bize verinizin yapısını verirseniz daha başarılır oluruz” diyor.
Nihayetinde, verileriniz ve onu nasıl kullanmak istediğiniz hakkında Elasticsearch’ün yapabileceğinden daha fazla şey biliyorsunuz. Dinamik eşlemeyi kontrol etmek için kurallar tanımlayabilir ve alanların nasıl depolandığını ve dizine eklendiğini tam olarak kontrol etmek için eşlemeleri açıkça tanımlayabilirsiniz.
Arama (search
) yaparken (tam metin araması — full text search
) belgelerdeki kelimeler hash değerler olarak, tersine çevrilmiş dizin (inverted index
) içinden bakılarak bulunuyor. Aşağıdaki gösterimde 3 belge içindeki kelimelerin stopword
diye tanımlı “anahtar kelime” kabul edilemeyecekleri hariç endeksleniyor (dizinleniyor). Her kelimenin, hangi belgede ve ne sıklıkla (frekans) geçtiği dizinleniyor/endekleniyor.
Arama yaptığımız kelimelerin geçtiği belgeler bulunup, kelimenin belgede geçiş sıklığına, aranan kelimelerin verildiği sırada mı yoksa farklı yerlerde olduklarına bakılarak bir puanlama (score
) yapılarak en yüksek puanlı belgeyi en üstte olacak şekilde sonuç listesi dönüyor Elasticsearch.
Ancak sıralama veya kümeleme/toplama (aggregation
) yaparken ise kelimeleri olduğu gibi kullanıyor ve tersine çevrilmiş endeksleri değil!
Farklı amaçlar için aynı alanı farklı şekillerde dizine eklemek genellikle yararlıdır. Örneğin, bir dize alanını hem tam metin araması için bir metin alanı hem de verilerinizi sıralamak veya toplamak için bir anahtar sözcük alanı olarak dizine eklemek isteyebilirsiniz.
Level
alanına göre kümeleme yapmak istiyorum.
GET vtoalarm/_search
{
"query": {
"range": {
"@timestamp": {
"gte": "now-10d/d",
"lt": "now/d"
}
}
},
"aggs": {
"Level-Gruplama": {
"terms": {
"field": "Level"
}
}
}
}
Ancak aldığım hata mesajı:
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [Level] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead."
}
],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"phase": "query",
"grouped": true,
"failed_shards": [
{
"shard": 0,
"index": "vtoalarm",
"node": "PMpVc1cbTMOoJy24Q7EfDg",
"reason": {
"type": "illegal_argument_exception",
"reason": "..yukarıdakiyle aynı..."
}
}
],
"caused_by": {
"type": "illegal_argument_exception",
"reason": "..yukarıdakiyle aynı...",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "..yukarıdakiyle aynı..."
}
}
},
"status": 400
}
Önce Level alanının haritalanmasına bakalım:
GET vtoalarm/_mapping/field/Level
Level alanı:
{
"vtoalarm" : {
"mappings" : {
"Level" : {
"full_name" : "Level",
"mapping" : {
"Level" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}
Level
alanında fielddata
özelliğini aktif etmek gerekecek.
PUT vtoalarm/_mapping
{
"properties": {
"Level": {
"type": "text",
"fielddata": true
}
}
}
Tekrar Level alanının haritasına bakalım:
GET vtoalarm/_mapping/field/Level{
"vtoalarm" : {
"mappings" : {
"Level" : {
"full_name" : "Level",
"mapping" : {
"Level" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword"
}
},
"fielddata" : true
}
}
}
}
}
}
Şimdi tekrar yukarıdaki kümeleme sorgumuzu çalıştırdığımızda:
{“query”:{“range”:{“@timestamp”:{“gte”:”now-10d/d”,”lt”:”now/d”}}},”aggs”:{“Level-Gruplama”:{“terms”:{“field”:”Level”}}}}
Hepsini bir örnekte görelim:
# eyeColor Alanına göre gruplayarak kaç farklı göz rengi olduğunu görelimGET test/_search
{
"size":0,
"aggs": {
"eyeColor_alanina_gore_essiz_degerler": {
"cardinality": {
"field": "eyeColor"
}
}
}
}# "reason" : "Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [eyeColor] in order to load field data by uninverting the inverted index. Note that this can use significant memory."GET test/_mapping/field/eyeColor
# >>> Çıktı:
# {
# "test" : {
# "mappings" : {
# "eyeColor" : {
# "full_name" : "eyeColor",
# "mapping" : {
# "eyeColor" : {
# "type" : "text",
# "fields" : {
# "keyword" : {
# "type" : "keyword",
# "ignore_above" : 256# metin alanı olduğu için gruplanamıyor
# tekrar **fielddata** ile haritalayalım
PUT test/_mapping
{
"properties": {
"eyeColor": {
"type": "text",
"fielddata": true
}
}
}
# >>> Çıktı:
# { "acknowledged" : true }GET test/_mapping/field/eyeColor
# >>> Çıktı:
# {
# "test" : {
# "mappings" : {
# "eyeColor" : {
# "full_name" : "eyeColor",
# "mapping" : {
# "eyeColor" : {
# "type" : "text",
# "fields" : {
# "keyword" : {
# "type" : "keyword",
# "ignore_above" : 256
# }
# },
# "fielddata" : trueGET test/_search?filter_path=hits.total,hits.hits._source.eyeColor,aggregations
{
"size":1,
"aggs": {
"eyeColor_alanina_gore_essiz_degerler": {
"cardinality": {
"field": "eyeColor"
}
}
}
}# 343 Kayıt içinde 3 eyeColor değeri kullanılıyor (brown, green, blue)
#
# {
# "hits" : {
# "total" : {
# "value" : 343,
# "relation" : "eq"
# },
# "hits" : [
# {
# "_source" : {
# "eyeColor" : "brown"
# }
# }
# ]
# },
# "aggregations" : {
# "eyeColor_alanina_gore_essiz_degerler" : {
# "value" : 3
# }
# }
# }
Bir diğer örnek:
# Metin alanları belleğin heap bölümünü işgal ettiği için ön tanımlı olara
# fielddata=false geliyor tüm metin alanları için.
# Bu yüzden istediğimiz alanın mappings ile özelliğini değiştiriyoruz.
PUT test/_mapping
{
"properties": {
"gender": {
"type": "text",
"fielddata": true
}
}
}GET test/_search
{
"size": 0,
"aggs": {
"gender_aggs": {
"cardinality": {
"field": "gender"
}
}
}
}# Female ve Male olarak iki farklı değer dönüyor
# {
# "took" : 10,
# "timed_out" : false,
# "_shards" : {
# "total" : 1,
# "successful" : 1,
# "skipped" : 0,
# "failed" : 0
# },
# "hits" : {
# "total" : {
# "value" : 343,
# "relation" : "eq"
# },
# "max_score" : null,
# "hits" : [ ]
# },
# "aggregations" : {
# "gender_aggs" : {
# "value" : 2
# }
# }
# }
size İle 0–10 Arasında Gelecek Sonuçların Sayısı Getirme
Varsayılan olarak 10 kayıt getirir ancak size ile 0–10 arasında “hiç sonuç getirme” veya “10'dan az getir” denilebilir.
Hiç Sonuç Getirme, Verinin Verisini (metadata) Getir
GET vtoalarm/_search
{
"query": {
"range": {
"@timestamp": {
"gte": "now-10d/d",
"lt": "now/d"
}
}
},
"size": 0,
"aggs": {
"Level-Gruplama": {
"terms": {
"field": "Level"
}
}
}
}
Bucket — Kova
# Hangi cinsiyetten kaç kişi kayıtlı diye bakmak istediğimizde
# kova (bucket) ile çözüyor olacağız
# Bu kez AGGS içinde TERMS ile hangi FIELD için yazacağımızı belirliyoruz
GET test/_search
{
"size": 0,
"aggs": {
"gender_kovası": {
"terms": {
"field": "gender"
}
}
}
}# Erkek: 164, Kadın: 179
# {
# "took" : 19,
# "timed_out" : false,
# "_shards" : {
# "total" : 1,
# "successful" : 1,
# "skipped" : 0,
# "failed" : 0
# },
# "hits" : {
# "total" : {
# "value" : 343,
# "relation" : "eq"
# },
# "max_score" : null,
# "hits" : [ ]
# },
# "aggregations" : {
# "gender_kovası" : {
# "doc_count_error_upper_bound" : 0,
# "sum_other_doc_count" : 0,
# "buckets" : [
# {
# "key" : "female",
# "doc_count" : 179
# },
# {
# "key" : "male",
# "doc_count" : 164
# }
# ]
# }
# }
# }
#
Aralık İçerisindeki Değerler (range)
GET test/_search
{
"size": 0,
"aggs": {
"yas_araliklari": {
"range": {
"field": "age",
"ranges": [
{ "to": 20 },
{ "from": 20, "to": 30 },
{ "from": 30, "to": 40 },
{ "from": 40 }
]
}
}
}
}# Aralık içeren kova oluşturyoruz
# {
# "took" : 9,
# "timed_out" : false,
# "_shards" : {
# "total" : 1,
# "successful" : 1,
# "skipped" : 0,
# "failed" : 0
# },
# "hits" : {
# "total" : {
# "value" : 343,
# "relation" : "eq"
# },
# "max_score" : null,
# "hits" : [ ]
# },
# "aggregations" : {
# "yas_araliklari" : {
# "buckets" : [
# {
# "key" : "*-20.0",
# "to" : 20.0,
# "doc_count" : 0
# },
# {
# "key" : "20.0-30.0",
# "from" : 20.0,
# "to" : 30.0,
# "doc_count" : 164
# },
# {
# "key" : "30.0-40.0",
# "from" : 30.0,
# "to" : 40.0,
# "doc_count" : 162
# },
# {
# "key" : "40.0-*",
# "from" : 40.0,
# "doc_count" : 17
# }
# ]
# }
# }
Yukarıdaki aralık değerlerini dizi olarak alıyoruz ancak eğer bir nesnenin alanları olarak almak istersek keyed: true
diyerek işletiyoruz:
GET test/_search
{
"size": 0,
"aggs": {
"yas_araliklari": {
"range": {
"field": "age",
"keyed": true,
"ranges": [
{ "to": 20 },
{ "from": 20, "to": 30 },
{ "from": 30, "to": 40 },
{ "from": 40 }
]
}
}
}
}{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 343,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"yas_araliklari" : {
"buckets" : {
"*-20.0" : { "to" : 20.0, "doc_count" : 0 },
"20.0-30.0" : {
"from" : 20.0, "to" : 30.0, "doc_count" : 164
},
"30.0-40.0" : {
"from" : 30.0, "to" : 40.0, "doc_count" : 162
},
"40.0-*" : { "from" : 40.0, "doc_count" : 17 }
}
}
}
}
Takma adlarla sonuç gruplarını isimlendirmek
GET test/_search
{
"size": 0,
"aggs": {
"yas_araliklari": {
"range": {
"field": "age",
"keyed": true, # keyed:true olmazsa aşağıdaki çalışmaz
"ranges": [
{ "key": "genç", "to": 20 },
{ "key": "çalışan", "from": 20, "to": 30 },
{ "key": "orta-yaş", "from": 30, "to": 40 },
{ "key": "yaşlı", "from": 40 }
]
}
}
}
}
Sonuçta grupların isimleri var:
...,
"aggregations" : {
"yas_araliklari" : {
"buckets" : {
"genç" : { "to" : 20.0, "doc_count" : 0 },
"çalışan" : { "from" : 20.0, "to" : 30.0, "doc_count" : 164 },
"orta-yaş" : { "from" : 30.0, "to" : 40.0, "doc_count" : 162 },
"yaşlı" : { "from" : 40.0, "doc_count" : 17 }
}
}
}...
Gruplamanın üstünden metrik işlemler:
GET test/_search
{
"size": 0,
"aggs": {
"cinsiyet_kovası": {
"terms": {
"field": "gender"
},
"aggs": {
"yas_ortalamasi": {
"avg": {
"field": "age"
}
}
}
}
}
}# Sonuç olarak
# 179 kadının yaş ortalaması: ~30
# 164 erkeğin yaş ortalaması: ~29
#"aggregations" : {
# "cinsiyet_kovası" : {
# "doc_count_error_upper_bound" : 0,
# "sum_other_doc_count" : 0,
# "buckets" : [
# {
# "key" : "female",
# "doc_count" : 179,
# "yas_ortalamasi" : {
# "value" : 30.1731843575419
# }
# },
# {
# "key" : "male",
# "doc_count" : 164,
# "yas_ortalamasi" : {
# "value" : 29.445121951219512
# }
# }
# ]
# }
#}
iç içe kümeleme (nested aggregations
)
Grupladığımız sonuçların yaş ortalamasını bulalım :
GET test/_search
{
"size": 0,
"aggs": {
"cinsiyet_kovası": {
"terms": { "field": "gender" },
"aggs": {
"yas_araliklari": {
"range": {
"field": "age",
"keyed": true,
"ranges": [
{ "key": "genç", "to": 20 },
{ "key": "çalışan", "from": 20, "to": 30 },
{ "key": "orta-yaş", "from": 30, "to": 40 },
{ "key": "yaşlı", "from": 40 }
]
},
"aggs": {
"yas_ortalamasi": {
"avg": { "field": "age" }
}
}
}
}
}
}
}# "aggregations" : {
# "cinsiyet_kovası" : {
# "doc_count_error_upper_bound" : 0,
# "sum_other_doc_count" : 0,
# "buckets" : [
# {
# "key" : "female",
# "doc_count" : 179,
# "yas_araliklari" : {
# "buckets" : {
# "genç" : {
# "to" : 20.0,
# "doc_count" : 0,
# "yas_ortalamasi" : {
# "value" : null
# }
# },
# "çalışan" : {
# "from" : 20.0,
# "to" : 30.0,
# "doc_count" : 81,
# "yas_ortalamasi" : {
# "value" : 24.506172839506174
# }
# },
# "orta-yaş" : {
# "from" : 30.0,
# "to" : 40.0,
# "doc_count" : 90,
# "yas_ortalamasi" : {
# "value" : 34.4
# }
# },
# "yaşlı" : {
# "from" : 40.0,
# "doc_count" : 8,
# "yas_ortalamasi" : {
# "value" : 40.0
# }
# }
# }
# }
# },
# {
# "key" : "male",
# "doc_count" : 164,
# "yas_araliklari" : {
# "buckets" : {
# "genç" : {
# "to" : 20.0,
# "doc_count" : 0,
# "yas_ortalamasi" : {
# "value" : null
# }
# },
# "çalışan" : {
# "from" : 20.0,
# "to" : 30.0,
# "doc_count" : 83,
# "yas_ortalamasi" : {
# "value" : 23.93975903614458
# }
# },
# "orta-yaş" : {
# "from" : 30.0,
# "to" : 40.0,
# "doc_count" : 72,
# "yas_ortalamasi" : {
# "value" : 34.47222222222222
# }
# },
# "yaşlı" : {
# "from" : 40.0,
# "doc_count" : 9,
# "yas_ortalamasi" : {
# "value" : 40.0
# }
# }
# }
# }
# }
# ]
# }
# }
Kümelerin içinde süzmek (filters)
Aşağıdaki sorguda dikkat edeceğimiz noktalar:
aggs
ile bir toplama/kümeleme yapıyoruz vefilters
ile istediğimiz kadar istediğimiz alanda istediğimiz içeriğe göre grupluyoruz.- Çıktılara bakarsanız içinde
ALARM_MAJOR
veALARM_CRITICAL
olan olmayan hepsi var. Çünküquery
ile arama yapmıyor ve çıktıları dilediğimiz şartlara göre süzmüyoruz. Yani kümeleme içindekifilters
bize sonuçları süzmez, sonuçlar içinde (query ile arama yapılan ve filters ile içinde süzülen sonuçlar içinde) kümeleme yapar.
GET stoalarm/_search?_source=Detail.nfInstanceId,_id
{
"size": 1,
"aggs": {
"alarm_major": {
"filters": {
"filters": {
"major": { "match": { "Level": "ALARM_MAJOR" } },
"critical": { "match": { "Level": "ALARM_CRITICAL" } }
}
}
}
}
}# ALARM_CRITICAL olan 38 kayıt varken
# ALARM_MAJOR olan 86 kayıt var
# },
# "aggregations" : {
# "alarm_major" : {
# "buckets" : {
# "critical" : { "doc_count" : 38 },
# "major" : { "doc_count" : 86 }
# }
# }
# }
Kompozit Kümeleme
Birden fazla alanı içeren bir küme oluşturmak istiyorum. Önce sadece Level alanının olduğu bir kümeleme dizisi oluşturup çıktısına bakalım:
GET vtoalarm/_search
{
"query": {
"range": {
"@timestamp": {
"gte": "now-10d/d",
"lt": "now/d"
}
}
},
"size": 0,
"aggs": {
"kompozit-kumeleme": {
"composite": {
"sources": [
{
"Level-Gruplama": {
"terms": {
"field": "Level"
}
}
}
]
}
}
}
}
Komut Çalıştırma (SCRIPT)
script
Kelimesinin Türkçe karşılığı “senaryo, diyalog …”.
- Bizim yedekleri otomatik alıp, sıkıştıran ve ağda yedekler isimli dizine bu sıkıştırılmış dosyayı attıktan sonra aldığı son yedek dosyasını silen bir SENARYO (
script
) yazar mısın?
Hakikaten yazdığımız satır satır komutlardan oluşan script
dosyası, repliklerden oluşmuş bir senaryo gibi değil midir?
Gelin Elasticsearch üstünde script
yazmaya bakalım.
"script": {
"lang": "...",
"source" | "id": "...",
"params": { ... }
}
Varsayılan olarak painless (acısız) diliyle script yazılıyor ama expression (ifade) de yazabiliriz. Painless için başlangıç noktanız bu adres olabilir.
Örnekteki kayıt şöyleydi:
{
"first" : "mark" ,
"last" : "giordano" ,
"goals" : [6,30,15] ,
"assists" : [3,30,24] ,
"gp" : [26,60,63] ,
"born" : "1983/10/03"
}
Sorgumuz şöyle olacak ve birazdan daha iyi anlıyor olacağız:
GET hockey/_search
{
"query": {
"function_score": {
"script_score": {
"script": {
"lang": "painless", <-- Tek satır ifade de olabilirdi
"source": """ <-- kodumuz birden çok satırda olacak
int total = 0; <-- Gol değerlerinin toplamını tutacak
Satırın alanı: golas[6,30,15] ve 6+30+15 sonucumuz olacak
for (int i = 0; i < doc['goals'].length; ++i) {
doc ile satıra erişeceğiz. doc['alanAdı'] ile alanın değerine:
total += doc['goals'][i];
}
return total;
"""
}
}
}
}
}
Sonuçları Bir Kısmını Çekmek (top
Komutu Gibi)
Sonuçlar sadece hits
altında değil ayrıca kümelemelerin (aggs
)içinde (bucket — kova) olabilir. Her iki sonuç kümesinde sayfalama yapabilir (from
) ve sonuçların bir kısmını (size
) alabilirsiniz .”
Hem URL, hem de sorgunun içinde size
ile dönecek sonuç sayısını kısıtlayabiliyoruz. Aşağıdaki çıktıda URL içinde belirtilen kadar döndüğünü, gövdede tanımlı sayı olan 5'in etkisiz kaldığını görüyoruz.
Kümeleme içinde sonuçları sınırlamak için de size
kullanılabilinir. Önce sınırlama olmaksızın sonuçları getirelim (aggs
içinde size
yok!):
Şimdi kümenin içinde dönen sonuç sayısını 1'e indirelim:
from İle Başlangıç Belirlemek
Önce sıralayalım ki _score
temelli sonuçlar getireceği için sonuçları sürekli farklı sırada görmeyelim. from
ile vereceğimiz değerin çalıştığını görebilmek için sort
ile @timestamp
alanına göre sıralıyoruz:
from
değerini 0'a atadığımızda en üstte 01. saniyeden itibaren başlıyor. Ancak from
değerini 1 yaptığımızda en üstteki kayıt 47:16 olacak.
from : 1
Olduğunda 47:16 ilk sıraya geldi:
Sıralama (Sorting)
Sıralamayı
- hem sorgu sonuçlarında
- hem de kümeleme çıktılarında (
bucket
)
yapabiliriz.
Sonuçların Sıralanması (sorting hits)
Sorgu sonuçlarının sıralanması için yapımız şöyle:
{
"sort" : [
{ "alan_adı": { "order" : "asc|desc" } },
...
],
"query": { ... },
"aggs": { ... }
}
Bir veya daha fazla alana göre sıralamayı artan veya azalan olacak şekilde yapabiliriz.
Önce sadece tarihe göre sıralayalım ve çıktı olarak sadece tarihi görüntüleyelim (POST alarm_yeni_2/_search?size=100&filter_path=hits.hits._source.@timestamp
).
Şimdi hostname
alanına göre tersine sıralama yaparak sonuçları görüntüleyelim: POST alarm_yeni_2/_search?size=100&filter_path=hits.hits._source.@timestamp,hits.hits._source.hostname
Sorugumuzu buraya da yazalım:
POST alarm_yeni_2/_search?size=100&filter_path=hits.hits._source.@timestamp,hits.hits._source.hostname
{
"sort": [
{
"hostname" : { "order": "desc" },
"@timestamp" : { "order": "desc" }
}
],
"query": {
"bool": {
"filter": [
{
"range": {
"@timestamp": {
"gte": "2020-01-01 01:01:55",
"lt": "2020-12-28 01:01:55",
"format": "yyyy-MM-dd hh:mm:ss"
}
}
},
{
"terms": {
"Level": [ "ALARM_CRITICAL", "ALARM_MAJOR" ]
}
}
],
"must": [
{ "exists": { "field": "Detail.nfInstanceId" } },
{ "exists": { "field": "Detail.serviceInstanceId" } },
{ "exists": { "field": "Level" } }
]
}
},
"aggs": {
"terms_hostname": {
"terms": {
"field": "hostname",
"order": { "_key": "desc" }
},
"aggs": {
"terms_nfInstanceId": {
"terms": {
"field": "Detail.nfInstanceId",
"order": { "_key": "asc" }
},
"aggs": {
"terms_serviceInstanceId": {
"terms": {
"field": "Detail.serviceInstanceId",
"order": { "_key": "asc" }
},
"aggs": {
"terms_level": {
"terms": {
"field": "Level",
"order": { "_key": "asc" }
}
}
}
}
}
}
}
}
}
}
Kümeleme Sonuçlarının Sıralanması (sorting aggregation results)
aggregations
içinde terms_hostname
isimli kümeleme sonuçlarını sıralamak için "order": { "_key": "desc" }
sıralamayı asc
veya desc
ile aşağıdaki çıktılardaki gibi değiştiriyoruz. SQL’de ORDER BY hostname DESC
der gibi
"aggs": {
"terms_hostname": {
"terms": {
"field": "hostname",
"order": { "_key": "desc" }
},