ES101 — Elasticsearch Mimarisi
İlk blog yazım olarak ES101 sizlerle, yazı umarım açıklayıcı ve yararlı olmuştur. İçerik daha çok elasticsearch’ün mimarisi hakkında genel bilgi içermektedir. Genel olarak başlıklar şöyledir; index, inverted index, shard, lucene, segment. Evet ilk başta bu kavramlar çok karışık gelmektedir ancak görseller ile daha konuların oturduğunu söyleyebilirim.
Elasticsearch, lucene java kütüphanesi üzerine kurulmuş distributed arama motorudur. Elasticsearch’e gönderilen datayı lucene görür ve saklar.
NoSQL yapıları 4 temel başlıkta ayrılır:
- document-based
- key-value
- graph-based
- column-based
Elasticsearch burada document-based NoSQL grubuna girmektedir, key-value yapısına benzemektedir ancak value kısmında döküman’ı tutmaktadır.
Index nedir?
Aslında her bir index Relational Database’ler deki tablo yapısına denk geliyor diyebiliriz. Index’i oluştururken iki yöntem vardır biri manuel olarak bizim oluşturmamız, ikincisi ise bunu elasticsearch’e bıraktığımız yöntemdir. Temelde bir index bir veya birden fazla shard’ı temsil eden alan adıdır. Indexlemek(indexing) kavramı ise; datayı elasticsearch’e göndermek(verinin geri istenebilecek şekilde saklanması ve arama yapılmabileceği) anlamına gelmektedir.
- Yöntem
POST /users diyerek manuel oluşturmak
2. Yöntem
POST /users/user/1 diyerek yeni bir döküman eklerken, elasticsearch mappingleri kendisi yaparak bir index oluşturur
İndex’i oluşturan bileşenler; mappings, settings ve aliases’dır.
{
"index-name": {
"aliases": {},
"mappings": {
"user": {
"properties": {
"name": {
"type": "string"
}
}
}
},
"settings": {
"index": {
"creation_date": "1447004861339",
"uuid": "KzZW7zJyTPavy4n1TTeNkg",
"number_of_replicas": "1",
"number_of_shards": "5",
"version": {
"created": "1070399"
}
}
}
}
}
Aliases, index’e verdiğimiz takma isimlerdir ve bize uygulamanın kesinti yaşamadan index değiştirebilmesini sağlar.
Mappings, index içerisinde saklanacak veri yapısını tutmaktadır, aslında RDB’lerdeki tablo yapısı olarak düşünülebilir. Her ne kadar tutulan data json yapısında olarak bize esnek bir yapı sağlasada, data yapısını vermek bize performans sağlar.
Settings içerisinde index ile ilgili ayarlar bulunmaktadır, örnek olarak; “kaç shard?”, “replika sayısı kaç?” gibi soruların cevapları buradan ayarlanmaktadır. Bunun yanında analyzer bilgileride burada bulunmaktadır. Analyzer da seri işlemleri gerçekleştirerek en son bir çıktı verir elasticsearch için.
Type, benzer dökümanlar sınıfıdır diyebiliriz aslında şöyleki, “user” index’ini düşünelim. Bu index altında “admin”,”user” ve ”staff” gibi type’lar oluşturabiliriz.
Shard nedir?
Elasticsearch cluster’ı içerisinde çalışan lucene instance’larını temsil eder ve ayrıyetten lucene index de diyebiliriz. Bir index tek başına bir shard’dan oluşabilir ancak, genellikle index’in büyüyebilmesi ve bir kaç node üzerinde dağıtılabilmesi için bir kaç shard’dan oluşmaktadır. Primary shard döküman için ev sahipliği yapmaktadır, replica shard ise primary’nin bir kopyasıdır ve primary shard down olması durumunda replica shard kullanılır. Ayrıca dökümanı read ederkende replikalar kullanılabilir.(distributed read)
Replica, elasticsearch her verinin kopyasını başka makinalara da gönderir, böylece makinalardan bir tanesi down olması durumunda veri kaybı yaşanması engellenir.
Ayrıca elasticsearch’de consistency level tanımlayarak, indexleme işleminin bitmesini tüm shardlarda indexledikten sonra başarılı olarak dönecek şekilde ayarlayabiliriz.
Bir elasticsearch index search işlemi yapıldığında, bu arama işlemi tüm shardların tüm segmentlerinde gerçekleşir ve merge olurlar.
Shard aslında elasticsearch için bir “scaling unit” anlamı taşımaktadır. Örnek olarak; iki elasticsearch index search’ü (herbirine bir shard) ile bir index’in iki shard olması aynı durum çünkü, iki adet lucene index search işlemi gerçekleşir.
Dökümanları indexlerken, shardlara route olurlar, default olarak round-robin olarak dağıtılır. Index create edilirken verilen shard sayısı daha sonrasında değiştirilemez, bu demek değilki çözüm yolu yok. Bu durumda index shrink yada reindex işlemleri gerçekleştirilerek shard sayısı değiştirilebilir.
Index shrink işlemi shard sayısını azaltmak için kullanılır, burada önemli olan bir nokta ise; primary shard olarak düşünürsek yeni shard sayısı eski shard sayısının çarpanları olabilir. Örnek olarak, 15 primary shard’lı bir index düşünür isek, shrink işlemi ile primary shard sayısı 1,3,5 den biri olabilir. Shrink işleminde, öncelikle aynı özelliklerde sadece shard sayısını azaltarak yeni bir index oluşturur, daha sonra eğer dosya sistemi izin veriyorsa segmentleri yeni index’e hard-link yapar(dosya sistemi izin vermiyor ise yeni index’e kopyalar ve bu süreç daha uzun sürer). Daha sonrasında ise yeni index’i eskisinin yerine koyarak işlemi tamamlar.
Reindex işleminde ise, yeni index’e eski indexteki konfigurasyonlar aktarılmaz ve yeni konfigurasyonlar ile index oluşturduktan sonra, dökümanlar yeni index’e kopyalanır.
Lucene nedir?
Apache tarafından geliştirilen java tabanlı full-text search olanağı sağlayan bir kütüphanedir. Bir döküman basit olarak “field:value” olarak eşlenir. Lucene field’ın string yada tarih vb bir formatta olmasını önemsemez, opaque-bytes olarak kabul eder.
Lucene’de biz bir dökümanı indexlediğimiz zaman, her bir alan(field)için değerler(values) inverted index’e eklenir ve isteğe bağlı olarak orjinal değerler daha sonrada ulaşılabilir olması için değişmeden de saklanabilir.
Elasticsearch’teki type kavramı’nın lucene’de tam karşılığı yoktur ancak, lucene’de bu veriler bir dökümanın meta datasında _type diye bir alanda tutulur ve özel bir arama yaparken; lucene _type alanına göre filtreleme yapar.
Lucene üzerinde mapping diye bir kavram da bulunmamaktadır, mapping elasticsearch’ün karışık json dökümanının lucene’in beklediği bir yapıya çevrilmesi için kullanılır.
Lucene her ne kadar transaction sağlasa bile, elasticsearch bunu sağlamaz.
Inverted Index Nedir?
Aslında inverted-index ile; terimler-döküman ilişkisini bir yerde tuttuğumuzu düşünürsek, bir term ile search işlemi gerçekleşirken bu term’e denk gelen dökümanlara direkt erişebilirsiniz.
Inverted-Index’te önemli noktalardan bir tanesi ise analyzer’lardır. Çeşitli tokenizer ve filter’lar üzerinden geçtikten sonra inverted index’e kayıt edilir.
Örnek olarak; standart tokenizer burada text’i split etti aslında, lowercase filter ise hepsini küçük harfe çevirdi ve stopwords ise bazı kelimeleri çıkarttı. En sonun da en sağdaki gibi bir inverted-index oluştu, bu örnekte bir dökümanda birden fazla yerde aynı kelimenin geçme durumuda ele alınarak(bilgisayarın {1:1,1:14} gibi)
Yukarıdaki görsel elastic mimarisindeki hiyerarşiyi anlamanızda biraz daha yardımcı olacaktır. Cluster içerisinde node’lar, ve onların içerisinde ise shard’lar bulunmaktadır. Shard’lar nodelara dağıtılırken primary ve secondary shardlar farklı makinelere dağıtılarak olası problemleri minimize etmek gerekmektedir. Shard’ların da içerisinde segment’ler bulunmaktadır.
Segment Nedir?
Her shard birden fazla segment’e sahiptir ve her segment bir inverted indextir. Bir shard üzerinde yapılan arama sırasıyla her bir segmentte aranacaktır ve sonra elde edilen bu sonuçlar shard bazında oluşan, sonuçlar kümesinde toplanacaktır.
Bir dökümanı indexlerken, elasticsearch onları öncelikle transaction log’a yazacak(data kaybı olmaması için) ve memoryde(disk’e göre daha performanslı olması için) toplayacaktır. Daha sonra ise index.refresh_interval süresinde(default 1sn) bir diskte yeni bir segment oluşturarak veriyi segment içerisinde search edilebilir hale getirir ve refresh eder. Şu an veri arama yapılabilir(searchable) haldedir. Ancak yeni oluşan bu segment henüz fsync(flush) edilmemiş haldedir, yani halen kaybolma riskine sahiptir. Elasticsearch sık sık flush işlemini gerçekleştirir, ve bu işlem sonrasında veri artık işlenmiştir(indexlenmiştir) ve sonrasında transaction log’dan silinir.
Segment sayısı optimize olmalıdır, çünkü fazla segment olması daha performanslı olması anlamına gelmez. Ne kadar çok segment olur ise aramalar o kadar uzun sürer, çünkü segmentlerden dönen arama sonuçları shardda toplanırken merge maliyeti devreye girer.
Peki sürekli segment oluşturuyoruz o zaman nasıl optimize etmeliyiz derseniz, elasticsearch arkaplanda gerçekleştirdiği merge processsler sayesinde benzer büyüklükteki segmentleri birleştirerek daha büyük segmentler elde eder ve bunu diske yazdıktan sonra eskilerini siler. Merge operasyonları için ise birden fazla policy bulunmaktadır (Policy Tipleri, Örnekler).
Segmentler immutable’dır(değişmezler), döküman update edildiğinde ise eski döküman silindi olarak işaretlenir ve yeni bir döküman ekleniyormuş gibi işlem yapılır.
Yapılan merge processler esnasında ayrıyetten silindi olarak işaretlenen dökümanlarda çıkartılır. Daha fazla döküman eklenmesi herzaman index size’ının artması anlamına gelmez. Nasıl derseniz; silindi olarak işaretlenenler merge process’te silindiği için böyle bir durumla karşılaşılma ihtimalide bulunmaktadır.
Concurrent merge scheduling; merge işlemleri ayrı threadlerde gerçekleşir, maximum thread count’a gelindiğinde ise available olana dek bekler ve daha sonra devam eder. Lucene 2.3 versiyonundan önce her döküman ekleme işleminde “tiny segment”ler oluşturuluyordu, ancak artık “documents writer” ile döküman topluluğu (batch of documents) in-memory segmentlerde tutulur. Ayrıca lucene versiyon 4'ten sonra bu işlemlerin her biri bir thread’de de gerçekleşebilir, böylece conurrent flushing yaparak indexing performansının artışını sağlar, daha eski versiyonlarda ise; indexing flush’ın bitmesini bekler.
Indexing throughput oldukça önemli bir faktör, reindexing yaparken; küçük segmentleri flushing ve merging yapmak pek efektif değildir. Bu yüzden bu esnada geçici olarak “refresh interval”ı arttırmak yada refreshing’i kapatmak iyi bir yol olabilir.