Tüm yazılar

Elasticsearch'te primary ve replica shard dengesi

Elasticsearch shard'ları node'lara nasıl dağıtıyor, primary'lerin dengesiz dağılması neden göründüğü kadar önemli değil ve bir node diğerlerinden fazla yük aldığında hangi ayarlara bakıyorum.

Deepinfo'da domain verilerini tuttuğumuz Elasticsearch index'inde 1.000'den fazla TLD'ye ait 400 milyonun üzerinde kayıt var. Cluster çoğu gün sessiz sedasız çalışıyor, zaten olması gereken de bu. Bir sorun çıktığında ise sebep genelde aynı oluyor: shard'lar beklediğim yerde değil ve node'lardan biri payına düşenden fazla iş yapıyor.

Bu yazıda Elasticsearch'ün primary ve replica shard'ları nasıl yerleştirdiğini, neyi dengeleyip neyi hesaba katmadığını ve gerçekten işe yarayan birkaç ayarı anlatacağım.

Primary ve replica neredeyse aynı işi yapıyor

Her index primary shard'lara bölünür, her primary'nin de replica'ları olabilir. Primary sayısı index oluşturulurken belirlenir ve split ya da shrink yapmadıkça değişmez. Replica sayısını ise istediğin zaman değiştirebilirsin:

PUT domains/_settings
{
  "index": { "number_of_replicas": 1 }
}

Bir yazma isteği önce primary'ye gelir. Primary isteği doğrular, dokümanı index'ler ve aynı işlemi replica'lara gönderir; replica'lar da dokümanı kendi tarafında yeniden index'ler. Yani replica'lar kenarda bekleyen kopyalar değil. Elasticsearch'ün document replication modelinde bir replica, indexing sırasında aşağı yukarı primary'si kadar CPU harcar. Arama istekleri ise herhangi bir kopyaya gidebilir; adaptive replica selection her isteği o an en hızlı cevap veren kopyaya yönlendirir.

Buradan iki sonuç çıkıyor:

  • Bir primary ile replica'sı hiçbir zaman aynı node'a konmaz. Tek node'lu bir cluster'da number_of_replicas: 1 ayarlarsan cluster hep yellow kalır.
  • Bir node'da "fazla primary" olması çoğu zaman göründüğü kadar büyük bir sorun değil. Asıl sorun, en çok yük alan index'in shard'larının tek bir node'da toplanması.

Allocator neyi dengeliyor?

Balancer her node'un ağırlığını birbirine yakın tutmaya çalışır. 8.5'e kadar bu ağırlık iki şeye bakılarak hesaplanıyordu: node'daki toplam shard sayısı ve her index'ten o node'a düşen shard sayısı.

cluster.routing.allocation.balance.shard: 0.45    # node başına toplam shard
cluster.routing.allocation.balance.index: 0.55    # aynı index'ten node başına shard
cluster.routing.allocation.balance.threshold: 1.0 # shard'lar taşınmadan önce izin verilen fark

8.6 ile gelen desired balance allocator bunlara disk kullanımını ve tahmini write load'u da ekledi. Ama primary ile replica arasında hâlâ bir ayrım yapmıyor; rebalance söz konusu olduğunda ikisi de sadece birer shard.

Bu ağırlıkları nadiren değiştiriyorum. Dağılım bana yanlış göründüğünde sebep neredeyse her seferinde başka bir şey çıktı: bir disk watermark'ı, bir allocation filtresi ya da node sayısına bölünmeyen bir shard sayısı.

Shard sayısını node sayısına göre seç

5 primary ve 1 replica'sı olan bir index'te toplam 10 shard kopyası vardır. 4 node'lu bir cluster'da iki node üçer, diğer ikisi ikişer kopya taşır. Bunu hiçbir ayarla düzeltemezsin, sorun aritmetikte. Yeni bir index açarken primary sayısını, primary × (replica + 1) data node sayısına tam bölünecek şekilde seçiyorum ve cluster büyüyebilsin diye biraz pay bırakıyorum.

Çok yük alan bir index için node başına bir üst sınır koymak da işe yarıyor:

PUT domains/_settings
{
  "index.routing.allocation.total_shards_per_node": 3
}

