Scaling Stratejileri — Hot-Warm-Cold Mimari
Giriş — Horizontal/Vertical Scaling ve Hot-Warm-Cold Architecture
Bir restoranın büyüme hikayesi düşün. Başta küçük bir mekan: 10 masa, 1 aşçı, 1 garson. İş büyüyünce iki seçenek var: Ya daha büyük bir mutfak ve daha büyük masa kur (vertical scaling — aynı restoranı büyüt), ya da yan tarafa ikinci bir şube aç (horizontal scaling — yeni restoran ekle). Bir de akıllı bir strateji var: Sabah kahvaltı menüsü ucuz malzemeyle, öğlen iş menüsü orta malzemeyle, akşam fine-dining pahalı malzemeyle çalışsın (hot-warm-cold — kaynak kullanımını zamana göre optimize et).
Elasticsearch'te scaling, cluster'ınız büyüdükçe performansı ve kapasiteyi artırma stratejisidir. Doğru scaling kararı, gereksiz harcamayı önler ve sistemi geleceğe hazırlar.
1. Vertical Scaling — Daha Güçlü Donanım
Ne Zaman?
Mevcut node'ların kaynakları (CPU, RAM, disk) yetersiz
Cluster küçük (1-3 node) ve node eklemek pratik değil
Geçici çözüm gerekiyor
Nasıl?
# ÖNCE: 16GB RAM, 4 core, 500GB disk
# SONRA: 64GB RAM, 16 core, 2TB SSD
# elasticsearch.yml — Heap artışı
# ⚠️ 31GB'ı geçme (compressed oops)
# jvm.options
-Xms30g
-Xmx30gVertical Scaling Limitleri
| Kaynak | Pratik Limit | Neden |
|---|---|---|
| RAM (Heap) | 31GB | Compressed oops sınırı |
| CPU | 32-64 core | Diminishing returns, Lucene thread model |
| Disk | ~10TB/node | Single disk failure risk, recovery süresi |
💡 İpucu: Vertical scaling'in bir tavanı var. 64 core + 256GB RAM'li tek sunucu, 8x (16 core + 64GB RAM) sunucu kadar verimli değil. Elasticsearch dağıtık bir sistem — horizontal scaling doğal yolu.
2. Horizontal Scaling — Daha Fazla Node
Ne Zaman?
Veri hacmi mevcut node kapasitesini aşıyor
Arama latency artıyor (shard/node başına iş yükü çok)
Yazma throughput'u yetersiz
High availability (HA) gereksinimi
Node Ekleme
# elasticsearch.yml — Yeni data node
node.name: data-new-1
node.roles: [data_hot]
cluster.name: production-cluster
discovery.seed_hosts: ["master-1:9300", "master-2:9300", "master-3:9300"]Yeni node cluster'a katılınca, master otomatik olarak shard'ları rebalance eder.
Rebalance İzleme
# Yeni node ekledikten sonra shard hareketini izle
GET _cat/shards?v&h=index,shard,prirep,state,node&s=node
# Relocating shard'lar
GET _cat/recovery?v&active_only
# Allocation durumu
GET _cat/allocation?vScaling Hesaplaması
Kaç node gerektiğini hesaplamak için:
# Veri hesabı
Toplam veri = 500GB (primary)
Replica = 1 → Toplam = 1TB
Hedef shard boyutu = 25GB → 40 shard (20 primary + 20 replica)
# Node hesabı
Disk kapasitesi/node = 2TB
Kullanılabilir disk = %80 = 1.6TB
Node sayısı (disk) = 1TB / 1.6TB = 1 (ama HA için min 3)
# RAM hesabı (shard başına ~50MB heap + data cache)
40 shard × 50MB = 2GB heap overhead
Index boyutu / 50 = ~10GB heap (search/aggregation buffer)
Toplam heap ihtiyacı ≈ 12GB → 30GB heap yeterli per node
# Final: 3 data node (2TB SSD, 64GB RAM, 16 core each)Shard Rebalancing Hızlandırma
// Yeni node ekledikten sonra rebalance hızını artır
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.node_concurrent_incoming_recoveries": 4,
"cluster.routing.allocation.node_concurrent_outgoing_recoveries": 4,
"indices.recovery.max_bytes_per_sec": "200mb"
}
}3. Hot-Warm-Cold Architecture
Motivasyon
Tüm veriniz eşit değil:
Bugünün logları: Sürekli yazılıyor, sürekli okunuyor → hızlı donanım gerekli
Geçen haftanın logları: Bazen okunuyor, yazılmıyor → orta donanım yeter
Geçen ayın logları: Nadiren okunuyor → ucuz donanım yeter
Geçen yılın logları: Neredeyse hiç okunmuyor → en ucuz depolama
Hepsini NVMe SSD üzerinde tutmak para israfı. Hepsini HDD'de tutmak performans felaketi. Çözüm: katmanlı mimari.
Tier Tanımları
| Tier | Kullanım | Donanım | Maliyet |
|---|---|---|---|
| Hot | Aktif okuma/yazma | NVMe SSD, yüksek CPU | $$$ |
| Warm | Okuma, yazma yok | SATA SSD, orta CPU | $$ |
| Cold | Nadiren okuma | HDD, düşük CPU | $ |
| Frozen | Çok nadiren | S3/shared storage | ¢ |
Node Konfigürasyonu
# Hot node — elasticsearch.yml
node.name: hot-1
node.roles: [data_hot, data_content, ingest]
path.data: /mnt/nvme/elasticsearch
# Warm node — elasticsearch.yml
node.name: warm-1
node.roles: [data_warm]
path.data: /mnt/ssd/elasticsearch
# Cold node — elasticsearch.yml
node.name: cold-1
node.roles: [data_cold]
path.data: /mnt/hdd/elasticsearch
# Frozen node — elasticsearch.yml
node.name: frozen-1
node.roles: [data_frozen]
# Frozen tier genelde searchable snapshots kullanırILM ile Tier Geçişi
PUT _ilm/policy/tiered-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
},
"set_priority": {
"priority": 50
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"set_priority": {
"priority": 0
},
"allocate": {
"number_of_replicas": 0
}
}
},
"frozen": {
"min_age": "90d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "my-s3-repo",
"force_merge_index": true
}
}
},
"delete": {
"min_age": "365d",
"actions": {
"delete": {}
}
}
}
}
}Data Tier Preference
Elasticsearch 7.10+ ile _tier_preference ayarı otomatik çalışır:
// Index'in tier tercihini kontrol et
GET logs-000001/_settings?flat_settings=true&filter_path=*.settings.index.routing.allocation.include._tier_preference
// Manuel tier taşıma
PUT old-logs/_settings
{
"index.routing.allocation.include._tier_preference": "data_cold,data_warm,data_hot"
}_tier_preference sırası önemli: İlk tercihi dener, yoksa ikinciye fallback yapar.
Tier Geçişini İzleme
# Hangi index hangi tier'da?
GET _cat/indices?v&h=index,health,pri,rep,store.size,segments.count&s=store.size:desc
# Node'ların tier dağılımı
GET _cat/nodes?v&h=name,role,disk.used,disk.avail
# ILM durumu
GET logs-*/_ilm/explain?filter_path=indices.*.phase,indices.*.action4. Maliyet Optimizasyonu
Tier Bazlı Maliyet Analizi
Senaryo: Günde 100GB log, 1 yıl tutma.
Seçenek A: Tek Tier (Tümü Hot)
365 gün × 100GB = 36.5TB (primary)
+ Replica = 73TB toplam
NVMe SSD @ $0.20/GB/ay = $14,600/aySeçenek B: Hot-Warm-Cold
Hot (7 gün): 700GB × 2 = 1.4TB NVMe @ $0.20 = $280/ay
Warm (23 gün): 2.3TB × 2 = 4.6TB SSD @ $0.10 = $460/ay
Cold (335 gün): 33.5TB × 1 = 33.5TB HDD @ $0.03 = $1,005/ay
Toplam: $1,745/ayTasarruf: %88 ($14,600 → $1,745)
Replica Stratejisi
| Tier | Replica Sayısı | Neden |
|---|---|---|
| Hot | 1-2 | HA + arama performansı |
| Warm | 1 | HA (arama nadir) |
| Cold | 0-1 | Maliyet (snapshot'tan restore edilebilir) |
| Frozen | 0 | Snapshot'ta zaten var |
// Cold tier'da replica kaldır (ILM ile otomatik)
"cold": {
"actions": {
"allocate": {
"number_of_replicas": 0
}
}
}5. Searchable Snapshots — Frozen Tier
Elasticsearch 7.12+ ile gelen en güçlü maliyet optimizasyonu: veriyi S3/GCS/Azure Blob üzerinde tutup, gerektiğinde local cache ile arama yapmak.
Repository Oluşturma
PUT _snapshot/my-s3-repo
{
"type": "s3",
"settings": {
"bucket": "my-es-snapshots",
"region": "eu-central-1",
"base_path": "es-frozen"
}
}Searchable Snapshot Mount
// Frozen mount (minimal local cache)
POST _snapshot/my-s3-repo/snapshot-2024.01/_mount?storage=shared_cache
{
"index": "logs-2024.01",
"renamed_index": "frozen-logs-2024.01"
}
// Full mount (tamamen local cache — warm tier gibi)
POST _snapshot/my-s3-repo/snapshot-2024.01/_mount?storage=full_copy
{
"index": "logs-2024.01"
}Frozen Tier Node Konfigürasyonu
# elasticsearch.yml — Frozen node
node.roles: [data_frozen]
# Shared cache ayarları
xpack.searchable.snapshot.shared_cache.size: 500gb
xpack.searchable.snapshot.shared_cache.size.max_headroom: 10gb6. Auto-Scaling (Elastic Cloud / Kubernetes)
Elasticsearch Autoscaling API
// Autoscaling policy oluştur
PUT _autoscaling/policy/hot-tier-policy
{
"roles": ["data_hot"],
"deciders": {
"reactive_storage": {}
}
}
// Kapasiteyi oku
GET _autoscaling/capacity
// Yanıt:
{
"policies": {
"hot-tier-policy": {
"required_capacity": {
"total": {
"storage": 5368709120,
"memory": 34359738368
},
"node": {
"storage": 1073741824,
"memory": 8589934592
}
},
"current_capacity": {
"total": {
"storage": 3221225472,
"memory": 25769803776
},
"node": {
"storage": 1073741824,
"memory": 8589934592
}
},
"current_nodes": [
{ "name": "hot-1" },
{ "name": "hot-2" },
{ "name": "hot-3" }
]
}
}
}Kubernetes ile Auto-Scaling
# ECK (Elastic Cloud on Kubernetes) örneği
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: production-cluster
spec:
version: 8.12.0
nodeSets:
- name: hot
count: 3
config:
node.roles: [data_hot, data_content, ingest]
podTemplate:
spec:
containers:
- name: elasticsearch
resources:
requests:
memory: 64Gi
cpu: 16
limits:
memory: 64Gi
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 2Ti
storageClassName: fast-ssd
- name: warm
count: 2
config:
node.roles: [data_warm]
podTemplate:
spec:
containers:
- name: elasticsearch
resources:
requests:
memory: 32Gi
cpu: 8
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 4Ti
storageClassName: standard-ssd
- name: cold
count: 2
config:
node.roles: [data_cold]
podTemplate:
spec:
containers:
- name: elasticsearch
resources:
requests:
memory: 16Gi
cpu: 4
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 8Ti
storageClassName: standard-hdd
- name: master
count: 3
config:
node.roles: [master]
podTemplate:
spec:
containers:
- name: elasticsearch
resources:
requests:
memory: 8Gi
cpu: 27. Rolling Upgrade / Rolling Restart
Node'u Güvenli Şekilde Yeniden Başlatma
// 1. Shard allocation'ı durdur
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.enable": "primaries"
}
}
// 2. Flush yap (translog temizle)
POST _flush/synced
// 3. Node'u restart et (OS level)
// systemctl restart elasticsearch
// 4. Node'un cluster'a katılmasını bekle
GET _cat/nodes?v
// 5. Cluster yellow veya green olana kadar bekle
GET _cluster/health?wait_for_status=yellow&timeout=60s
// 6. Allocation'ı geri aç
PUT _cluster/settings
{
"transient": {
"cluster.routing.allocation.enable": "all"
}
}
// 7. Green olana kadar bekle
GET _cluster/health?wait_for_status=green&timeout=5mRolling Upgrade Script (Bash)
#!/bin/bash
NODES=("data-1" "data-2" "data-3" "data-4" "data-5")
ES_URL="https://localhost:9200"
CREDS="-u elastic:changeme"
for node in "${NODES[@]}"; do
echo "=== Upgrading $node ==="
# 1. Allocation durdur
curl -s -X PUT "$ES_URL/_cluster/settings" $CREDS \
-H "Content-Type: application/json" \
-d '{"transient":{"cluster.routing.allocation.enable":"primaries"}}'
# 2. Synced flush
curl -s -X POST "$ES_URL/_flush/synced" $CREDS
# 3. Node restart (SSH ile)
ssh "$node" "sudo systemctl stop elasticsearch"
ssh "$node" "sudo yum update elasticsearch -y" # veya apt
ssh "$node" "sudo systemctl start elasticsearch"
# 4. Node'un katılmasını bekle
echo "Waiting for $node to join..."
while true; do
NODES_COUNT=$(curl -s "$ES_URL/_cat/nodes" $CREDS | wc -l)
if [ "$NODES_COUNT" -ge "${#NODES[@]}" ]; then break; fi
sleep 5
done
# 5. Allocation aç
curl -s -X PUT "$ES_URL/_cluster/settings" $CREDS \
-H "Content-Type: application/json" \
-d '{"transient":{"cluster.routing.allocation.enable":"all"}}'
# 6. Green olana kadar bekle
echo "Waiting for green..."
while true; do
STATUS=$(curl -s "$ES_URL/_cluster/health" $CREDS | jq -r '.status')
if [ "$STATUS" = "green" ]; then break; fi
sleep 10
done
echo "=== $node upgraded successfully ==="
done
echo "All nodes upgraded!"8. Capacity Planning
Temel Formül
Storage İhtiyacı = Günlük Veri × Tutma Süresi × (1 + Replica) × 1.15 (overhead)
Örnek:
50GB/gün × 90 gün × 2 (replica=1) × 1.15 = 10,350GB ≈ 10.4TBRAM Hesabı
Heap = min(31GB, Toplam RAM / 2)
OS Page Cache = Toplam RAM - Heap
Kural: Index boyutunun en az %50'si kadar OS page cache olmalı
Örnek: 2TB data/node → min 1TB RAM (OS cache) → Toplam 64GB RAM (31 heap + 33 cache)Node Sayısı
Min Node = max(Disk İhtiyacı / Disk Per Node, RAM İhtiyacı / RAM Per Node, 3)
Örnek:
Disk: 10.4TB / 2TB per node = 6 node (disk)
RAM: Yeterli (64GB/node)
HA: min 3 data + 3 master = 6
Sonuç: 6 data node + 3 master nodeJava ile Capacity Planner
public class CapacityPlanner {
public record ClusterPlan(
int hotNodes, int warmNodes, int coldNodes,
int masterNodes,
String hotSpec, String warmSpec, String coldSpec
) {}
public ClusterPlan planCluster(
double dailyIngestGB,
int retentionDays,
int hotDays, int warmDays) {
// Veri hesabı
double hotDataGB = dailyIngestGB * hotDays * 2; // replica=1
double warmDataGB = dailyIngestGB * warmDays * 2;
int coldDays = retentionDays - hotDays - warmDays;
double coldDataGB = dailyIngestGB * coldDays * 1; // replica=0
// Node hesabı (overhead %15)
double hotDiskPerNode = 2000; // 2TB NVMe
double warmDiskPerNode = 4000; // 4TB SSD
double coldDiskPerNode = 8000; // 8TB HDD
int hotNodes = Math.max(3, (int) Math.ceil(
hotDataGB * 1.15 / hotDiskPerNode));
int warmNodes = Math.max(2, (int) Math.ceil(
warmDataGB * 1.15 / warmDiskPerNode));
int coldNodes = Math.max(2, (int) Math.ceil(
coldDataGB * 1.15 / coldDiskPerNode));
return new ClusterPlan(
hotNodes, warmNodes, coldNodes, 3,
"64GB RAM, 16 core, 2TB NVMe",
"64GB RAM, 8 core, 4TB SSD",
"32GB RAM, 4 core, 8TB HDD"
);
}
}
// Kullanım:
// Günde 100GB, 1 yıl tutma, 7 gün hot, 23 gün warm
var planner = new CapacityPlanner();
var plan = planner.planCluster(100, 365, 7, 23);
System.out.println("Hot nodes: " + plan.hotNodes() + " (" + plan.hotSpec() + ")");
System.out.println("Warm nodes: " + plan.warmNodes() + " (" + plan.warmSpec() + ")");
System.out.println("Cold nodes: " + plan.coldNodes() + " (" + plan.coldSpec() + ")");
System.out.println("Master nodes: " + plan.masterNodes());9. Best Practices
✅ Yap
| Konu | Öneri |
|---|---|
| Horizontal > Vertical | Elasticsearch dağıtık — node ekle |
| Hot-warm-cold | Maliyet optimizasyonu için tier kullan |
| ILM | Tier geçişini otomatize et |
| Searchable snapshots | Frozen tier ile maliyet %90 düşer |
| Capacity planning | Büyümeyi 6 ay önceden planla |
| Rolling restart | Allocation disable → restart → enable |
| Over-provision %20 | Spike'lara hazırlıklı ol |
❌ Yapma
| Konu | Neden |
|---|---|
| Tek dev node | SPOF, scaling imkansız |
| Tüm veriyi hot'ta tut | Para israfı |
| Cold tier'da replica=1 | Gereksiz maliyet (snapshot'tan restore edebilirsin) |
| Allocation disable unutma | Restart sonrası açmazsan shard atanmaz |
| Under-provision | Disk dolunca index read-only olur |
10. Yaygın Hatalar ve Çözümleri
Hata 1: "Node Eklendi Ama Shard Taşınmıyor"
// Kontrol 1: Allocation aktif mi?
GET _cluster/settings?flat_settings=true&filter_path=*.cluster.routing.allocation.enable
// Kontrol 2: Node doğru role sahip mi?
GET _cat/nodes?v&h=name,role
// Kontrol 3: Index'in allocation filter'ı var mı?
GET products/_settings?flat_settings=true&filter_path=*.settings.index.routing.allocation*
// Çözüm: Rebalance tetikle
POST _cluster/reroute?retry_failed=trueHata 2: "Warm Tier'a Geçiş Çok Yavaş"
// Recovery hızını artır
PUT _cluster/settings
{
"transient": {
"indices.recovery.max_bytes_per_sec": "200mb",
"cluster.routing.allocation.node_concurrent_incoming_recoveries": 4
}
}Hata 3: "Rolling Restart Sırasında Veri Kaybı"
// Sorun: Allocation disable etmeden restart yapıldı
// Shard'lar hemen redistribute edildi, node geri geldiğinde tekrar taşındı
// Doğru sıra:
1. cluster.routing.allocation.enable: "primaries"
2. POST _flush/synced
3. Node restart
4. Node katılsın, health kontrolü
5. cluster.routing.allocation.enable: "all"
6. Green olmasını bekleÖzet
Horizontal scaling Elasticsearch'ün doğal büyüme yolu — node eklemek, tek node'u büyütmekten neredeyse her zaman daha etkili.
Hot-warm-cold architecture maliyet optimizasyonunun anahtarı — aynı veri yıl boyunca NVMe SSD'de durmamalı, tier'lar arası otomatik geçiş yapılmalı.
ILM + Data Tiers birlikte kullanılarak veri yaşam döngüsü tamamen otomatize edilir — hot'ta yazılır, warm'a shrink edilir, cold'a taşınır, frozen'da arşivlenir.
Searchable snapshots ile frozen tier'da veri S3 gibi ucuz storage'da tutulup gerektiğinde aranabilir — maliyet %90'a kadar düşer.
Rolling restart sırasında allocation disable → flush → restart → enable sırası izlenmezse gereksiz veri taşıma saatler sürer.
Capacity planning altın formül: Günlük veri × tutma süresi × replica × overhead. 6 ay ilerisini planlayın, %20 over-provision yapın.
AI Asistan
Sorularını yanıtlamaya hazır