Index Optimizasyonu — Shard Stratejisi ve Routing
Giriş — Shard Stratejisi, Routing ve ILM
Bir kütüphane düşün. Kitapları tek bir rafta tutarsan, bir süre sonra o raf çöker — hem fiziksel olarak hem de aradığını bulmak imkansız hale gelir. Ama kitapları türlerine göre farklı raflara dağıtırsan, her raf makul boyutta kalır ve arama kolaylaşır. Şimdi bir de şunu düşün: her gün yüzlerce yeni kitap geliyor. Eski kitaplar okunmuyor ama yer kaplıyor. Bir sistem kurup "6 aydan eski kitaplar arka depoya, 1 yıldan eskiler arşive" desen işler düzene girer.
Elasticsearch'te index optimizasyonu tam olarak bu. Shard'larını doğru boyutlandır, veriyi akıllıca yönlendir (routing), ve zamanla verinin yaşam döngüsünü yönet (ILM). Bu üç kavram, Elasticsearch'ünüzün "günde 100 request'lik dev tool oyuncağı" ile "günde 1 milyar event'lik production canavarı" arasındaki farkı yaratır.
1. Shard Stratejisi — Kaç Shard, Ne Kadar Büyük?
Shard Nedir? (Hızlı Hatırlatma)
Her Elasticsearch index'i bir veya daha fazla shard'a bölünür. Her shard aslında bağımsız bir Lucene index'idir. Shard'lar cluster'daki node'lara dağıtılır — bu sayede paralel arama ve yatay ölçekleme mümkün olur.
Shard Boyutu: Altın Kural
Elasticsearch topluluğunda ve resmi dokümantasyonda önerilen shard boyutu: 10GB ile 50GB arası.
Neden bu aralık?
Çok küçük shard'lar (< 1GB): Her shard'ın overhead'i var. Cluster state'te yer kaplar, segment merge yapar, file handle tutar. 1000 tane 100MB'lık shard, 20 tane 5GB'lık shard'dan çok daha fazla kaynak tüketir.
Çok büyük shard'lar (> 50GB): Recovery süresi uzar. Bir node düşerse, o devasa shard'ı başka bir node'a taşımak dakikalar hatta saatler sürebilir. Ayrıca segment merge işlemleri disk ve CPU'yu boğar.
# Mevcut shard boyutlarını kontrol et
GET _cat/shards?v&h=index,shard,prirep,store,node&s=store:desc
# Çıktı örneği:
# index shard prirep store node
# logs-2024.01 0 p 45.2gb node-1
# logs-2024.01 1 p 43.8gb node-2
# logs-2024.01 0 r 45.2gb node-3
# users 0 p 2.1gb node-1Shard Sayısı Hesaplama
Formül basit:
Toplam Shard Sayısı = Toplam Veri Boyutu / Hedef Shard BoyutuÖrnek: 500GB log veriniz var, hedef shard boyutu 25GB → 20 primary shard.
Ama dikkat — shard sayısını index oluşturduktan sonra değiştiremezsiniz (split/shrink API'leri hariç, ama bunlar pahalı operasyonlar). Bu yüzden baştan doğru planlamak kritik.
// 500GB veri için index oluşturma
PUT logs-2024.01
{
"settings": {
"number_of_shards": 20,
"number_of_replicas": 1
}
}Node Başına Shard Limiti
Elasticsearch 7.x'ten itibaren varsayılan olarak node başına 1000 shard limiti var. Bu limite ulaşmak, cluster'ınızda bir tasarım problemi olduğunun işareti:
# Cluster-wide shard sayısını kontrol et
GET _cluster/health?filter_path=active_primary_shards,active_shards
# Node başına shard dağılımı
GET _cat/allocation?v&h=node,shards,disk.used,disk.availShard Başına Document Sayısı
Lucene'in hard limiti: shard başına ~2.1 milyar document. Pratikte bu limiti zorlamamalısınız — 200-500 milyon document/shard ideal aralık.
Over-Sharding Sorunu
Yeni başlayanların en sık hatası: "Daha fazla shard = daha hızlı arama" sanmak. Gerçekte her shard:
~50MB heap memory kullanır (metadata için)
Segment merge için CPU ve I/O harcar
Arama sırasında coordinating node'da birleştirilmesi gerekir
// ❌ YANLIŞ — Küçük index için aşırı shard
PUT small-index
{
"settings": {
"number_of_shards": 20,
"number_of_replicas": 1
}
}
// ✅ DOĞRU — Küçük index için az shard
PUT small-index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}💡 İpucu: 1GB'dan küçük index'ler için 1 shard yeterlidir. Shard sayısını artırmak performans kazandırmaz, overhead ekler.
2. Shrink ve Split API — Shard Sayısını Değiştirme
Shard sayısını yanlış belirlediniz mi? Panik yok — ama bedavaya da gelmez.
Shrink API
Shard sayısını azaltır. Ama kural var: yeni shard sayısı, eskinin tam böleni olmalı. Örneğin 12 shard → 6, 4, 3, 2 veya 1 yapılabilir. 12 → 5 yapılamaz.
// 1. Adım: Index'i read-only yap ve tüm shard'ları tek node'a taşı
PUT logs-old/_settings
{
"settings": {
"index.routing.allocation.require._name": "node-1",
"index.blocks.write": true
}
}
// 2. Adım: Shrink işlemi
POST logs-old/_shrink/logs-old-shrunk
{
"settings": {
"index.number_of_shards": 2,
"index.number_of_replicas": 1,
"index.routing.allocation.require._name": null,
"index.blocks.write": null
}
}Split API
Shard sayısını artırır. Yeni sayı, eskinin katı olmalı. 2 shard → 4, 6, 8... yapılabilir.
// Index'i read-only yap
PUT logs-current/_settings
{
"settings": {
"index.blocks.write": true
}
}
// Split işlemi (2 → 6 shard)
POST logs-current/_split/logs-current-split
{
"settings": {
"index.number_of_shards": 6,
"index.blocks.write": null
}
}⚠️ Dikkat: Hem shrink hem split, arka planda tüm veriyi yeniden yazan ağır operasyonlardır. Production'da off-peak saatlerde yapın.
3. Routing — Veriyi Akıllıca Yönlendirme
Varsayılan Routing
Elasticsearch, bir document'ı hangi shard'a koyacağına şu formülle karar verir:
shard_num = hash(_routing) % number_of_primary_shardsVarsayılan _routing değeri document'ın _id'sidir. Bu, verilerin shard'lara eşit dağılmasını sağlar.
Custom Routing
Peki ya belirli verileri aynı shard'da tutmak istiyorsanız? Mesela multi-tenant bir uygulamada, her müşterinin verisi aynı shard'da olsun istiyorsunuz. Böylece bir müşterinin araması tüm shard'ları taramak yerine sadece kendi shard'ını tarar — devasa performans kazancı.
// Custom routing ile document indexleme
PUT my-index/_doc/1?routing=customer_123
{
"customer_id": "customer_123",
"order": "Laptop",
"amount": 15000
}
PUT my-index/_doc/2?routing=customer_123
{
"customer_id": "customer_123",
"order": "Mouse",
"amount": 250
}
PUT my-index/_doc/3?routing=customer_456
{
"customer_id": "customer_456",
"order": "Keyboard",
"amount": 800
}// Arama sırasında routing belirt — sadece ilgili shard taranır
GET my-index/_search?routing=customer_123
{
"query": {
"match": {
"customer_id": "customer_123"
}
}
}Bu arama, 20 shard'lı bir index'te bile sadece 1 shard'ı tarar. 20x hızlanma demek bu.
Routing Zorunlu Kılma
Routing belirtmeyi unutmayı engellemek için:
PUT my-index
{
"mappings": {
"_routing": {
"required": true
}
}
}Routing ile Dengesiz Shard'lar (Hotspot)
Custom routing'in en büyük riski: bir müşterinin verisi çok büyükse, o shard diğerlerinden çok daha büyük olur. Buna hotspot denir.
Çözüm: routing_partition_size ile veriyi birden fazla shard'a yayabilirsiniz:
PUT my-index
{
"settings": {
"number_of_shards": 20,
"routing_partition_size": 5
},
"mappings": {
"_routing": {
"required": true
}
}
}Bu durumda her routing değeri 5 farklı shard'a yayılır (20 yerine 5 shard taranır — hâlâ büyük kazanç).
Java ile Custom Routing
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
// Document indexleme with routing
IndexRequest<Order> request = IndexRequest.of(b -> b
.index("orders")
.id("order-1001")
.routing("customer_123")
.document(new Order("customer_123", "Laptop", 15000))
);
client.index(request);
// Routing ile arama
SearchResponse<Order> response = client.search(s -> s
.index("orders")
.routing("customer_123")
.query(q -> q
.match(m -> m
.field("customer_id")
.query("customer_123")
)
),
Order.class
);4. Index Lifecycle Management (ILM)
Problem: Zamana Bağlı Veri
Log, metrik, event gibi zamana bağlı veriler sürekli büyür. Bugünün logları kritik, geçen ayın logları ara sıra bakılır, geçen yılın logları neredeyse hiç okunmaz ama saklanması gerekir. Her yaştaki veriyi aynı şekilde tutmak kaynak israfıdır.
ILM Nedir?
Index Lifecycle Management, bir index'in hayat döngüsünü otomatik yöneten politikadır. Bir index doğar (hot), yaşlanır (warm), emekliye ayrılır (cold), ve sonunda ölür (delete).
Fazlar (Phases)
| Faz | Amaç | Tipik Donanım |
|---|---|---|
| Hot | Aktif yazma ve okuma | Hızlı SSD, yüksek CPU |
| Warm | Sadece okuma, yazma yok | Orta SSD veya HDD |
| Cold | Nadiren okuma | Ucuz HDD, yavaş disk |
| Frozen | Çok nadiren okuma | Shared storage (S3, etc.) |
| Delete | Silme | — |
ILM Policy Oluşturma
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "25gb",
"max_age": "1d"
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": {
"number_of_shards": 1
},
"forcemerge": {
"max_num_segments": 1
},
"set_priority": {
"priority": 50
},
"allocate": {
"require": {
"data": "warm"
}
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"set_priority": {
"priority": 0
},
"allocate": {
"require": {
"data": "cold"
}
}
}
},
"frozen": {
"min_age": "90d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "my-s3-repo"
}
}
},
"delete": {
"min_age": "365d",
"actions": {
"delete": {}
}
}
}
}
}Bu policy şunu diyor:
Hot: Yazma ve okuma. Shard 25GB'a ulaşınca veya 1 gün geçince rollover yap.
Warm (7 gün sonra): Shard'ları 1'e düşür, segment'leri birleştir, warm node'lara taşı.
Cold (30 gün sonra): Cold node'lara taşı.
Frozen (90 gün sonra): Searchable snapshot olarak S3'e taşı.
Delete (365 gün sonra): Sil.
ILM Policy'yi Index'e Bağlama
// Index template oluştur
PUT _index_template/logs-template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1,
"index.lifecycle.name": "logs-policy",
"index.lifecycle.rollover_alias": "logs"
}
}
}
// İlk index'i alias ile oluştur
PUT logs-000001
{
"aliases": {
"logs": {
"is_write_index": true
}
}
}Rollover — Otomatik Index Rotasyonu
ILM'in en güçlü özelliği rollover. Belirlediğiniz koşullar sağlandığında (boyut, yaş, document sayısı) otomatik olarak yeni bir index oluşturur:
// Manuel rollover (test için)
POST logs/_rollover
{
"conditions": {
"max_primary_shard_size": "25gb",
"max_age": "1d",
"max_docs": 10000000
}
}
// Sonuç: logs-000002 oluşur, logs alias'ı yeni index'i gösterirILM Durumunu Kontrol Etme
# Index'in ILM durumu
GET logs-000001/_ilm/explain
# Tüm ILM policy'leri
GET _ilm/policy
# ILM durumunu kontrol et
GET _ilm/statusData Stream ile ILM
Elasticsearch 7.9+ ile gelen Data Streams, time-series veri için ILM'i daha da kolaylaştırır:
// Component template
PUT _component_template/logs-mappings
{
"template": {
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "text" },
"level": { "type": "keyword" },
"service": { "type": "keyword" }
}
}
}
}
// Index template (data stream)
PUT _index_template/logs-ds-template
{
"index_patterns": ["logs-ds-*"],
"data_stream": {},
"composed_of": ["logs-mappings"],
"template": {
"settings": {
"index.lifecycle.name": "logs-policy"
}
}
}
// Data stream otomatik oluşur (ilk document yazıldığında)
POST logs-ds-app/_doc
{
"@timestamp": "2024-01-15T10:30:00Z",
"message": "User login successful",
"level": "INFO",
"service": "auth-service"
}Data Stream avantajları:
Rollover alias'a gerek yok — otomatik
Append-only — güncelleme ve silme kısıtlı (log/metrik senaryoları için ideal)
@timestampzorunlu — zamana bağlı veri garanti
5. Force Merge — Segment Optimizasyonu
Her shard birden fazla segment içerir. Yeni document'lar yeni segment'ler oluşturur. Çok fazla segment = yavaş arama.
Segment Durumunu Kontrol Etme
GET _cat/segments/logs-2024.01?v&h=index,shard,segment,size,docs.count
# Çıktı:
# index shard segment size docs.count
# logs-2024.01 0 _0 1.2gb 1500000
# logs-2024.01 0 _1 800mb 1000000
# logs-2024.01 0 _2 50mb 60000
# logs-2024.01 0 _3 2mb 2500Force Merge İşlemi
// ⚠️ Sadece yazma işlemi biten index'lerde yapın!
POST logs-2024.01/_forcemerge?max_num_segments=1
// Birden fazla index
POST logs-2024.01,logs-2024.02/_forcemerge?max_num_segments=1⚠️ Dikkat: Force merge aktif yazma olan index'lerde yapılmamalıdır! Yeni segment'ler oluşmaya devam edeceği için merge işlemi tekrar tekrar çalışır ve I/O'yu boğar. Sadece warm/cold fazındaki, artık yazma almayan index'lerde kullanın.
6. Index Sorting — Yazma Anında Sıralama
Elasticsearch, index'e yazılan verileri belirli bir field'a göre sıralı tutabilir. Bu, o field'a göre yapılan aramalarda erken sonlandırma (early termination) sağlar.
PUT sorted-logs
{
"settings": {
"index": {
"sort.field": ["@timestamp", "level"],
"sort.order": ["desc", "asc"]
}
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"message": { "type": "text" }
}
}
}Bu index'te @timestamp'e göre azalan sırayla arama yapıldığında, Elasticsearch istenen sayıda sonuç bulunca geri kalan segment'leri taramadan durabilir.
💡 İpucu: Index sorting yazma performansını düşürür (her document sıralı eklenmeli), ama okuma performansını artırır. Write-heavy senaryolarda dikkatli kullanın.
7. Codec Ayarı — Sıkıştırma Stratejisi
// Varsayılan: LZ4 (hızlı sıkıştırma, orta oran)
PUT fast-index
{
"settings": {
"index.codec": "default"
}
}
// best_compression: DEFLATE (yavaş sıkıştırma, yüksek oran)
PUT archive-index
{
"settings": {
"index.codec": "best_compression"
}
}| Codec | Algoritma | Sıkıştırma Oranı | Yazma Hızı | Okuma Hızı |
|---|---|---|---|---|
| default | LZ4 | Orta | Hızlı | Hızlı |
| best_compression | DEFLATE | Yüksek (~%15-25 daha iyi) | Yavaş | Biraz yavaş |
Warm/cold fazındaki index'lerde best_compression kullanmak disk tasarrufu sağlar.
8. Bütünleşik Örnek: E-Commerce Log Sistemi
Gerçek dünya senaryosu: Günde 50GB log üreten bir e-ticaret platformu.
// 1. ILM Policy
PUT _ilm/policy/ecommerce-logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "30gb",
"max_age": "1d"
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "3d",
"actions": {
"shrink": {
"number_of_shards": 1
},
"forcemerge": {
"max_num_segments": 1
},
"allocate": {
"number_of_replicas": 1,
"require": {
"data": "warm"
}
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "14d",
"actions": {
"readonly": {},
"allocate": {
"require": {
"data": "cold"
}
},
"set_priority": {
"priority": 0
}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}
// 2. Index Template (custom routing + ILM)
PUT _index_template/ecommerce-logs
{
"index_patterns": ["ecommerce-logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"index.lifecycle.name": "ecommerce-logs-policy",
"index.lifecycle.rollover_alias": "ecommerce-logs",
"index.routing.allocation.require.data": "hot",
"index.sort.field": ["@timestamp"],
"index.sort.order": ["desc"]
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"store_id": { "type": "keyword" },
"event_type": { "type": "keyword" },
"user_id": { "type": "keyword" },
"product_id": { "type": "keyword" },
"message": { "type": "text" },
"response_time_ms": { "type": "integer" },
"status_code": { "type": "short" }
}
}
}
}
// 3. Bootstrap index
PUT ecommerce-logs-000001
{
"aliases": {
"ecommerce-logs": {
"is_write_index": true
}
}
}
// 4. Veri yazma (alias üzerinden)
POST ecommerce-logs/_doc
{
"@timestamp": "2024-01-15T14:30:00Z",
"store_id": "store_42",
"event_type": "purchase",
"user_id": "user_789",
"product_id": "prod_12345",
"message": "Purchase completed successfully",
"response_time_ms": 234,
"status_code": 200
}Java ile ILM Durumu Kontrolü
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.ilm.GetLifecycleResponse;
import co.elastic.clients.elasticsearch.ilm.ExplainLifecycleResponse;
// ILM policy'yi oku
GetLifecycleResponse lifecycle = client.ilm()
.getLifecycle(g -> g.name("ecommerce-logs-policy"));
System.out.println("Policy phases: " + lifecycle.get("ecommerce-logs-policy").policy().phases());
// Index'in ILM durumunu kontrol et
ExplainLifecycleResponse explain = client.ilm()
.explainLifecycle(e -> e.index("ecommerce-logs-000001"));
explain.indices().forEach((indexName, status) -> {
System.out.println("Index: " + indexName);
System.out.println("Phase: " + status.phase());
System.out.println("Action: " + status.action());
System.out.println("Age: " + status.age());
});9. Best Practices
✅ Yap
| Konu | Öneri |
|---|---|
| Shard boyutu | 10-50GB arasında tut |
| Küçük index'ler | 1 shard yeterli |
| Time-series veri | ILM + rollover kullan |
| Multi-tenant | Custom routing düşün |
| Warm/cold index'ler | Force merge + best_compression |
| Data streams | Yeni projeler için tercih et |
❌ Yapma
| Konu | Neden |
|---|---|
| 1000+ shard/node | Cluster state şişer, master node zorlanır |
| Aktif index'te force merge | I/O patlar, yazma yavaşlar |
| Routing olmadan tüm shard tarama | Gereksiz kaynak israfı |
| ILM'siz log index'leri | Disk dolana kadar büyür, elle müdahale gerekir |
| Gereğinden fazla shard | Overhead birikir, arama yavaşlar |
10. Yaygın Hatalar ve Çözümleri
Hata 1: "Too Many Shards" Uyarısı
# Sorun: cluster.max_shards_per_node limitine ulaşıldı
# Çözüm 1: Gereksiz index'leri sil
DELETE old-unused-index-*
# Çözüm 2: Limiti artır (geçici çözüm, tavsiye edilmez)
PUT _cluster/settings
{
"persistent": {
"cluster.max_shards_per_node": 1500
}
}
# Çözüm 3 (doğru): Index'leri shrink et veya ILM ile yönetHata 2: Dengesiz Shard Dağılımı
# Sorun: Bazı node'lar çok daha fazla shard barındırıyor
GET _cat/allocation?v
# Çözüm: Rebalance tetikle
POST _cluster/reroute?retry_failed=true
# Veya allocation ayarlarını kontrol et
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.balance.shard": 0.45,
"cluster.routing.allocation.balance.index": 0.55
}
}Hata 3: ILM Policy Uygulanmıyor
# Kontrol et
GET my-index/_ilm/explain
# Yaygın sorunlar:
# 1. ILM servisi durmuş olabilir
GET _ilm/status
# Çözüm:
POST _ilm/start
# 2. Rollover alias yok veya yanlış
# Write index flag kontrolü:
GET my-index/_aliasHata 4: Routing Belirtmeden Arama
// ❌ Routing zorunlu ama belirtilmemiş → Hata!
GET my-index/_search
{
"query": { "match_all": {} }
}
// routing_missing_exception hatası alırsınız
// ✅ Routing belirt
GET my-index/_search?routing=customer_123
{
"query": { "match_all": {} }
}11. İleri Seviye: Index Template Stratejisi
Production'da genelde tek bir index template değil, katmanlı template yapısı kullanılır:
// Component template: Ortak ayarlar
PUT _component_template/base-settings
{
"template": {
"settings": {
"number_of_replicas": 1,
"index.lifecycle.name": "default-policy"
}
}
}
// Component template: Log-specific mappings
PUT _component_template/log-mappings
{
"template": {
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "text" },
"level": { "type": "keyword" },
"host": { "type": "keyword" },
"service": { "type": "keyword" }
}
}
}
}
// Composed index template
PUT _index_template/logs-template
{
"index_patterns": ["logs-*"],
"composed_of": ["base-settings", "log-mappings"],
"template": {
"settings": {
"number_of_shards": 3
}
},
"priority": 200
}Bu yaklaşım DRY (Don't Repeat Yourself) prensibini uygular. Ortak ayarları bir kere tanımlar, farklı index pattern'ları için birleştirirsiniz.
Özet
Shard boyutu 10-50GB arasında olmalı — çok küçük overhead yaratır, çok büyük recovery'yi zorlaştırır.
Custom routing ile multi-tenant senaryolarda devasa performans kazanımı sağlanır — ama hotspot riskine dikkat edin.
ILM (Index Lifecycle Management) time-series veri için olmazsa olmaz — hot → warm → cold → delete döngüsünü otomatikleştirir.
Rollover ile index'ler belirli boyut/yaş limitlerinde otomatik döner — elle index oluşturma derdinden kurtulursunuz.
Force merge sadece yazma bitmiş index'lerde yapılır — segment sayısını azaltır, arama hızlandırır.
Data Streams yeni projelerde time-series veri için en temiz çözüm — rollover alias'ın yerini alır, daha az konfigürasyon gerektirir.
AI Asistan
Sorularını yanıtlamaya hazır