Elasticsearch İç Yapısı — Lucene, Segment, Translog
Giriş — Motorun Kaputunu Açıyoruz
Bir araba kullanırken motorun nasıl çalıştığını bilmenize gerek yoktur. Ama bir yarış pilotu iseniz — enjeksiyon sistemi, turbo basıncı, vites oranları hakkında derin bilgi sahibi olmanız gerekir. Çünkü limit durumlarda performansı bu bilgi belirler.
Elasticsearch'ü "kullanmak" için iç yapısını bilmenize gerek yok. Ama production ortamında performans sorunları yaşadığınızda, refresh interval'ı neden değiştirmeniz gerektiğini, merge policy'nin disk I/O'yu nasıl etkilediğini veya translog'un veri güvenliğiyle ilişkisini anlamanız şart olur. Bu ders, Elasticsearch'ün kaputunu açıyor.
1. Apache Lucene — Elasticsearch'ün Kalbi
1.1 Lucene Nedir?
Elasticsearch bir arama motoru değildir — bir dağıtık arama platformudur. Gerçek arama motoru, altında çalışan Apache Lucene'dir. Lucene, Doug Cutting tarafından 1999'da Java ile yazılmış, dünyanın en performanslı full-text search kütüphanesidir.
Elasticsearch Mimarisi:
┌─────────────────────────────────────────────┐
│ Elasticsearch │
│ ┌────────────────────────────────────────┐ │
│ │ Dağıtık Koordinasyon Katmanı │ │
│ │ (Cluster, Shard Routing, Replication) │ │
│ └────────────────┬───────────────────────┘ │
│ │ │
│ ┌────────────────▼───────────────────────┐ │
│ │ REST API + Query DSL │ │
│ │ (HTTP Interface, JSON Processing) │ │
│ └────────────────┬───────────────────────┘ │
│ │ │
│ ┌────────────────▼───────────────────────┐ │
│ │ Apache Lucene │ │
│ │ (Indexing, Searching, Scoring) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │
│ │ │ Segment 1│ │ Segment 2│ │Segment3│ │ │
│ │ └──────────┘ └──────────┘ └────────┘ │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘Elasticsearch'ün yaptığı şeyleri kategorize edelim:
Lucene'in işi:
Inverted index oluşturma ve arama
Token'ları segmentlere yazma
BM25 scoring
Segment merge
Stored fields, doc values
Elasticsearch'ün işi:
Dağıtık koordinasyon (cluster, shard routing)
REST API sağlama
JSON doküman yönetimi
Replica senkronizasyonu
Cluster health monitoring
Cross-shard aggregation
1.2 Lucene Index vs Elasticsearch Index
Bu terminoloji kafa karıştırıcı olabilir:
Elasticsearch Index = Mantıksal container
├── Shard 0 = 1 Lucene Index
├── Shard 1 = 1 Lucene Index
├── Shard 2 = 1 Lucene Index
└── ...
Her Lucene Index = Birden fazla Segment
├── Segment 0 (immutable)
├── Segment 1 (immutable)
├── Segment 2 (immutable)
└── ...Yani bir Elasticsearch index'i 3 primary shard'a sahipse, aslında 3 bağımsız Lucene index'i vardır. Her Lucene index'i de birden fazla segmentten oluşur.
REST API ile segment bilgisi:
GET /my-index/_segments
// Yanıt:
{
"_shards": { "total": 2, "successful": 2 },
"indices": {
"my-index": {
"shards": {
"0": [{
"routing": { "state": "STARTED", "primary": true, "node": "node-1" },
"num_committed_segments": 3,
"num_search_segments": 3,
"segments": {
"_0": {
"generation": 0,
"num_docs": 15000,
"deleted_docs": 200,
"size_in_bytes": 5242880,
"committed": true,
"search": true,
"version": "9.7.0",
"compound": true
},
"_1": {
"generation": 1,
"num_docs": 8000,
"deleted_docs": 50,
"size_in_bytes": 2621440,
"committed": true,
"search": true
}
}
}]
}
}
}
}2. Segment Yapısı — Immutable Veri Blokları
2.1 Segment Nedir?
Segment, Lucene'in temel veri birimidir. Her segment immutable (değiştirilemez) bir mini-index'tir. Bir segment oluşturulduktan sonra asla değiştirilmez — sadece silinebilir (merge sırasında).
Bir Segment İçeriği:
┌──────────────────────────────────┐
│ Segment _0 │
├──────────────────────────────────┤
│ Inverted Index │
│ term → [doc1, doc3, doc7, ...] │
│ term → [doc2, doc5, ...] │
├──────────────────────────────────┤
│ Stored Fields │
│ doc1 → {_source JSON} │
│ doc2 → {_source JSON} │
├──────────────────────────────────┤
│ Doc Values (columnar) │
│ field → [val1, val2, val3...] │
├──────────────────────────────────┤
│ Term Vectors (opsiyonel) │
├──────────────────────────────────┤
│ Norms (length normalization) │
├──────────────────────────────────┤
│ Points (numeric, geo, date) │
├──────────────────────────────────┤
│ Live Docs Bitset │
│ [1, 1, 0, 1, 1, 0, ...] │
│ (0 = deleted) │
└──────────────────────────────────┘2.2 Neden Immutable?
Segment'lerin değiştirilemez olmasının çok önemli nedenleri var:
1. Concurrency (Eşzamanlılık): Immutable veri yapıları lock gerektirmez. Birden fazla thread aynı segment'i aynı anda okuyabilir — hiçbir senkronizasyon overhead'i yoktur.
2. Cache Dostu: İşletim sistemi, immutable dosyaları OS file cache'inde tutabilir çünkü değişmeyeceklerini bilir. Bu, disk I/O'yu dramatik şekilde azaltır.
3. Sıkıştırma: Immutable veriler üzerinde daha agresif sıkıştırma uygulanabilir — çünkü veri değişmeyeceği için sıkıştırma bir kez yapılır.
4. Basitlik: Write-once semantiği, veri yapısını çok daha basit tutar. Karmaşık güncelleme mekanizmalarına gerek yoktur.
2.3 Doküman Güncelleme ve Silme
Segment'ler immutable olduğuna göre, güncelleme ve silme nasıl çalışır?
Silme: Doküman gerçekten silinmez. Segment içindeki live docs bitset'te ilgili bit 0'a çevrilir. Bu doküman artık arama sonuçlarında görünmez ama fiziksel olarak hâlâ segment'te durur.
Güncelleme: Eski doküman "silinir" (bitset'te 0), yeni versiyonu yeni bir segment'e yazılır. Yani her güncelleme aslında delete + insert'tir.
Doküman Güncelleme Akışı:
1. doc-1 (v1) → Segment A'da [live]
2. Update doc-1 (v2) gelir
3. Segment A'da doc-1 → [deleted] (bitset: 0)
4. doc-1 (v2) → In-memory buffer'a yazılır
5. Refresh → Yeni segment B oluşur, doc-1 (v2) burada [live]Fiziksel silme ancak merge sırasında gerçekleşir — merge edilirken deleted dokümanlar gerçekten atılır.
// Silinen doküman sayısını görmek
GET /my-index/_stats/docs
// Yanıt:
{
"_all": {
"primaries": {
"docs": {
"count": 100000, // Canlı doküman sayısı
"deleted": 15000 // Silinmiş ama hâlâ disk'te olan
}
}
}
}⚠️ Dikkat: Çok fazla güncelleme yapan index'lerde
deletedsayısı hızla artar. Bu, disk kullanımını ve arama performansını olumsuz etkiler çünkü her aramada deleted dokümanlar da kontrol edilir (sadece sonuçtan çıkarılır). Bu durumda force merge düşünülebilir.
3. Write İşlemi — Doküman Nasıl Index'lenir?
3.1 Write Akışı Detaylı
Bir doküman index'lediğinizde neler olur? Adım adım bakalım:
POST /products/_doc/1 { "title": "Laptop", "price": 999 }
Adım 1: Coordinating Node
├── Dokümanı alır
├── Routing hesaplar: shard = hash(_id) % num_primary_shards
└── İlgili primary shard'a yönlendirir
Adım 2: Primary Shard (Lucene Level)
├── a) Doküman in-memory buffer'a eklenir
├── b) Doküman translog'a yazılır (disk'e fsync)
└── c) "Acknowledge" döner
Adım 3: Replica Sync
├── Primary, dokümanı tüm replica'lara gönderir
├── Replica'lar da kendi buffer + translog'a yazar
└── Tüm replica'lar onayladığında istemciye yanıt döner
Adım 4: Refresh (varsayılan 1 saniye sonra)
├── In-memory buffer → Yeni Lucene segment (in-memory)
├── Segment aranabilir hale gelir
└── Buffer temizlenir
Adım 5: Flush (periyodik veya translog boyut limiti)
├── In-memory segment'ler disk'e yazılır
├── Commit point oluşturulur
└── Translog temizlenir3.2 In-Memory Buffer
Dokümanlar ilk olarak in-memory buffer'a (indexing buffer) yazılır. Bu buffer henüz aranabilir değildir — yani dokümanı yazdıktan hemen sonra arayamazsınız (near-realtime).
Buffer → [doc1, doc2, doc3, doc4, ...]
│
refresh (1s)
│
▼
Yeni Segment (aranabilir)Buffer boyutu indices.memory.index_buffer_size ile kontrol edilir:
# elasticsearch.yml
indices.memory.index_buffer_size: 10% # Varsayılan: heap'in %10'u
indices.memory.min_index_buffer_size: 48mb
indices.memory.max_index_buffer_size: unbounded💡 İpucu: Yoğun indexing yapıyorsanız (bulk import gibi), buffer size'ı artırmak performansı önemli ölçüde artırabilir. Ama bu, diğer işlemler için (arama, aggregation) daha az heap kalması demektir.
4. Transaction Log (Translog) — Veri Güvenliği
4.1 Translog Nedir?
In-memory buffer'daki veriler henüz disk'e yazılmamıştır. Sunucu çökerse bu veriler kaybolur. İşte translog (transaction log) tam da bu sorunu çözer.
Her write işlemi hem in-memory buffer'a hem de translog dosyasına yazılır. Translog, disk'e fsync edilir — yani güç kesilse bile veri korunur.
Write İşlemi:
│
├──────────────► In-Memory Buffer (hızlı, volatile)
│
└──────────────► Translog (disk, fsync, durable)Sunucu çöktüğünde yeniden başlatılırken:
Son commit point'ten disk'teki segment'ler yüklenir
Translog'daki commit point'ten sonraki işlemler tekrar oynatılır (replay)
Veri kaybı olmaz
4.2 Translog Yapılandırması
PUT /my-index/_settings
{
"index": {
"translog": {
"durability": "request",
"sync_interval": "5s",
"flush_threshold_size": "512mb"
}
}
}`durability` parametresi:
`request` (varsayılan): Her index/delete/update işleminden sonra translog fsync edilir. En güvenli ama en yavaş
`async`: Translog
sync_intervalaralıklarıyla fsync edilir. Daha hızlı ama sync_interval kadar veri kaybı riski var
// Yüksek throughput gerektiğinde (bazı veri kaybı kabul edilebilir)
PUT /logs-index/_settings
{
"index": {
"translog": {
"durability": "async",
"sync_interval": "30s"
}
}
}⚠️ Dikkat:
durability: asyncayarı,sync_intervalkadar (varsayılan 5 saniye) veri kaybı riski taşır. Log verileri gibi kaybolması tolere edilebilir veriler için kullanılabilir. Finansal işlemler gibi kritik veriler için aslaasynckullanmayın.
`flush_threshold_size`: Translog bu boyuta ulaştığında otomatik flush tetiklenir. Varsayılan 512MB'tır.
4.3 Translog İstatistikleri
GET /my-index/_stats/translog
// Yanıt:
{
"_all": {
"primaries": {
"translog": {
"operations": 125000,
"size_in_bytes": 67108864,
"uncommitted_operations": 5000,
"uncommitted_size_in_bytes": 2621440,
"earliest_last_modified_age": 30000
}
}
}
}operations: Toplam translog işlem sayısıuncommitted_operations: Henüz commit edilmemiş (flush bekleyen) işlem sayısıearliest_last_modified_age: En eski uncommitted işlemin yaşı (ms)
5. Refresh — Near-Realtime Search
5.1 Refresh Mekanizması
Refresh, in-memory buffer'daki dokümanları yeni bir in-memory segment haline getirip aranabilir yapma işlemidir. Segment henüz disk'e yazılmaz ama aranabilir hale gelir.
Refresh Öncesi:
Buffer: [doc1, doc2, doc3] ← Aranamaz
Segment_0: [eski docs] ← Aranabilir
Refresh Sonrası:
Buffer: [] ← Temizlendi
Segment_0: [eski docs] ← Aranabilir
Segment_1: [doc1,doc2,doc3] ← Yeni, aranabilir!Varsayılan refresh interval: 1 saniye. Bu, Elasticsearch'ün "near-realtime" (yakın gerçek zamanlı) olarak adlandırılmasının nedenidir — doküman yazıldıktan en fazla 1 saniye sonra aranabilir hale gelir.
5.2 Refresh Interval Yapılandırması
// Index bazında refresh interval
PUT /my-index/_settings
{
"index": {
"refresh_interval": "30s"
}
}Farklı senaryolar için önerilen değerler:
Senaryo refresh_interval
───────────────────────────────────────────────
Gerçek zamanlı arama (varsayılan) 1s
Log indexing (yoğun yazma) 30s
Bulk import (tek seferlik) -1 (kapalı)
Near-realtime dashboard 5s
Yüksek throughput + toleranslı arama 10s-60s5.3 Manuel Refresh
// Belirli bir index'i refresh et
POST /my-index/_refresh
// Tüm index'leri refresh et
POST /_refreshBulk import senaryosu — refresh'i kapatıp açma:
// 1. Refresh'i kapat
PUT /my-index/_settings
{ "index": { "refresh_interval": "-1" } }
// 2. Bulk import yap
POST /my-index/_bulk
{ "index": {} }
{ "title": "Doc 1" }
{ "index": {} }
{ "title": "Doc 2" }
// ... binlerce doküman
// 3. İmport bitti, refresh'i aç
PUT /my-index/_settings
{ "index": { "refresh_interval": "1s" } }
// 4. Son bir refresh yap
POST /my-index/_refresh5.4 Refresh'in Maliyeti
Her refresh yeni bir segment oluşturur. Çok sık refresh → çok fazla küçük segment → daha fazla file descriptor, daha fazla merge ihtiyacı, daha yavaş arama.
1 saniye refresh ile 1 dakikada:
→ 60 yeni segment oluşur
→ Her segment OS file handle kullanır
→ Her aramada 60 segment taranır
→ Merge pressure artar
30 saniye refresh ile 1 dakikada:
→ 2 yeni segment oluşur
→ Çok daha az overhead💡 İpucu: Eğer index'inize saniyede yüzlerce doküman yazıyorsanız ve arama gecikmeniz artıyorsa, ilk denemeniz gereken şey
refresh_interval'ı artırmaktır. 1s → 5s veya 30s yapmak dramatik performans iyileştirmesi sağlayabilir.
6. Flush ve Commit Point
6.1 Flush Nedir?
Flush, refresh'ten farklıdır. Flush:
In-memory segment'leri disk'e yazar (fsync)
Yeni bir commit point oluşturur
Translog'u temizler
Flush Öncesi:
Memory: [Segment_1] [Segment_2] ← In-memory segment'ler
Disk: [Segment_0] ← Daha önce flush edilmiş
Translog: [op1, op2, op3, ..., op500] ← Büyüyor
Flush Sonrası:
Memory: [] ← Temizlendi
Disk: [Segment_0] [Segment_1] [Segment_2] ← Hepsi disk'te
Translog: [] ← Temizlendi
Commit: [Segment_0, Segment_1, Segment_2] ← Yeni commit point6.2 Commit Point
Commit point, disk'teki hangi segment'lerin geçerli olduğunu kaydeden bir dosyadır. Sunucu çöküp yeniden başlatıldığında, commit point'teki segment'ler yüklenir ve translog'daki kalan işlemler replay edilir.
Commit Point Dosyası:
├── segments_3 ← Lucene commit point
│ ├── Segment _0 ← geçerli
│ ├── Segment _1 ← geçerli
│ └── Segment _2 ← geçerli
└── segments.gen ← Güncel generation numarası6.3 Flush Tetikleme
Flush otomatik olarak tetiklenir:
Translog boyutu
flush_threshold_size'ı aştığında (varsayılan 512MB)Belirli aralıklarla (Elasticsearch kendi yönetir)
Manuel flush:
// Manuel flush
POST /my-index/_flush
// Synced flush (deprecated in 8.x, artık otomatik)
POST /my-index/_flush/synced6.4 Refresh vs Flush — Karşılaştırma
┌─────────────┬────────────────────────┬────────────────────────────┐
│ │ Refresh │ Flush │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Ne yapar? │ Buffer → In-memory │ In-memory segment → │
│ │ segment │ Disk segment │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Aranabilir? │ Evet (segment oluşur) │ Zaten aranabilirdi │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Disk yazımı │ Hayır │ Evet (fsync) │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Translog │ Değişmez │ Temizlenir │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Commit point│ Değişmez │ Yeni commit point oluşur │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Varsayılan │ Her 1 saniye │ Translog 512MB olunca │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Maliyet │ Düşük (memory) │ Yüksek (disk I/O) │
├─────────────┼────────────────────────┼────────────────────────────┤
│ Kullanım │ Aranabilirlik kontrolü │ Veri dayanıklılığı │
└─────────────┴────────────────────────┴────────────────────────────┘Veri akışı özet:
Doküman → Buffer (aranamaz) → [REFRESH] → In-memory Segment (aranabilir)
→ [FLUSH] → Disk Segment (kalıcı)
→ Translog (her zaman disk'te) ─────────[FLUSH]──→ Temizlenir7. Merge — Segment Birleştirme
7.1 Neden Merge Gerekli?
Her refresh yeni bir segment oluşturur. Zamanla segment sayısı artar. Çok fazla segment:
Her aramada tüm segment'ler taranır → yavaşlama
Her segment OS file handle kullanır → kaynak tüketimi
Deleted dokümanlar hâlâ yer kaplar → disk israfı
Merge işlemi birden fazla küçük segmenti alır, birleştirir ve tek bir büyük segment oluşturur. Bu sırada deleted dokümanlar da fiziksel olarak temizlenir.
Merge Öncesi:
[Seg_0: 1000 docs, 200 deleted]
[Seg_1: 500 docs, 50 deleted]
[Seg_2: 300 docs, 10 deleted]
[Seg_3: 800 docs, 100 deleted]
= 4 segment, 2600 live docs, 360 deleted
Merge Sonrası:
[Seg_4: 2600 docs, 0 deleted]
= 1 segment, 2600 live docs, 0 deleted7.2 Merge Policy
Lucene, hangi segment'lerin ne zaman merge edileceğini belirlemek için bir merge policy kullanır. Elasticsearch varsayılan olarak TieredMergePolicy kullanır.
TieredMergePolicy parametreleri:
PUT /my-index/_settings
{
"index": {
"merge": {
"policy": {
"floor_segment": "2mb",
"max_merge_at_once": 10,
"max_merged_segment": "5gb",
"segments_per_tier": 10,
"deletes_pct_allowed": 20.0
}
}
}
}`floor_segment` (varsayılan 2MB): Bu boyutun altındaki segment'ler bu boyuta "yuvarlanır" — çok küçük segment'lerin merge önceliğini artırır
`max_merge_at_once` (varsayılan 10): Bir merge işleminde en fazla kaç segment birleştirilebilir
`max_merged_segment` (varsayılan 5GB): Merge sonucu oluşacak segment'in maksimum boyutu. Bundan büyük segment'ler merge edilmez (force merge hariç)
`segments_per_tier` (varsayılan 10): Her boyut katmanında kaç segment tutulacağı
`deletes_pct_allowed` (varsayılan 20.0): Segment'teki deleted doküman yüzdesi bu değeri aşarsa merge öncelik kazanır
7.3 Merge Scheduler
PUT /my-index/_settings
{
"index": {
"merge": {
"scheduler": {
"max_thread_count": 1,
"max_merge_count": 6
}
}
}
}`max_thread_count`: Eşzamanlı merge thread sayısı
- HDD için: 1 (HDD sequential write'ta iyidir, paralel merge disk'i yorar) - SSD için: Math.max(1, Math.min(4, cores/2)) (varsayılan)
`max_merge_count`: Maksimum eşzamanlı merge sayısı. Bu limit aşılırsa indexing throttle edilir
💡 İpucu: HDD kullanıyorsanız
max_thread_count: 1ayarı çok önemlidir. Paralel merge HDD'de random I/O oluşturur ve performansı ciddi şekilde düşürür.
7.4 Force Merge
Force merge, segment sayısını manuel olarak azaltmanızı sağlar. Sadece artık yazılmayan index'ler için kullanılmalıdır.
// Segment sayısını 1'e düşür
POST /old-logs-2024.01/_forcemerge?max_num_segments=1
// Sadece deleted dokümanları temizle
POST /my-index/_forcemerge?only_expunge_deletes=trueJava ile force merge:
ForcemergeRequest forcemergeRequest = ForcemergeRequest.of(f -> f
.index("old-logs-2024.01")
.maxNumSegments(1L)
);
ForcemergeResponse response = client.indices().forcemerge(forcemergeRequest);
System.out.println("Shards: " + response.shards().successful() + " successful");⚠️ Dikkat: Force merge çok kaynak yoğun bir işlemdir. Aktif yazılan index'lerde force merge yapmayın — yeni segment'ler oluşmaya devam edeceği için merge sonuçsuz kalır ve sadece disk I/O harcar. Force merge, "artık değişmeyecek" index'ler için idealdir (örneğin geçmiş ay log'ları).
7.5 Merge İstatistikleri
GET /my-index/_stats/merge
// Yanıt:
{
"_all": {
"primaries": {
"merges": {
"current": 0,
"current_docs": 0,
"current_size_in_bytes": 0,
"total": 150,
"total_time_in_millis": 45000,
"total_docs": 500000,
"total_size_in_bytes": 2147483648,
"total_stopped_time_in_millis": 0,
"total_throttled_time_in_millis": 5000
}
}
}
}current: Şu anda devam eden merge sayısıtotal_throttled_time_in_millis: Merge throttling süresi — eğer bu değer yüksekse, merge indexing'i yavaşlatıyor demektir
8. Near-Realtime Search Mekanizması
8.1 Realtime vs Near-Realtime
Gerçek zamanlı (realtime): Doküman yazıldığı anda aranabilir. Elasticsearch bunu sağlamaz (varsayılan olarak).
Yakın gerçek zamanlı (near-realtime): Doküman yazıldıktan 1 saniye sonra (varsayılan refresh_interval) aranabilir. Elasticsearch bunu sağlar.
Timeline:
t=0.0s → POST /products/_doc/1 {"title":"Laptop"} → 201 Created
t=0.1s → GET /products/_search?q=Laptop → 0 hits (henüz refresh olmadı!)
t=0.5s → GET /products/_search?q=Laptop → 0 hits (hâlâ...)
t=1.0s → [REFRESH gerçekleşir]
t=1.1s → GET /products/_search?q=Laptop → 1 hit ✅8.2 Anında Aranabilirlik İstiyorsanız
İki seçenek var:
Seçenek 1: `refresh=true` parametresi
// Dokümanı yaz ve hemen refresh et
POST /products/_doc/1?refresh=true
{
"title": "Laptop"
}
// Artık hemen aranabilirSeçenek 2: `refresh=wait_for` parametresi
// Dokümanı yaz, sonraki refresh'i bekle
POST /products/_doc/1?refresh=wait_for
{
"title": "Laptop"
}
// Yanıt, doküman aranabilir olduğunda dönerFark:
refresh=true: Hemen yeni bir refresh tetikler. Çok sık kullanılırsa performansı öldürürrefresh=wait_for: Sonraki doğal refresh'i bekler. Daha nazik ama biraz daha yavaş
// Java'da refresh seçenekleri
// refresh=true
IndexRequest<Product> request = IndexRequest.of(i -> i
.index("products")
.id("1")
.document(product)
.refresh(Refresh.True)
);
// refresh=wait_for
IndexRequest<Product> request2 = IndexRequest.of(i -> i
.index("products")
.id("2")
.document(product)
.refresh(Refresh.WaitFor)
);⚠️ Dikkat:
refresh=true'yu her write işleminde kullanmak, indexing throughput'unu %50-90 oranında düşürebilir. Sadece gerçekten anında aranabilirlik gerektiğinde kullanın. Çoğu durumda 1 saniyelik gecikme kabul edilebilirdir.
8.3 GET API vs Search API
İlginç bir detay: GET /my-index/_doc/1 (doküman ID ile doğrudan getirme) near-realtime değil, realtime'dır! GET API, translog'dan da okuyabilir — yani refresh olmadan bile dokümanı bulur.
// Dokümanı yaz
POST /products/_doc/1
{ "title": "Laptop" }
// Hemen ID ile get → BULUR (realtime)
GET /products/_doc/1 → 200 OK, {"title": "Laptop"}
// Hemen search → BULAMAZ (near-realtime, refresh beklenmeli)
GET /products/_search?q=Laptop → 0 hitsBu fark, GET API'nin inverted index yerine doğrudan doküman ID ile translog/stored fields'tan okuması sayesindedir.
Eğer bu davranışı kapatmak isterseniz:
GET /products/_doc/1?realtime=false9. Store Types ve OS Cache
9.1 Memory-Mapped Files ve OS Cache
Elasticsearch'ün performansının büyük kısmı OS file cache (page cache) sayesindedir. Lucene segment dosyaları OS tarafından RAM'e cache'lenir. Bu yüzden Elasticsearch'e verilen heap'in yanında, OS cache için de yeterli RAM bırakmak kritik önemdedir.
Elasticsearch varsayılan olarak hybridfs store type kullanır — mmapfs ve niofs'un hybrid'i, dosya tipine göre en uygun okuma yöntemini seçer.
Sunucu: 64 GB RAM
├── Elasticsearch JVM Heap: 31 GB (max %50 önerisi)
├── OS / Diğer procesler: 1 GB
└── OS File Cache: ~32 GB ← Segment dosyaları burada cache'lenir!💡 İpucu: Elasticsearch sunucusunda heap'i 32GB'ın üzerine çıkarmayın. JVM 32GB altında Compressed OOPs (Ordinary Object Pointers) kullanır — pointerlar 4 byte yerine 8 byte'a çıkmaz. 32GB heap, compressed OOPs sayesinde ~48GB'lık adres alanını gösterebilir. 32GB'ı aşarsanız, compressed OOPs devre dışı kalır ve aslında daha az efektif bellek elde edersiniz.
10. Gerçek Dünya Optimizasyon Senaryosu
10.1 Senaryo: Yoğun Log Indexing
Saniyede 50,000 log satırı index'leyen bir sistemde performans sorunları yaşıyorsunuz. Aşağıdaki optimizasyonları sırayla uygulayalım:
// 1. Refresh interval'ı artır (log'lar için 30s yeterli)
PUT /logs-*/_settings
{
"index": { "refresh_interval": "30s" }
}
// 2. Translog'u async yap (log verisi için kabul edilebilir risk)
PUT /logs-*/_settings
{
"index": {
"translog": {
"durability": "async",
"sync_interval": "30s",
"flush_threshold_size": "1gb"
}
}
}
// 3. Merge thread'lerini disk tipine göre ayarla
PUT /logs-*/_settings
{
"index": {
"merge": {
"scheduler": {
"max_thread_count": 1 // HDD için
}
}
}
}
// 4. Replica'ları indexing sırasında kapat, sonra aç
PUT /logs-*/_settings
{
"index": { "number_of_replicas": 0 }
}
// Bulk import tamamlandıktan sonra:
PUT /logs-*/_settings
{
"index": { "number_of_replicas": 1 }
}Java ile bu optimizasyonları uygulama:
public class IndexingOptimizer {
private final ElasticsearchClient client;
public IndexingOptimizer(ElasticsearchClient client) {
this.client = client;
}
public void optimizeForBulkIndexing(String indexPattern) throws IOException {
// Refresh interval artır
client.indices().putSettings(s -> s
.index(indexPattern)
.settings(st -> st
.refreshInterval(t -> t.time("30s"))
)
);
// Replica'ları kapat
client.indices().putSettings(s -> s
.index(indexPattern)
.settings(st -> st
.numberOfReplicas("0")
)
);
System.out.println("Index optimized for bulk indexing");
}
public void restoreAfterBulkIndexing(String indexPattern) throws IOException {
// Refresh interval'ı geri al
client.indices().putSettings(s -> s
.index(indexPattern)
.settings(st -> st
.refreshInterval(t -> t.time("1s"))
)
);
// Replica'ları geri aç
client.indices().putSettings(s -> s
.index(indexPattern)
.settings(st -> st
.numberOfReplicas("1")
)
);
// Force refresh
client.indices().refresh(r -> r.index(indexPattern));
System.out.println("Index settings restored for normal operation");
}
}10.2 İç Yapı Monitoring Checklist
Production'da izlenmesi gereken internal metrikler:
// 1. Segment sayısı ve boyutu
GET /my-index/_stats/segments
// segments.count çok yüksekse (>50/shard) → merge sorunları
// 2. Translog boyutu
GET /my-index/_stats/translog
// uncommitted_size_in_bytes çok büyükse → flush sorunları
// 3. Merge istatistikleri
GET /my-index/_stats/merge
// total_throttled_time yüksekse → disk I/O darboğazı
// 4. Refresh istatistikleri
GET /my-index/_stats/refresh
// total_time çok yüksekse → çok sık refresh
// 5. Store boyutu
GET /my-index/_stats/store
// Beklenen boyuttan büyükse → deleted dokümanlar birikmiş
// 6. Tüm detaylar tek seferde
GET /my-index/_stats?level=shards11. Özet
Apache Lucene, Elasticsearch'ün altındaki gerçek arama motorudur. Segment bazlı, immutable veri yapısı kullanır
Segment'ler değiştirilemez mini-index'lerdir. Güncelleme = delete + insert, gerçek silme ancak merge sırasında olur
Translog, in-memory buffer'daki veri kaybı riskini ortadan kaldırır. Her write hem buffer'a hem translog'a yazılır
Refresh (varsayılan 1s), buffer'ı aranabilir in-memory segment'e dönüştürür. Elasticsearch bu yüzden "near-realtime"dır — "realtime" değil
Flush, in-memory segment'leri disk'e yazar ve commit point oluşturur. Translog temizlenir
Merge, küçük segment'leri birleştirir ve deleted dokümanları fiziksel olarak temizler. TieredMergePolicy parametreleri performansı doğrudan etkiler
OS file cache, Elasticsearch performansının en kritik bileşenlerinden biridir. Heap'i %50'nin üzerine çıkarmayın — kalan RAM OS cache için kullanılmalıdır
AI Asistan
Sorularını yanıtlamaya hazır