Elasticsearch 101

Halit Gürpınar
Finartz
Published in
10 min readDec 17, 2020

Merhabalar, bu yazıda Elasticsearch’e giriş niteliğindeki konular ele alınacaktır. Bu kapsamda Elasticsearch nedir, hangi amaçlarla kullanılır, kimler kullanır, nasıl bir altyapıya sahiptir, özellikleri nelerdir gibi bilgilere değinilip sağladığı Rest API’lar üzerinden kullanım örnekleri gösterilecektir.

Elasticsearch Nedir?

Elasticsearch, uzun tanımlamalara girmeden önce ilk bakışta adından da anlaşılacağı üzere esnek yapılı bir arama motorudur. İlk versiyonu 2010 yılında piyasaya sürülen Elasticsearch; Apache Lucene üzerine inşa edilmiş, dağıtık mimariye sahip, Java diliyle yazılmış, açık kaynak kodlu bir projedir. Ayrıca db-engines derecelendirmesine göre şu an piyasadaki en popüler search engine’dir.

DB-Engines Ranking for Search Engines

Hangi Amaçlarla Kullanılır?

Aramanın yanı sıra loglama ve log analizi, uygulama performans izleme, jeo-uzlamsal veri analizi ve görselleştirme gibi amaçlarla da oldukça fazla kullanılmaktadır.

Elasticsearch veri saklamak için bir veritabanı niteliğinde de kullanılabilir. Fakat “storage & querying & aggregation” kombinasyonu onu sadece veri saklayan bir sistemden farklı kılıyor. Bundan dolayı Elasticsearch’ü veri saklamak için değil, search etmek için kullanmak daha doğru olur. Verileri bir veritabanında tutup sadece gerekli dataları Elasticsearch’e aktarmak gerekir.

Kimler Tarafından Kullanılmaktadır?

Birçok dev firma tarafından kullanılan Elasticsearch, çok geniş bir müşteri havuzuna sahiptir. Bu firmalardan öne çıkanları şunlardır:

Firmaların Elasticsearch’ü hangi amaçla kullandığına dair hikayelere de şu linkten ulaşabilirsiniz.

Hangi Dilleri Destekler?