Yalnız dikkatli kullanmak lazım. Bu sınır bir node düştüğünde de geçerli; fazla sıkı tutarsan, shard'ların taşınması gereken anda unassigned kalırlar.

Shard'ların ne zaman taşınacağını belirleyen ayarlar

Pratikte en çok kurcaladığım ayarlar bunlar:

AyarVarsayılanNe işe yarar
cluster.routing.rebalance.enableallHangi shard'ların rebalance edilebileceği: all, primaries, replicas ya da none
cluster.routing.allocation.allow_rebalanceindices_all_activeRebalance, cluster'daki bütün shard'lar active olana kadar bekler
cluster.routing.allocation.cluster_concurrent_rebalance2Cluster genelinde aynı anda kaç shard taşınabileceği
cluster.routing.allocation.node_concurrent_recoveries2Bir node'da aynı anda kaç recovery çalışabileceği
indices.recovery.max_bytes_per_sec40mbNode başına recovery bant genişliği sınırı

Bu değerleri yükseltmek cluster'ın daha çabuk dengeye oturmasını sağlar, ama o sırada aramalar yavaşlar. Gün içinde varsayılanlarla çalışıyorum. Gece planlı bir rebalance yapacaksam değerleri artırıyor, iş bitince eski hâline getiriyorum.

Disk watermark'larına ayrıca değinmek gerekiyor, çünkü shard'ları kimseye sormadan taşıyorlar:

  • low (%85): node'a yeni shard atanmaz.
  • high (%90): Elasticsearch shard'ları node'dan taşımaya başlar.
  • flood_stage (%95): node'da shard'ı bulunan bütün index'ler read-only olur (index.blocks.read_only_allow_delete).

Bir node diğerlerinden hızlı doluyorsa allocator shard'ları ondan uzaklaştırıp durur. Bu durumda gördüğün "dengesizlik" aslında bir disk sorunudur.

Node'u yeniden başlatırken shard'ları boşuna taşımamak

Bir node cluster'dan ayrıldığında Elasticsearch, eksik replica'ları başka node'larda yeniden oluşturmadan önce index.unassigned.node_left.delayed_timeout kadar bekler (varsayılanı bir dakika). Restart bundan uzun sürerse veri baştan kopyalanır, node geri geldiğinde de shard'lar tekrar taşınır. Rolling restart yaparken şöyle ilerliyorum:

# Node'u durdurmadan önce: sadece primary'lerin atanmasına izin ver
curl -X PUT "$ES/_cluster/settings" -H 'Content-Type: application/json' -d '{
  "persistent": { "cluster.routing.allocation.enable": "primaries" }
}'

# ...node'u yeniden başlat, cluster'a geri katılmasını bekle...

# Sonra: varsayılana dön
curl -X PUT "$ES/_cluster/settings" -H 'Content-Type: application/json' -d '{
  "persistent": { "cluster.routing.allocation.enable": null }
}'

Bir sonraki node'a geçmeden önce de cluster'ın green olmasını bekliyorum.

Hangi shard nerede?

Çoğu sorunun cevabını üç istek veriyor:

# Node başına shard kopyaları, primary (p) ve replica (r) olarak ayrılmış
curl -s "$ES/_cat/shards?h=node,prirep" | sort | uniq -c

# Node başına disk kullanımı ve shard sayısı
curl -s "$ES/_cat/allocation?v"

# Bir shard neden orada (ya da neden orada değil)
curl -s "$ES/_cluster/allocation/explain?pretty" -H 'Content-Type: application/json' -d '{
  "index": "domains", "shard": 0, "primary": false
}'

allocation/explain'i keşke çok daha önce öğrenseydim. Tahmin yürütmek yerine, her node için hangi allocation decider'ın evet, hangisinin hayır dediğini tek tek görüyorsun.

Sırayla neye bakıyorum

  1. primary × (replica + 1) data node sayısına tam bölünüyor mu?
  2. Disk watermark'larına yaklaşan bir node var mı?
  3. Çok yük alan bir index tek bir node'da mı toplanmış? Öyleyse total_shards_per_node.
  4. Yanlış yerde duran shard için allocation/explain ne diyor?
  5. Bütün bunlardan sonra, en son: balance ağırlıkları.