Tüm yazılara dön

Elasticsearch Indexing Yavaş mı? Backpressure Rehberi

Segment üretimi merge hızını aşınca 429 rejection ve throttled write başlar. Indexing backpressure'ı teşhis ve çözüm adımlarıyla anlatıyorum.

Elasticsearch Indexing Yavaş mı? Backpressure Rehberi

"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ı:

  1. 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.
  2. Rejection'ı queue_size bü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. _forcemerge read-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

  1. 429 bir özelliktir. Client tarafında backoff ile retry edin; reddedilen bulk'ı asla sessizce düşürmeyin.
  2. Önce teşhis, sonra ayar: _cat/thread_pool/write, _nodes/stats indexing pressure, index merge istatistikleri, hot threads — bu sırayla.
  3. En yüksek kaldıraç refresh_interval. Log index'inde 1 saniyelik görünürlük bir gereksinim değil, maliyettir.
  4. Bulk boyutu kopyalanmaz, keşfedilir — throughput platoya oturana kadar benchmark.
  5. İlk yüklemede replica kapalı + refresh kapalı; iş bitince ikisini geri açın.
  6. 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.

Arama altyapınızı sınırların ötesine taşıyalım.

Yüksek performanslı ve hatasız bir arama deneyimi için hemen iletişime geçin.