Java, JavaScript (Node.js), Go, .NET (C#), PHP, Perl, Python, Ruby dilleri Elasticsearch tarafından official olarak desteklenmektedir.

Özellikleri Nelerdir?

  • Oldukça hızlıdır. Verileri saklama esnasında yaptığı indexleme sayesinde verinin indexlendikten sonra aramaya hazır hale gelmesi neredeyse 1 saniye kadar sürmektedir. Bu da gerçek zamana yakın bir arama platformu olmasını sağlamaktadır.
  • Apache Lucene altyapısına sahip olduğu için mükemmel bir full-text search yeteneğine sahiptir.
  • Structured, semi-structured ve unstructured veri tiplerini destekler.
  • Document-based bir yapıya sahiptir ve bu nedenle verileri JSON formatında tutar.
  • Tüm search, CRUD ve analiz işlemleri sağladığı Rest API üzerinden rahatlıkla yapılabilir.

Temel Kavramlar

Document
Elasticsearch’te her kayıt her bir JSON document’a karşılık gelmektedir. Document’lar ilişkisel veritabanlarındaki row’a karşılık gelir. Her bir document, bir type ve unique id’e sahiptir ve bir index içerisinde tutulur.

Field
İlişkisel veritabanlarındaki column alanına karşılık gelir. Bir document birden fazla field’a sahip olabilir. Field’lar mapping işlemi için field typle’lara sahiptir. Bu type kavramının document type ile karıştırılmaması gerekir. Field type o field’ın hangi tür data tutabileceğini belirtir (integer, string, object gibi).

Index
Index’ler JSON document yığınlarıdır. İlişkisel veritabanlarındaki database’lere karşılık gelir. Bir Elasticsearch cluster’ı birden fazla index bulundurabilir.

Type
Index’e kaydedilen her bir document’ın türünü belli eden bir type’ı vardır. type, ilişkisel veritabanlarındaki table’a karşılık gelmektedir. Örneğin product ve user’ı birer type olarak ele alabiliriz.
Type kavramı, Elasticsearch 7.0 versiyonuyla birlikte deprecated edilmiştir. Fakat birçok kaynakta bu kavramla karşılaşabilirsiniz. Bu nedenle bu makalede type’ın ne olduğu ve neden deprecate edildiğine de değineceğiz.
Deprecated edilmesinin sebebi:
Type’ın ilişkisel veritabanlarındaki table’lara karşılık geldiğini düşünsek de durum tam olarak böyle değil. İlişkisel veritabanlarındaki tablolar birbirinden bağımsızdır. Örnek olarak user tablosundaki name field’ı ile product tablosundaki name field’ının bir bağlantısı yoktur. Fakat Elasticsearch’te aynı index altında bulunan aynı isme sahip field’lar, arka planda aynı Lucene field’ına denk gelmektedir.
Yani önceki örneğe binaen user type’ının name field’ı ile product type’ının name field’ı aynı alanda saklanır. Bu da birtakım sorunlara yol açmaktadır. Örneğin bir type içerisindeki isDeleted field’ının integer türünde olması gerektiğini düşündüğünüzde, aynı index içerisindeki diğer bir type’da bu field boolean olarak tutuluyor olabilir. Bu nedenle Type kavramı kaldırılmıştır. Type yerine hangi alternatiflerin önerildiğine ve bu süreç hakkında daha fazla detaya şu linkten ulaşabilirsiniz.

Mapping
Verilerin Elasticsearch’e nasıl aktarılacağı ve okunacağının belirtilmesi işlemidir. Her bir JSON field’ının sakladığı data türüne göre nasıl davranması gerektiği bilgisini sağlar. İlişkisel veritabanlarındaki schema’ya karşılık gelir.
Mapping, manuel olarak yapılabilir. Eğer mapping yapılmazsa Elasticsearch kendisi otomatik olarak bu işi yapar (Dynamic Mapping).
Elasticsearch’ün desteklediği bazı veri tipleri:
- Basit veri tipleri: text, keyword, date, long, double, boolean
- Kompleks veri tipleri: object, nested
- Özel veri tipleri: geo_point, geo_shape, completion

Elasticsearch’teki kavramların ilişkisel veri tabanlarındaki karşılıklarını şu şekilde gösterebiliriz:

Cluster
Bir veya birden fazla node içeren yapıdır.

Node
Cluster içerisinde çalışan her bir Elasticsearch instance’ına node adı verilir. Node’lar; verileri tutma, indexleme ve arama işlerinden sorumludur.
Elasticsearch ayağa kalkarken node’lar cluster name’lerine göre unicast discover yapar ve ilgili cluster’a katılır.
Node’lar farklı özelliklere ve rollere sahip olabilir. Detaylara şu linkten erişebilirsiniz.

Shard
Index’lerde çok fazla veri olması performans, ölçeklenebilirlik ve bakım sorunlarına yol açabilir. Bu yüzden index’ler birer Lucene instance’ı olan shard’lara bölünebilir. Index oluşturulurken shard’ların sayısı belirlenir ve veriler shard’lara dağıtılır.
Bu yapı yatayda ölçeklenebilmeyi ve gelen istekleri farklı shard’lar üzerinde paralel olarak işlemeyi sağlar.

Replica
Shard’ın veya bir node’un bozulması, devre dışı kalması gibi durumların tolere edilmesi ve erişebilirliğin sağlanması için Elasticsearch shard’ların kopyalarını oluşturmaktadır. Bir shard’ın birden fazla kopyası olabilir.
Kopyalanan shard, primary shard olarak adlandırılır. Oluşan kopyalarına ise replica adı verilir.
Erişilebilirliğin sağlanması için replica’ların primary shard’la aynı node üzerinde olmaması gerekir.
Arama istekleri replica’lar üzerinde de paralel olarak işlendiği için, replica’lar performansa da katkı sağlar.

Elasticsearch Cluster Yapısı

Indexing & Search
Elasticsearch, veriler kaydedilirken bir kelimenin hangi dökümanda geçtiği bilgisini indexler.
Bu yapı kitaplarda bulunan index kısmı gibidir. Kitap index’lerinde hangi kelimenin hangi sayfalarda bulunduğunun bilgisi vardır. Benzer şekilde Elasticsearch de hangi kelimenin hangi dökümanlarda bulunduğu bilgisini Apache Lucene’nin Inverted Index altyapısı ile tutar.
Inverted Index aşağıdaki görselde olduğu gibi, dökümanlardaki unique sözcükleri ve bu sözcüklerin bulundukları yerleri bünyesinde tutan bir veri yapısıdır.
Arama yapılırken de Elasticsearch tüm veriler üzerinde arama yapmak yerine, Inverted Index listesinden o kelimeyi içeren dökümanları bularak JSON objeleri olarak döndürür.
Bu dökümanlar ayrıca bir skorlama algoritmasına göre de sıralanır. Skorlama algoritması aranılan kelimenin dökümanda geçme sıklığı gibi bazı kriterlere göre belirlenir.

Inverted Index Yapısı

Kullanım

Kurulum
Kurulum işlemleri işletim sistemine göre farklılık gösterdiği için burada detaya girilmeyecektir. Kurulumu şu link üzerinden gerçekleştirebilirsiniz.
Kurulumu yaptıktan sonra Elasticsearch’ü ayağa kaldırıp http://localhost:9200/ adresinden Elasticsearch’ün çalışıp çalışmadığı kontrol edilebilir. Aşağıdaki gibi bir ekranın sizi karşılaması gerekmektedir:

http://localhost:9200/

Elasticsearch REST APIs
Elasticsearch farklı fonksiyonlara sahip birçok API sağlamaktadır. Bunlardan bazıları şunlardır:

1- Cat API
Bu API sayesinde index, cluster, node, shard gibi birçok şey ile ilgili bilgiler görüntülenebilir. Dönen response’lar JSON formatı yerine daha user-friendly bir biçimde olduğu için kullanımı kolaydır. Bu API’nin sağladığı bazı endpointler şunlardır:

GET /_cat/nodes?help → Kullanılabilecek komutları gösterir
GET /_cat/indices?v → Indexleri görüntüler
GET /_cat/nodes?v → Node’ları görüntüler
GET /_cat/shards?v → Shardları görüntüler
GET /_cat/nodes?v&h=ip → ip bilgisini verir
GET /_cat/nodes?v&h=ip,port → Birden fazla parametreyi virgül ile ayırıp sorgulayabiliriz

2- Cluster API
Cluster ile ilgili bilgileri görüntüler. Bu API’nin sağladığı bazı endpointler şunlardır:
GET /_cluster/health → Cluster’ın genel sağlık durumunu görüntüler
GET /_cluster/state → Clusterl’a ilgili geniş kapsamlı bilgilere erişilir

3- Index API
Index bazında create, delete, monitoring, mapping, settings gibi işlerden sorumludur.

  • Create Index
    Index oluşturmayı sağlar. Settings ve mappings’i manuel girmek için request body ekleyebileceğimiz gibi, request body olmadan da index oluşturabiliriz.
Request:
PUT movie
Response:
{
“acknowledged” : true,
“shards_acknowledged” : true,
“index” : “movie”
}

Shard ve replica bilgilerini manuel vererek bir index oluşturabiliriz:

Request:
PUT category
{
“settings” : {
“index” : {
“number_of_shards” : 2,
“number_of_replicas” : 2
}
}
}
Response:
{
“acknowledged” : true,
“shards_acknowledged” : true,
“index” : “category”
}

Bu index’te her primary shard için 2 replica olacağını belirttik. Bu nedenle toplam 6 shard olur.

Dynamic Mapping yerine manuel mapping yaparak index oluşturabiliriz:

Request:
PUT city
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"plate": {
"type": "integer"
},
"location": {
"type": "geo_point"
},
"region": {
"type": "keyword"
}
}
}
}
Response:
{
“acknowledged” : true,
“shards_acknowledged” : true,
“index” : “city”
}
  • Get Index
    Oluşturulan indexleri görüntülemeyi sağlar. Bir, birden fazla veya tüm indexler için sorgulama yapılabilir.
