"Elasticsearch yazma hızımıza yetişemiyor." Ingest ağırlıklı hemen her projede bu cümleyi duyuyorum ve genelde yanında bir stack trace geliyor: es_rejected_execution_exception, HTTP 429, Logstash veya Kafka consumer tarafında birikmiş retry'lar. İlk refleks donanımı suçlamak; gerçek hikâye neredeyse her zaman segment yaşam döngüsü.
Index'lediğiniz her doküman immutable bir Lucene segment'inin parçası olur. Refresh sürekli küçük segment'ler üretir; arka plandaki merge'ler bunları büyük segment'lerde birleştirir. Segment üretimini merge'lerin (ve disklerin) sindirebileceğinden hızlı beslerseniz Elasticsearch direnmeye başlar — önce kuyruk, sonra rejection, en sonunda yazmayı bilerek yavaşlatma. Bu rehber o sinyalleri doğru okumak ve gerçek darboğazı çözmek üzerine.
Kısa cevap: Yavaş veya reddedilen indexing bir bug değil, backpressure'dır. Önce write thread pool rejection'larına bakın (GET _cat/thread_pool/write?v), sonra merge istatistiklerine ve loglardaki "now throttling indexing" satırına. Çözüm segment üretimini azaltmak: index.refresh_interval değerini yükseltin, bulk boyutunu benchmark ile bulun, auto-generated ID kullanın, ilk yüklemede replica'ları kapatın ve client'ların 429'u backoff ile retry etmesini sağlayın. Thread pool kuyruğunu büyütmeyin — sinyali gizler, heap baskısını artırır.
Segment'ler nasıl doğar — merge neden geride kalır
Elasticsearch, aktif aranan bir shard'ı varsayılan olarak saniyede bir refresh eder (index.refresh_interval: 1s). Her refresh, in-memory indexing buffer'ı yeni bir aranabilir segment'e çevirir. Onlarca shard'da saniyelik refresh, kesintisiz bir minik segment akışı demek; Lucene'in ConcurrentMergeScheduler'ı bunları bulk trafiği devam ederken arka planda birleştirmek zorunda.
Aynı anda her operasyon translog'a da yazılır. Varsayılan index.translog.durability: request ile Elasticsearch her bulk'ı onaylamadan önce translog'u fsync eder — güvenli, ama diskleriniz sıcak yola iki kez girer: bir translog fsync'leri, bir de segment yazma ve merge'ler için.
Yani "segment yazımı yetişemiyor" aslında aynı I/O ve CPU için yarışan üç kuyruk: refresh (segment üretimi), merge (segment birleştirme) ve translog (durability). Arıza iki farklı biçimde görünür ve ikisinin çözümü farklıdır.
Rejection'ı okumak: es_rejected_execution_exception
write thread pool sabit boyutludur (allocated processor başına bir thread) ve sınırlı bir kuyruğu vardır (varsayılan 10000). Kuyruk dolduğunda Elasticsearch HTTP 429 ve es_rejected_execution_exception döner. 7.9'dan beri üstte ikinci bir koruma daha var: indexing pressure, uçuştaki indexing byte'ları indexing_pressure.memory.limit sınırını (varsayılan heap'in %10'u) aşarsa istekleri reddeder.
İlk komut her zaman bu:
GET _cat/thread_pool/write?v&h=node_name,name,active,queue,rejected,completed
rejected node açılışından beri kümülatiftir — sıfırdan büyük olmasına değil, artıp artmadığına bakın. Sonra baskının node bazında nerede oturduğuna:
GET _nodes/stats?filter_path=nodes.*.name,nodes.*.thread_pool.write,nodes.*.indexing_pressure
Rejection'ların çoğu tek node'daysa elinizde hot shard veya çarpık routing var, cluster geneli bir kapasite sorunu değil. Bütün data node'lar birlikte reject ediyorsa ingest gerçekten cluster kapasitesini aşıyordur; çözüm producer tarafında: daha az eşzamanlı bulk yükü, daha fazla node veya daha hızlı disk.
Cluster'a dokunmadan önce iki client kuralı:
- 429'u hata değil backpressure olarak ele alın. Ciddi her client (Logstash, Elastic Agent, dillerin bulk helper'ları) 429'u exponential backoff ile retry edebilir. Retry edilen 429 güvenle ertelenmiş veridir; düşürülen 429 sizin seçtiğiniz veri kaybıdır.
- Rejection'ı
queue_sizebüyüterek "çözmeyin". Daha uzun kuyruk aynı throughput, daha fazla heap ve daha kötü latency demektir. Kuyruk sadece habercidir.
Merge throttling: Lucene geri ittiğinde
İkinci arıza modu daha sessizdir. Merge'ler yeni segment hızının gerisinde kalınca Elasticsearch o shard'da indexing'i tek thread'e düşürür ve loglara now throttling indexing: numMergesInFlight=6, maxNumMerges=5 gibi bir satır yazar. Bulk latency tırmanır ama kimse 429 görmez — cluster, merge'ler yetişsin diye sizi bilerek yavaşlatıyordur.
Kontrolü:
GET my-index/_stats/merge?filter_path=indices.*.total.merges
GET _nodes/hot_threads
Merge istatistiklerinde büyüyen current sayısı ve yüksek total_throttled_time_in_millis bunu doğrular; hot threads çıktısında Lucene merge thread'lerinin başı çektiğini görürsünüz. Merge davranışını merge scheduler yönetir (index.merge.scheduler.max_thread_count, otomatik I/O throttle). Varsayılanlar SSD için doğrudur; spinning disk veya kısıtlı cloud volume üzerindeyseniz dürüst çözüm ayar değil storage'dır. NVMe üzerinde kalıcı merge throttling ise genelde tek anlama gelir: çok fazla segment üretiyorsunuz — yani sorun refresh ve bulk boyutunda.
Gerçekten fark yaratan ayarlar
Aşağıdakilerin hepsi Elastic'in kendi tune for indexing speed rehberinden; sahada en sık kazandıran sırayla.
refresh_interval'ı yükseltin
Write ağırlıklı bir index'te kimsenin 1 saniyelik arama görünürlüğüne ihtiyacı yoksa bedelini ödemeyin:
PUT logs-write/_settings
{
"index.refresh_interval": "30s"
}
Daha az refresh → daha az ve daha büyük başlangıç segment'i → dramatik ölçüde az merge işi. Saf bulk yüklemede (reindex, migration, ilk import) daha ileri gidin:
PUT bulk-load-index/_settings
{
"index": {
"refresh_interval": "-1",
"number_of_replicas": 0
}
}
Yükleme bitince ikisini de eski haline getirin. İlk yüklemede replica, her dokümanın hiçbir kazanç olmadan iki kez index'lenmesi demektir — sonradan primary'den replica recovery daha ucuzdur.
Bulk boyutunu doğru seçin
Sihirli bir bulk boyutu yok ve ben de uydurmayacağım. Benchmark yapın: küçük başlayın (birkaç yüz doküman), throughput artışı durana kadar ikiye katlayın; isteklerin http.max_content_length sınırına yaklaştığı veya indexing pressure rejection tetiklediği noktanın altında kalın. Çok küçük bulk round-trip israfıdır; çok büyük bulk heap baskısını büyütür ve her 429'un retry maliyetini artırır. Ayrıca birden fazla client worker/thread kullanın — tek bulk akışı bir cluster'ı nadiren doyurur.
Auto-generated ID kullanın
Dışarıdan verilen ID ile Elasticsearch her dokümanın zaten var olup olmadığını kontrol etmek zorundadır — her yazmaya bir okuma ekler. Pipeline'ınız idempotent upsert gerektirmiyorsa ID üretimini Elasticsearch'e bırakıp bu lookup'tan kurtulun.
Async translog durability — gözünüz açık
PUT logs-write/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "30s"
}
Bu ayar yazmaları fsync'ten önce onaylar ve translog fsync'lerini latency yolundan çıkarır. Bedeli açıktır: node ölürse sync_interval kadar onaylanmış operasyonu kaybedebilirsiniz. Kafka'dan tekrar beslenebilen log ve metric verisi için uygundur; yeniden ingest edemeyeceğiniz hiçbir veri için değil.
Yapmamanız gerekenler
- Canlı index'e force merge atmayın. İnternette dolaşan "index'lerinizi düzenli defragment edin" tavsiyesi yanlıştır; Elasticsearch'te defragmentation yoktur.
_forcemergeread-only index'ler içindir (örneğin ILM rollover sonrası). Aktif yazılan index'te devasa segment'ler yaratır ve asıl merge'lerin ihtiyacı olan I/O'yu çalar. - Thread pool kuyruğunu veya thread sayısını büyütmeyin. CPU çekirdeğine göre sabitlenmiş pool'lar bilinçli bir tasarımdır.
- Data node rejection'ını coordinating node ekleyerek çözmeye çalışmayın. Darboğaz segment'lerin yazıldığı yerdedir.
Indexing pressure sorunları cluster istikrarsızlığını da besler: merge'lerde boğulan bir node master'a geç cevap verir, cluster'dan düşebilir ve sizi "yavaş yazma"dan unassigned shard'lara taşır. Oraya vardıysanız o yolu ayrıca yazdım: unassigned shard ve RED cluster çözümü. Rejection, merge throttling ve thread pool doygunluğunu producer'larınız alarm vermeden önce görmek istiyorsanız, searchali.com/monitoring tam olarak bunu sürekli izler.
Sık Sorulan Sorular
Elasticsearch'te es_rejected_execution_exception neden olur?
Bir data node'daki write thread pool kuyruğu doldu: bulk istekleri o node'un index'leyebileceğinden hızlı geldi. Elasticsearch, client'lar geri çekilebilsin diye HTTP 429 döner. GET _cat/thread_pool/write?v ile hangi node'ların reject ettiğine bakın — tek sıcak node shard/routing çarpıklığına, tüm node'lar gerçek kapasite sınırına işaret eder.
refresh_interval'ı -1 yapmak production'da güvenli mi?
Durability açısından güvenli — dokümanlar translog'dadır, kaybolmaz — ama bir sonraki refresh'e (veya flush kaynaklı refresh'e) kadar aramada görünmezler. -1 değerini bulk yükleme ve migration için kullanın, sonra gerçek bir aralığa dönün. Sürekli write ağırlıklı index'lerde genelde 30s daha doğru dengedir.
Merge throttling'in indexing'i yavaşlattığını nasıl anlarım?
Üç sinyal: data node loglarında now throttling indexing mesajı, GET <index>/_stats/merge çıktısında büyüyen throttle_time_in_millis ve GET _nodes/hot_threads içinde başı çeken Lucene merge thread'leri. Throttling kalıcıysa segment üretimini azaltın (daha yüksek refresh_interval, daha büyük bulk) veya daha hızlı storage'a geçin.
write thread pool queue_size değerini artırmalı mıyım?
Hayır. Daha büyük kuyruk throughput eklemez; heap kullanımı ve latency ekler, producer'ların ihtiyacı olan backpressure sinyalini geciktirir. Varsayılanı koruyun, client'lara 429 için backoff'lu retry verin ve alttaki segment üretimi ya da kapasite sorununu çözün.
Öğrendiklerimiz
- 429 bir özelliktir. Client tarafında backoff ile retry edin; reddedilen bulk'ı asla sessizce düşürmeyin.
- Önce teşhis, sonra ayar:
_cat/thread_pool/write,_nodes/statsindexing pressure, index merge istatistikleri, hot threads — bu sırayla. - En yüksek kaldıraç
refresh_interval. Log index'inde 1 saniyelik görünürlük bir gereksinim değil, maliyettir. - Bulk boyutu kopyalanmaz, keşfedilir — throughput platoya oturana kadar benchmark.
- İlk yüklemede replica kapalı + refresh kapalı; iş bitince ikisini geri açın.
- Force merge yalnızca read-only index'lere. Elasticsearch'te "defragmentation" yoktur.
Kendi cluster'ınızda indexing backpressure ile mi boğuşuyorsunuz? searchali.com üzerinden ulaşın.