Request:
GET category
Response:
{
"category": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1605857462624",
"number_of_shards": "2",
"number_of_replicas": "2",

"uuid": "psL3qeTcTYatNt1PldBEsw",
"version": {
"created": "7090299"
},
"provided_name": "category"
}
}
}
}

Category ve movie index’leri için ikili sorgu yapabiliriz:

Request:
GET category,movie
Response:
{
"category": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1605857462624",
"number_of_shards": "2",
"number_of_replicas": "2",
"uuid": "psL3qeTcTYatNt1PldBEsw",
"version": {
"created": "7090299"
},
"provided_name": "category"
}
}
},
"movie": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1605857405592",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "pdLYAnpOQO2sMMtDIIrlpQ",
"version": {
"created": "7090299"
},
"provided_name": "movie"
}
}
}
}

Tüm index’leri sorgulayabiliriz:

Request:
GET _all
Response:
{
"category": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1605857462624",
"number_of_shards": "2",
"number_of_replicas": "2",
"uuid": "psL3qeTcTYatNt1PldBEsw",
"version": {
"created": "7090299"
},
"provided_name": "category"
}
}
},
"movie": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1605857405592",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "pdLYAnpOQO2sMMtDIIrlpQ",
"version": {
"created": "7090299"
},
"provided_name": "movie"
}
}
},
"user": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1605858095142",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "_6yEINIgR2KSusLX_I2aaA",
"version": {
"created": "7090299"
},
"provided_name": "user"
}
}
}
}
  • Index Settings
    Index’lere ait settings bilgilerini sorgulayabiliriz:
Request:
GET category/_settings
Response:
{
"category": {
"settings": {
"index": {
"creation_date": "1605857462624",
"number_of_shards": "2",
"number_of_replicas": "2",
"uuid": "psL3qeTcTYatNt1PldBEsw",
"version": {
"created": "7090299"
},
"provided_name": "category"
}
}
}
}
  • Delete Index
    Index’leri silmeyi sağlar. Bir, birden fazla veya tüm index’ler için silme işlemini gerçekleştirebiliriz.
Request:
DELETE category
Response:
{
"acknowledged": true
}

Birden fazla index için silme yapabiliriz:

Request:
DELETE movie,user
Response:
{
"acknowledged": true
}

Tüm index’leri silebiliriz:

Request:
DELETE _all
Response:
{
"acknowledged": true
}

4- Document API
Dökümanlar üzerinde CRUD operasyonlarını yapmamızı sağlar.

  • Index API
    Dökümanı insert etmek yani Elasticsearch’te indexlemek için kullanılır.

Dynamic mapping kullanarak ve id’sini belirleyerek document indexleyebiliriz:

Request:
PUT movie/_doc/1
{
"name": "contratiempo",
"year": 2016,
"category": "crime",
"rating": 8.1
}
Response:
{
"_index": "movie",
"_type": "_doc",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}

Id bilgisinin otomatik olarak verilmesini istersek şu şekilde kullanabiliriz:

Request:
POST user/_doc
{
"name": "user1",
"age": 25
}
Response:
{
"_index": "user",
"_type": "_doc",
"_id": "0rS-5HUBF5TLNoQAJVB_",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
  • Get API
    Oluşturduğumuz document’ları görüntülemeyi sağlar.
Request:
GET movie/_doc/1
Response:
{
"_index": "movie",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "contratiempo",
"year": 2016,
"category": "crime",
"rating": 8.1
}
}

Document’in belirlediğimiz field’larını görmek için istek yollayabiliriz:

Request:
GET movie/_doc/1?_source_includes=name,rating
Response:
{
"_index": "movie",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "contratiempo",
"rating": 8.1
}
}
  • Update API
    Document’i update etmek için kullanılır.
Request:
POST movie/_update/1
{
"doc": {
"rating": 8.4
}
}
Response:
{
"_index": "movie",
"_type": "_doc",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
  • Bulk API
    Index, create, update ve delete işlemlerini çoklu bir şekilde yapmayı sağlar. Index ve create ikisi de temelde insert işlemi yaparken aralarında bir fark vardır. Index ile yollanan istekte aynı Id’ye sahip başka bir document olsa bile onu replace eder. Create ise bu durumda hata verir.
Request:
POST city/_bulk
{"index": {"_id": "1"}}
{"name":"istanbul", "plate": 34}
{"index": {"_id": "2"}}
{"name":"trabzon", "plate": 61}
{"index": {"_id": "3"}}
{"name":"bursa", "plate": 16}
{"index": {"_id": "3"}}
{"name":"samsun", "plate": 55}
{"create": {"_id": "3"}}
{"name":"ankara", "plate": 6}

Bu örnekte Bursa id=3 ile indexlenmiştir. Daha sonra aynı id ile Samsun için index isteği gönderilmiştir ve artık id’si 3 olan document Samsun olmuştur. Devamında benzer şekilde aynı id ile Ankara için create isteği gönderilmiştir fakat bu istek hata alacaktır.

  • Delete API
    Document silmek için kullanılır.
Request:
DELETE movie/_doc/1
Response:
{
"_index": "movie",
"_type": "_doc",
"_id": "1",
"_version": 3,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}

5-Search API
Elasticsearch’te arama yapmak için kullanılır. İki tür arama yöntemi vardır:
-URI Query: Request body yoktur, hızlı sorgu işlemlerinde kullanılır, karmaşık sorgular için uygun değildir.

Country index’indeki tüm document’leri dönmek için:

Request:
GET country/_search?q=*

Country index’inde name alanı Turkey olan document’leri dönmek için:

Request:
GET localhost:9200/country/_search?q=name:Turkey

-Query DSL: Request body kullanılır, okunması kolaydır, karmaşık sorgular için uygundur.
Query DSL için yapacağımız örnek sorgular için bir dataset üzerinde çalışacağız. Bu dataseti bulk api ile indexleyip search işlemlerini gerçekleştireceğiz.

Request:
POST country/_bulk
{"index":{"_id":"1"}}
{"name":"Turkey","capital":"Ankara","population": 83,"date": "1923/10/29","continent": "ASIA","flag": ["red","white"] }
{"index":{"_id":"2"}}
{"name":"Germany","capital":"Berlin", "population": 90, "date": "1990/10/03","continent": "EUROPA","flag": ["red","yellow","black"]}
{"create":{"_id":"3"}}
{"name":"Italy","capital":"Roma", "population": 50, "date": "1861/03/17","continent": "EUROPA","flag": ["red","white","green"]}
{"index":{"_id":"5"}}
{"name":"United States","capital":"Washington","population": 300, "date": "1776/07/04","continent": "AMERICA","flag": ["red","white","blue"]}
{"index":{"_id":"4"}}
{"name":"Greece","capital":"Atina", "population": 15, "date": "1821/03/25","continent": "EUROPA","flag": ["blue","white"]}
{"create":{"_id":"6"}}
{"name":"Iran","capital":"Tahran", "population": 80, "date": "1979/04/01","continent": "ASIA","flag": ["red","white","green"]}
{"create":{"_id":"7"}}
{"name":"Mexico","capital":"Mexico City", "population": 125, "date": "1810/09/16","continent": "AMERICA","flag": ["red","white","green"]}

Query DSL’de search için birtakım keywordler bulunmaktadır. Bunlardan bazıları şunlardır:

match_all: tüm document’leri döndürür:

Request:
GET country/_search
{
"query": {
"match_all": {}
}
}

match: flag field’ında red içeren document’leri döndürür:

Request:
GET country/_search
{
"query": {
"match": {
"flag": "red"
}
}
}

query_string: herhangi bir field’ında “ASIA” geçen document’leri döndürür:

Request:
GET country/_search
{
"query":{
"query_string":{
"query":"ASIA"
}
}
}

term: case-sensitive olarak continent field’ı “EUROPA” olan document’leri döndürür. Aramayı “europa” veya “Europa” gibi kelimelerle yaptığımızda herhangi bir eşleşme bulamayacaktır:

Request:
GET country/_search
{
"query": {
"term": {
"continent.keyword": "europa"
}
}
}

range: population field’ı 80'e eşit veya 80'den büyük document’leri döndürür:

Request:
GET country/_search
{
"query": {
"range": {
"population": {
"gte": 80
}
}
}
}

range ile kullanılabilecek operatörler şunlardır:
-gte: greater than equal to
-gt:
greater than
-lte:
less than equal to
-lt:
less than

bool: komplike sorgular için kullanılır. continent field’ı “EUROPA” olan ve date field’ı “1900/01/01” den küçük veya eşit olan document’leri döndürür:

Request:
{
"query": {
"bool": {
"must": [
{
"match": {
"continent": "EUROPA"
}
}
],
"filter": [
{
"range": {
"date": {
"lte": "1900/01/01"
}
}
}
]
}
}
}

Özet

Bu yazıda Elasticsearh’e giriş niteliğinde bilgiler verilmiştir. Terminolojisinden, kullanım alanlarından, temel prensiplerinden bahsedilmiş ve sağladığı Rest API’lar üzerinden temel düzeyde kullanım örnekleri verilmiştir.
Rest APIs bölümünde kullanılan requestlere postman collections olarak github üzerinden ulaşabilirsiniz.

Kaynaklar

--

--