Kapsamlı Elasticsearch Geliştirici Rehberi

7 Ağustos 2026 · netologist · 26 dakika, 5329 kelime ·

Elasticsearch’ün en çok kullanılan özelliklerini, best practice’leri ve gerçek dünyada kanıtlanmış pattern’leri kapsayan derin ve pratik bir referans kaynağı.


İçindekiler

  1. Temel Kavramlar ve Mimari
  2. Cluster, Node ve Shard’lar
  3. Index Yönetimi
  4. Mapping ve Veri Tipleri
  5. Metin Analizi (Analyzer, Tokenizer, Filter)
  6. CRUD ve Bulk İşlemleri
  7. Query DSL Derinlemesine
  8. Aggregation’lar
  9. Relevance (İlgililik) ve Skorlama
  10. Sayfalama (Pagination) Stratejileri
  11. Veri Modelleme Pattern’leri
  12. Index Lifecycle Management (ILM)
  13. Performans Best Practice’leri
  14. Search-as-You-Type ve Otomatik Tamamlama
  15. Geo Sorguları
  16. Güvenlik
  17. İzleme ve Sorun Giderme
  18. Yaygın Anti-Pattern’ler
  19. Client Kod Örnekleri
  20. Hızlı Referans (Cheat Sheet)

1. Temel Kavramlar ve Mimari

Elasticsearch, Apache Lucene üzerine inşa edilmiş, dağıtık (distributed), RESTful bir arama ve analiz motorudur. Veriyi JSON dokümanlar olarak saklar ve near-real-time (neredeyse gerçek zamanlı) arama sunar.

KavramAçıklama
Document (Doküman)Bir index içinde saklanan JSON nesnesi. İlişkisel veritabanındaki “satır"a benzer ama şema esnekliği vardır.
IndexOrtak bir mapping’e sahip dokümanlar koleksiyonu. Gevşek anlamda “tablo"ya benzer.
MappingBir doküman tipinin şema tanımı: alan isimleri, veri tipleri, analyzer’lar.
ShardTek bir Lucene index’i. Her ES index’i, yatay ölçeklenme için bir veya daha fazla shard’a bölünür.
ReplicaYüksek erişilebilirlik ve okuma throughput’u için bir shard’ın kopyası.
NodeÇalışan tek bir Elasticsearch örneği.
ClusterTüm veri setinizi birlikte tutan node’lar topluluğu.
SegmentBir shard içindeki değişmez (immutable) Lucene dosyası. Segment’ler zamanla merge edilir (birleştirilir).

Neden Elasticsearch?

Doküman Yaşam Döngüsü

  1. Doküman bir coordinating node‘a gönderilir.
  2. Doğru primary shard‘a yönlendirilir (_routing üzerinden, varsayılan olarak doküman _id‘sinin hash’i).
  3. Bir bellek içi buffer ve translog‘a (dayanıklılık için) indexlenir.
  4. Her refresh_interval‘da (varsayılan 1s), buffer yeni bir segment’e yazılır ve aranabilir hale gelir.
  5. Her index.translog.durability flush aralığında, veri diske fsync edilir (flush).
  6. Arka planda çalışan merge işlemi küçük segment’leri daha büyük olanlarla birleştirir ve silinmiş dokümanları temizler.

Kritik nokta: Bir doküman hemen indexlenir ancak bir sonraki refresh’e kadar aranabilir değildir. “Near real-time” ifadesinin anlamı budur.


2. Cluster, Node ve Shard’lar

Node Rolleri

node.roles: [ master, data, ingest, ml, remote_cluster_client ]
RolAmaç
masterCluster state yönetimi, index oluşturma/silme, shard allocation kararları
data (data_hot, data_warm, data_cold, data_frozen)Veriyi saklar, CRUD/arama/aggregation işlemlerini çalıştırır
ingestIngest pipeline’larını çalıştırır (indexlemeden önce ön işleme)
mlMachine learning işleri
coordinating onlyHiçbir rol atanmamış; sadece istekleri yönlendirir (özel olarak nadiren gereklidir)
remote_cluster_clientCross-cluster search/replication’ı etkinleştirir

Best practice: Production cluster’larında (>= 6-10 node), master-eligible node’ları data node’lardan ayrı tutun (3 adet, tek sayı, küçük instance, veri tutmayan) — split-brain’i ve GC baskısının cluster state’i etkilemesini önlemek için.

Shard Boyutlandırma — #1 Operasyonel Karar

Kaba formül:
number_of_shards = ceil(beklenen_index_boyutu_GB / 30GB)

Split Brain ve Quorum


3. Index Yönetimi

Açık Ayarlarla Index Oluşturma

PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "5s",
    "analysis": {
      "analyzer": {
        "custom_english": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "english_stop", "english_stemmer"]
        }
      },
      "filter": {
        "english_stop": { "type": "stop", "stopwords": "_english_" },
        "english_stemmer": { "type": "stemmer", "language": "english" }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": { "type": "text", "analyzer": "custom_english" },
      "sku": { "type": "keyword" },
      "price": { "type": "scaled_float", "scaling_factor": 100 },
      "created_at": { "type": "date" }
    }
  }
}

Index Template’leri (Composable, 7.8+‘dan beri)

Best practice: production index’lerinin asla sadece dynamic mapping’e güvenmemesi. Tutarlılık için index template’leri kullanın.

PUT _index_template/logs_template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs_policy"
    },
    "mappings": {
      "dynamic": "strict",
      "properties": {
        "@timestamp": { "type": "date" },
        "level": { "type": "keyword" },
        "message": { "type": "text" }
      }
    }
  },
  "composed_of": ["component_mappings", "component_settings"],
  "priority": 200
}

Alias’lar — Kesintisiz (Zero-Downtime) Reindexing’in Temeli

Uygulamaları asla doğrudan fiziksel bir index’e işaret etmeyin. Her zaman bir alias kullanın.

POST /_aliases
{
  "actions": [
    { "add": { "index": "products_v2", "alias": "products" } },
    { "remove": { "index": "products_v1", "alias": "products" } }
  ]
}

Reindex pattern’i (kesintisiz):

  1. Yeni mapping ile products_v2 oluşturun.
  2. products_v1products_v2 arasında POST _reindex çalıştırın.
  3. products alias’ını tek bir _aliases çağrısında atomik olarak değiştirin (eskiyi kaldır, yeniyi ekle).
  4. Doğruladıktan sonra products_v1‘i silin.
POST _reindex
{
  "source": { "index": "products_v1" },
  "dest": { "index": "products_v2" }
}

Dönüşümlü (Transform) Reindex

POST _reindex
{
  "source": { "index": "products_v1" },
  "dest": { "index": "products_v2" },
  "script": {
    "source": "ctx._source.full_name = ctx._source.first_name + ' ' + ctx._source.last_name"
  }
}

Index’leri Kapatma / Açma / Dondurma


4. Mapping ve Veri Tipleri

Temel Alan Tipleri

TipKullanım alanı
textTam metin arama, analiz edilmiş, tokenize edilmiş
keywordTam eşleşme, sıralama, aggregation, filtreleme
long/integer/short/byteTam sayılar
double/float/half_float/scaled_floatOndalık sayılar (para birimi için scaled_float kullanın)
dateISO8601 veya özel formatlar
booleantrue/false
objectJSON nesnesi (dahili olarak düzleştirilir — array ilişkilerini kaybeder)
nestedAlanlar arasındaki ilişkileri koruyan nesne array’i
geo_pointEnlem/boylam koordinatları
geo_shapeKarmaşık geometriler
ipIPv4/IPv6
completionOtomatik tamamlama önerici (suggester)
dense_vectorVektör arama / kNN / embedding’ler
flattenedTüm bir nesneyi açık alt-mapping olmadan tek bir alan olarak indexler (rastgele JSON için iyi)
joinTek bir index içinde parent/child ilişkileri
aliasBaşka bir alan ismine işaret eder
constant_keywordIndex’teki her doküman için aynı değer
rank_features / rank_featureSayısal özelliklere dayalı skorlama boost’u
search_as_you_typeOtomatik tamamlama tarzı prefix sorguları için optimize edilmiş

text vs keyword — En Önemli Ayrım

{
  "properties": {
    "title": {
      "type": "text",
      "fields": {
        "keyword": { "type": "keyword", "ignore_above": 256 }
      }
    }
  }
}

Kural: bir string alanında sıralama veya aggregation yapmanız gerekiyorsa, mutlaka bir keyword alt-alanı olmalı veya doğrudan keyword olarak map edilmelidir.

object vs nested

// object - ilişkili alan array'leri için YANLIŞ
{ "user": [ { "first": "John", "last": "Smith" }, { "first": "Alice", "last": "Doe" } ] }

Dahili olarak user.first: [John, Alice], user.last: [Smith, Doe] şeklinde düzleşir — first: John AND last: Doe sorgusu yanlışlıkla eşleşir! Nesne sınırlarını korumak için nested kullanın:

{
  "properties": {
    "user": { "type": "nested" }
  }
}

nested sorgusu ile sorgulama:

{
  "query": {
    "nested": {
      "path": "user",
      "query": {
        "bool": {
          "must": [
            { "match": { "user.first": "John" } },
            { "match": { "user.last": "Doe" } }
          ]
        }
      }
    }
  }
}

Trade-off: her nested nesne, gizli ayrı bir Lucene dokümanı olarak indexlenir — daha fazla nested nesne = daha fazla overhead. Derin nested yapılardan veya çok büyük nested array’lerden (parent başına binlerce nested doküman) kaçının.

Dynamic Mapping Kontrolü

{
  "mappings": {
    "dynamic": "strict",   // "true" | "false" | "strict"
    "properties": { ... }
  }
}

Multi-fields Pattern’i (tek bir alanda arama + sıralama + aggregation + otomatik tamamlama)

{
  "properties": {
    "title": {
      "type": "text",
      "analyzer": "standard",
      "fields": {
        "keyword": { "type": "keyword" },
        "english": { "type": "text", "analyzer": "english" },
        "autocomplete": { "type": "search_as_you_type" }
      }
    }
  }
}

Runtime Field’lar (sorgu zamanında alan tanımlama, reindex gerektirmez)

GET /products/_search
{
  "runtime_mappings": {
    "price_with_tax": {
      "type": "double",
      "script": "emit(doc['price'].value * 1.2)"
    }
  },
  "query": { "range": { "price_with_tax": { "gte": 100 } } }
}

Deneme yapmak veya nadiren kullanılan alanlar için iyidir; sorgu zamanında CPU maliyeti vardır (indexleme zamanında disk maliyetine karşı) — yüksek QPS’li hot path’lerde kullanmayın.


5. Metin Analizi

Analyzer Anatomisi

Analyzer = Karakter Filtreleri (0..n) → Tokenizer (1) → Token Filtreleri (0..n)

Analyzer’ları Test Etme (deploy etmeden önce her zaman yapın)

POST /_analyze
{
  "analyzer": "standard",
  "text": "The QUICK Brown-Foxes jumped!"
}

Otomatik Tamamlama için Özel Analyzer (edge n-gram)

PUT /articles
{
  "settings": {
    "analysis": {
      "analyzer": {
        "autocomplete_analyzer": {
          "type": "custom",
          "tokenizer": "autocomplete_tokenizer",
          "filter": ["lowercase"]
        },
        "autocomplete_search_analyzer": {
          "type": "custom",
          "tokenizer": "lowercase"
        }
      },
      "tokenizer": {
        "autocomplete_tokenizer": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 10,
          "token_chars": ["letter", "digit"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "autocomplete_analyzer",
        "search_analyzer": "autocomplete_search_analyzer"
      }
    }
  }
}

Kritik kural: sorgu zamanında (edge_ngram olmadan) farklı bir search_analyzer kullanın, aksi takdirde arama sorgusunun kendisi de n-gram’lanır ve ilgisiz eşleşmeler üretir.

Eş Anlamlılar (Synonyms)

"filter": {
  "synonym_filter": {
    "type": "synonym",
    "synonyms": [
      "laptop, notebook",
      "tv, television => television"
    ]
  }
}

Çok kelimeli eş anlamlılar için doğru phrase sorgu desteğiyle synonym_graph + multiplexer kullanın.

Dile Özgü Analiz

Elasticsearch, her dil için stemming ve stopword’leri doğru şekilde işleyen hazır dil analyzer’ları (english, turkish, french, vb.) sunar. Manuel özel stemmer oluşturmak yerine bunları tercih edin.

{ "type": "text", "analyzer": "turkish" }

Not: Türkçe metinler için turkish analyzer’ı kullanmak, Türkçe’ye özgü ekleri (çekim ekleri) ve büyük/küçük harf kurallarını (örn. “İ”/“i” ve “I”/“ı” ayrımı) doğru şekilde ele alır. Standart standard analyzer’ı Türkçe için yetersiz kalabilir.


6. CRUD ve Bulk İşlemleri

Tekil Doküman İşlemleri

PUT /products/_doc/1
{ "name": "Wireless Mouse", "price": 25.99 }

GET /products/_doc/1

POST /products/_update/1
{ "doc": { "price": 22.99 } }

DELETE /products/_doc/1

Optimistic Concurrency Control (İyimser Eşzamanlılık Kontrolü)

PUT /products/_doc/1?if_seq_no=10&if_primary_term=1

Eşzamanlı yazma senaryolarında kayıp güncellemeleri önlemek için _seq_no + _primary_term kullanın (bu amaç için eski version yaklaşımının yerini alan modern yöntem).

Bulk API — Birden Fazla Doküman için Her Zaman Bunu Kullanın

POST /_bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "name": "Mouse", "price": 25.99 }
{ "update": { "_index": "products", "_id": "2" } }
{ "doc": { "price": 19.99 } }
{ "delete": { "_index": "products", "_id": "3" } }

Bulk indexleme için best practice’ler:

Update by Query / Delete by Query

POST /products/_update_by_query
{
  "query": { "term": { "category": "electronics" } },
  "script": { "source": "ctx._source.price *= 1.1" }
}

POST /products/_delete_by_query
{
  "query": { "range": { "created_at": { "lt": "now-1y" } } }
}

Bunlar kaynak açısından yoğun işlemlerdir (aslında arka planda reindex gibi çalışır) — requests_per_second ile throttle edin ve paralellik için slices kullanmayı düşünün.


7. Query DSL Derinlemesine

Query Context vs Filter Context

Skorlama gerektirmeyen koşulları her zaman must yerine filter içine koyun.

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "description": "wireless mouse" } }
      ],
      "filter": [
        { "term": { "category": "electronics" } },
        { "range": { "price": { "gte": 10, "lte": 100 } } }
      ],
      "should": [
        { "match": { "brand": "logitech" } }
      ],
      "must_not": [
        { "term": { "discontinued": true } }
      ]
    }
  }
}
ClauseSkorlamaAmaç
mustevetAND, skora katkıda bulunur
filterhayır (cache’lenir)AND, skora katkı yok — hızlı
shouldevetOR, eşleşirse skoru artırır (veya must yoksa OR gibi davranır)
must_nothayırNOT, cache’lenir

Tam Metin Sorguları

// match - standart analiz edilmiş tam metin sorgusu
{ "match": { "title": "quick brown fox" } }

// match_phrase - terimlerin tam sırası
{ "match_phrase": { "title": "quick brown fox" } }

// match_phrase_prefix - phrase + son terimde prefix (otomatik tamamlama)
{ "match_phrase_prefix": { "title": "quick bro" } }

// multi_match - boost ile birden fazla alanda arama
{
  "multi_match": {
    "query": "wireless mouse",
    "fields": ["title^3", "description", "tags^2"],
    "type": "best_fields"
  }
}

// query_string / simple_query_string - kullanıcı tarafından yazılan sorgu sözdizimi (Google benzeri)
{ "simple_query_string": { "query": "wireless +mouse -wired", "fields": ["title", "description"] } }

multi_match tipleri:

TipDavranış
best_fields (varsayılan)Tek en iyi eşleşen alanın skorunu kullanır
most_fieldsEşleşen tüm alanların skorlarını birleştirir — aynı metnin farklı alanlarda farklı analiz edildiği durumlarda iyidir
cross_fieldsBirden fazla alanı tek büyük bir alan gibi ele alır (first_name/last_name arasında isim araması için iyi)
phraseHer alanda match_phrase çalıştırır
bool_prefixmatch_bool_prefix çalıştırır — search-as-you-type için iyi

Term-Level Sorguları (tam değerler, analiz edilmemiş)

{ "term": { "status.keyword": "active" } }
{ "terms": { "status.keyword": ["active", "pending"] } }
{ "range": { "price": { "gte": 10, "lt": 100 } } }
{ "exists": { "field": "email" } }
{ "prefix": { "sku": "AB-" } }
{ "wildcard": { "sku": "AB-*" } }
{ "fuzzy": { "name": { "value": "quikc", "fuzziness": "AUTO" } } }
{ "ids": { "values": ["1", "2", "3"] } }

text alanında asla term sorgusu çalıştırmayın — alan indexleme zamanında analiz edilir/küçük harfe çevrilir, bu yüzden büyük/küçük harf duyarlı tam terimler eşleşmez. .keyword alt-alanlarını kullanın.

Bileşik (Compound) Sorgular

// boosting - "negative" ile eşleşen dokümanların skorunu düşür ama dışlama
{
  "boosting": {
    "positive": { "match": { "title": "apple" } },
    "negative": { "match": { "title": "fruit" } },
    "negative_boost": 0.2
  }
}

// constant_score - bir filtreyi sar, sabit skor ver (skorlamayı tamamen atlamak için kullanılır)
{ "constant_score": { "filter": { "term": { "status": "active" } }, "boost": 1.2 } }

// dis_max - clause'lar arasında toplam değil maksimum skoru al ("farklı alan anlamları arasında OR" için en iyisi)
{
  "dis_max": {
    "queries": [
      { "match": { "title": "star wars" } },
      { "match": { "description": "star wars" } }
    ],
    "tie_breaker": 0.3
  }
}

Sıralama (Sorting)

{
  "sort": [
    { "price": "asc" },
    { "_score": "desc" },
    { "created_at": { "order": "desc", "missing": "_last" } }
  ]
}

text alanlarında doğrudan sıralamaya izin verilmez — .keyword veya fielddata: true kullanın (fielddata’dan kaçının, bellek açısından pahalıdır).

Highlighting (Vurgulama)

{
  "query": { "match": { "description": "wireless mouse" } },
  "highlight": {
    "fields": { "description": { "fragment_size": 150, "number_of_fragments": 3 } }
  }
}

8. Aggregation’lar

Metric Aggregation’lar

{
  "aggs": {
    "avg_price": { "avg": { "field": "price" } },
    "price_stats": { "stats": { "field": "price" } },
    "unique_categories": { "cardinality": { "field": "category.keyword" } },
    "percentiles_price": { "percentiles": { "field": "price", "percents": [50, 95, 99] } }
  }
}

cardinality yaklaşık bir değerdir (HyperLogLog++) — hassasiyeti precision_threshold ile ayarlayın (bellek trade-off’u).

Bucket Aggregation’lar

{
  "aggs": {
    "by_category": {
      "terms": { "field": "category.keyword", "size": 10 },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } }
      }
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 50 },
          { "from": 50, "to": 200 },
          { "from": 200 }
        ]
      }
    },
    "sales_over_time": {
      "date_histogram": {
        "field": "created_at",
        "calendar_interval": "month"
      }
    }
  }
}

Pipeline Aggregation’lar (diğer aggregation’ların sonuçları üzerinde aggregation)

{
  "aggs": {
    "sales_per_month": {
      "date_histogram": { "field": "date", "calendar_interval": "month" },
      "aggs": { "total_sales": { "sum": { "field": "amount" } } }
    },
    "max_monthly_sales": {
      "max_bucket": { "buckets_path": "sales_per_month>total_sales" }
    },
    "cumulative_sales": {
      "cumulative_sum": { "buckets_path": "sales_per_month>total_sales" }
    }
  }
}

terms Aggregation Doğruluğu — Önemli Tuzak

Sharded bir index üzerinde terms aggregation’ı varsayılan olarak yaklaşıktır (her shard kendi top N’ini döner, sonra coordinator birleştirir). Yüksek kardinaliteli alanlarda doğru sonuçlar için:

{
  "terms": {
    "field": "category.keyword",
    "size": 10,
    "shard_size": 100
  }
}

doc_count hatasını azaltmak (ortadan kaldırmak değil) için shard_size‘ı size‘dan çok daha büyük yapın. Yanıttaki sum_other_doc_count ve doc_count_error_upper_bound alanlarını kontrol edin.

filter/filters ve composite Aggregation’lar

// composite - tüm bucket kombinasyonları arasında sayfalama (tüm agg verisini export etmek için harika)
{
  "aggs": {
    "my_buckets": {
      "composite": {
        "size": 1000,
        "sources": [
          { "category": { "terms": { "field": "category.keyword" } } },
          { "month": { "date_histogram": { "field": "date", "calendar_interval": "month" } } }
        ]
      }
    }
  }
}

Tüm kombinasyonları numaralandırmanız gerektiğinde derinlemesine nested terms agg’ları yerine composite kullanın (normal terms‘ten farklı olarak sayfalama için after destekler).

Sadece Aggregation İçin search.size: 0

{ "size": 0, "aggs": { ... } }

Sadece aggregation sonuçlarını istediğinizde her zaman size: 0 ayarlayın — gereksiz hit çekme/serileştirmeyi önler.


9. Relevance (İlgililik) ve Skorlama

BM25 (ES 5.0’dan beri varsayılan benzerlik algoritması)

Skor kabaca şu fonksiyona dayanır:

PUT /products
{
  "settings": {
    "index": {
      "similarity": {
        "custom_bm25": {
          "type": "BM25",
          "b": 0.75,
          "k1": 1.2
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "description": { "type": "text", "similarity": "custom_bm25" }
    }
  }
}

function_score — İlgililiği İş Mantığıyla Birleştirme

{
  "query": {
    "function_score": {
      "query": { "match": { "title": "laptop" } },
      "functions": [
        { "filter": { "term": { "featured": true } }, "weight": 2 },
        { "field_value_factor": { "field": "sales_count", "modifier": "log1p", "factor": 0.1 } },
        { "gauss": { "created_at": { "origin": "now", "scale": "10d", "decay": 0.5 } } }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  }
}

Yaygın kullanım: tazelik (freshness) azalması (gauss/exp/linear decay fonksiyonları), popülerlik boost’u, manuel sabitleme (pinning).

Rescoring (pahalı skorlamayı sadece en üstteki N sonuca uygulama)

{
  "query": { "match": { "title": "laptop" } },
  "rescore": {
    "window_size": 100,
    "query": {
      "rescore_query": { "match_phrase": { "title": { "query": "gaming laptop", "slop": 2 } } },
      "query_weight": 0.7,
      "rescore_query_weight": 1.2
    }
  }
}

Pahalı sorguları (phrase matching, learning-to-rank, vektör yeniden sıralama) tüm sonuç kümesi yerine sadece üst pencereye uygulamak için rescoring kullanın — büyük performans kazancı.

explain API — İlgililik Hata Ayıklama

GET /products/_explain/1
{ "query": { "match": { "title": "wireless mouse" } } }

Ayrıca normal bir _search isteği içinde "explain": true, her hit için skorlama detayını gösterir — relevance ayarı yaparken gereklidir.


10. Sayfalama (Pagination) Stratejileri

YöntemKullanım alanıKısıtlama
from + sizeKüçük sonuç kümeleri, UI sayfalaması (1-100. sayfa)Derin sayfalama (from > 10.000) pahalıdır/varsayılan olarak engellenir (index.max_result_window)
search_afterDerin sayfalama, gerçek zamanlı “sonraki sayfa”Kararlı bir sort gerektirir (genelde _shard_doc/_id tie-breaker ile); rastgele sayfaya atlama yok
Scroll APITam veri export’u / reindex benzeri batch işlemeKullanıcıya yönelik sayfalama için değil; bir point-in-time snapshot’ı açık tutar (kaynak maliyeti); PIT tarafından yerini alıyor
Point in Time (PIT) + search_afterScroll’un modern yerine geçeni; sayfalanmış istekler arasında tutarlı görünümBiraz daha fazla kurulum (PIT aç, PIT kapat)

PIT ile search_after (Önerilen Modern Pattern)

POST /products/_pit?keep_alive=1m

GET /_search
{
  "size": 100,
  "query": { "match_all": {} },
  "pit": { "id": "<pit_id>", "keep_alive": "1m" },
  "sort": [ { "created_at": "asc" }, { "_shard_doc": "asc" } ]
}

Sonraki istekte search_after değeri olarak son hit’in sort değerlerini kullanın.

{
  "search_after": [1622512800000, 987654],
  "sort": [ { "created_at": "asc" }, { "_shard_doc": "asc" } ]
}

İşiniz bitince PIT’i DELETE /_pit ile kapatın.

Best practice: from + size değerinin asla index.max_result_window‘u (varsayılan 10.000) aşmasına izin vermeyin — hata fırlatır ve sayfalama tasarımı için kırmızı bir bayraktır.


11. Veri Modelleme Pattern’leri

Denormalizasyon Elasticsearch’te Normaldir

İlişkisel veritabanlarının aksine, ES’te ölçekte index’ler arası join yoktur. İlişkili veriyi indexleme zamanında dokümanın içine denormalize edin.

{
  "order_id": "1001",
  "customer": { "id": "55", "name": "Jane Doe", "tier": "gold" },
  "items": [
    { "sku": "A1", "name": "Widget", "qty": 2, "price": 9.99 }
  ]
}

Parent/Child (join alanı) — İlişkileri Modellemek Zorunda Olduğunuzda

Sadece child’lar parent’lardan çok daha sık güncellendiğinde kullanın (parent’ı reindexlemeyi önler).

PUT /forum
{
  "mappings": {
    "properties": {
      "join_field": { "type": "join", "relations": { "question": "answer" } }
    }
  }
}

Trade-off: daha yavaş sorgular (has_child/has_parent), parent+child için aynı shard routing gerektirir. Okuma ağırlıklı, nadiren güncellenen ilişkiler için nested‘i, çoğu durum için denormalizasyonu tercih edin.

Nested vs Parent/Child vs Denormalize — Karar Tablosu

PatternGüncelleme sıklığıSorgu karmaşıklığıPerformansNe zaman kullanılır
DenormalizeHer parent güncellemesinde veri çoğaltılırBasitEn hızlı okumaVarsayılan seçim — çoğu durum
nestedHerhangi bir nested değişikliğinde tüm parent doküman reindexlenirOrta (nested sorgusu)İyiÇoğunlukla statik, küçük-orta boy ilişkili nesne array’leri
join (parent/child)Child’lar bağımsız güncellenirKarmaşık (has_child)Daha yavaşChild’lar parent’tan çok daha sık güncellendiğinde, büyük 1:çok ilişkiler

Time-Series / Log Pattern’i: Zaman Dilimi Başına Index

logs-2026.08.01
logs-2026.08.02
logs-2026.08.03

Bir alias (logs-write) ve ILM rollover ile birleştirildiğinde — eski verinin kolayca silinmesini sağlar (tüm index’i düşürmek delete_by_query‘e göre çok daha hızlıdır) ve hot/warm/cold tier’ları index yaşına göre izole eder. Bu, modern data streams özelliğinin temelidir.

Data Streams (bu pattern üzerine inşa edilmiştir, 7.9+‘dan beri)

PUT _index_template/logs-template
{
  "index_patterns": ["logs-myapp-*"],
  "data_stream": {},
  "template": {
    "settings": { "index.lifecycle.name": "logs-policy" }
  }
}

POST /logs-myapp-default/_doc
{ "@timestamp": "2026-08-17T10:00:00Z", "message": "hello" }

Data stream’ler, bir dizi gizli backing index’i, rollover’ı otomatik olarak yönetir ve time-series ingest işlemlerini basitleştirir — log/metrik için manuel olarak yönetilen günlük index pattern’lerine göre tercih edilir.

Alan Patlaması — Mapping Büyümesine Dikkat

Varsayılan index.mapping.total_fields.limit 1000’dir. Rastgele JSON’ın (örn. kullanıcı tarafından sağlanan metadata) dynamic mapping’i bunu patlatabilir. Bunların dinamik olarak map edilmesine izin vermek yerine rastgele/değişken JSON nesneleri için flattened tipini kullanın:

{ "metadata": { "type": "flattened" } }

12. Index Lifecycle Management (ILM)

ILM, index’leri yaş/boyut/doküman sayısına göre hot → warm → cold → frozen → delete fazları arasında otomatik olarak taşır.

PUT _ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": { "max_primary_shard_size": "30gb", "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 }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": { "set_priority": { "priority": 0 }, "freeze": {} }
      },
      "delete": {
        "min_age": "90d",
        "actions": { "delete": {} }
      }
    }
  }
}

Ana aksiyonlar:

Best practice: time-series veriyi asla delete_by_query ile manuel silmeyin — her zaman zaman-dilimi-başına-index + ILM delete fazı olarak yapılandırın (tüm bir index’i düşürmek anındadır; delete_by_query pahalıdır ve tombstone bırakır).


13. Performans Best Practice’leri

İndexleme Performansı

Arama Performansı

Donanım ve JVM

Circuit Breaker’lar

Elasticsearch’ün OOM’u önlemek için yerleşik circuit breaker’ları vardır (indices.breaker.total.limit, fielddata, request). Sık sık circuit_breaking_exception alıyorsanız, sadece limitleri yükseltmek yerine sorgu pattern’lerini (büyük aggregation’lar, fielddata kullanımı) araştırın.


14. Search-as-You-Type ve Otomatik Tamamlama

Seçenek 1: search_as_you_type alan tipi (en basit)

{
  "properties": {
    "title": { "type": "search_as_you_type" }
  }
}
{
  "query": {
    "multi_match": {
      "query": "wirele mo",
      "type": "bool_prefix",
      "fields": ["title", "title._2gram", "title._3gram"]
    }
  }
}

Seçenek 2: completion suggester (en hızlı, bellek içi FST yapısı)

{
  "properties": {
    "suggest": { "type": "completion" }
  }
}
{
  "suggest": {
    "product-suggest": {
      "prefix": "wirel",
      "completion": { "field": "suggest", "fuzzy": { "fuzziness": 1 }, "size": 5 }
    }
  }
}

En iyi kullanım: milisaniye gecikmeli dropdown tarzı otomatik tamamlama. Kısıtlama: tam bir sorgudan daha az esnek sıralama/filtreleme.

Seçenek 3: Edge n-gram özel analyzer (bkz. bölüm 5)

En iyi kullanım: relevance skorlaması/filtreleme ile birleştirilmiş tam metin tarzı prefix eşleştirme.

Öneri: saf otomatik tamamlama widget’ları için completion, diğer filtreler/skorlama ile birleştirilmesi gerektiğinde edge_ngram veya search_as_you_type kullanın.


15. Geo Sorguları

{
  "properties": {
    "location": { "type": "geo_point" }
  }
}
// geo_distance filtresi
{
  "query": {
    "bool": {
      "filter": {
        "geo_distance": {
          "distance": "10km",
          "location": { "lat": 40.73, "lon": -73.99 }
        }
      }
    }
  }
}

// mesafeye göre sıralama
{
  "sort": [
    {
      "_geo_distance": {
        "location": { "lat": 40.73, "lon": -73.99 },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

// geo_bounding_box - hızlı dikdörtgen filtre
{ "query": { "geo_bounding_box": { "location": { "top_left": { "lat": 41, "lon": -74.5 }, "bottom_right": { "lat": 40.5, "lon": -73.5 } } } } }

Karmaşık poligonlar/şekiller için, geo_shape sorgusu ile geo_shape alan tipini kullanın (intersects, within, contains, disjoint destekler).


16. Güvenlik

Temel Yapı Taşları (X-Pack Security, 7.1’den beri temel özellikler için ücretsiz dahildir)

POST /_security/role/read_only_products
{
  "indices": [
    {
      "names": ["products"],
      "privileges": ["read"],
      "field_security": { "grant": ["name", "price", "category"] },
      "query": { "term": { "public": true } }
    }
  ]
}

Bu, tek bir rolde alan seviyesi güvenliki (sadece belirli alanları göster) ve doküman seviyesi güvenliki (satır seviyesi filtreleme) birleştirir.

API Key’ler (servis-servis kimlik doğrulaması için basic auth’a göre tercih edilir)

POST /_security/api_key
{
  "name": "my-app-key",
  "role_descriptors": {
    "app_role": { "indices": [ { "names": ["products"], "privileges": ["read"] } ] }
  },
  "expiration": "30d"
}

Best Practice’ler


17. İzleme ve Sorun Giderme

Temel Cluster API’leri

GET /_cluster/health?level=indices
GET /_cluster/state
GET /_cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m
GET /_cat/indices?v&s=store.size:desc
GET /_cat/shards?v&h=index,shard,prirep,state,docs,store,node
GET /_nodes/stats
GET /_nodes/hot_threads
GET /_cat/thread_pool/write?v
GET /_cat/pending_tasks?v

Cluster Health Renkleri

DurumAnlamı
GreenTüm primary + replica shard’lar allocate edilmiş
YellowTüm primary’ler allocate edilmiş, bazı replica’lar değil (tek node’lu dev cluster’larda yaygın)
RedBazı primary shard’lar allocate edilmemiş — etkilenen index’lerde veri kaybı riski / sorgular başarısız oluyor

Slow Log

PUT /products/_settings
{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.fetch.warn": "1s",
  "index.indexing.slowlog.threshold.index.warn": "2s"
}

Tam profiling overhead’i olmadan production’da yavaş sorguları/indexleme işlemlerini tespit etmek için kullanın.

Profile API (derin sorgu performans analizi)

GET /products/_search
{
  "profile": true,
  "query": { "match": { "title": "laptop" } }
}

Clause başına zamanlama detayını gösterir (Lucene seviyesinde) — az kullanın, overhead ekler, production hot path’leri için değil.

Yaygın Hatalar ve Çözümleri

HataNedeniÇözüm
circuit_breaking_exceptionSorgu/agg çok fazla bellek kullanıyorAgg boyutunu/kardinalitesini azaltın, filter ekleyin, heap artırın, fielddata’yı kontrol edin
es_rejected_execution_exceptionThread pool kuyruğu dolu (bulk/search)Backoff + tekrar dene, bulk boyutunu/eşzamanlılığını azaltın, node’ları ölçeklendirin
mapper_parsing_exceptionIndexleme sırasında tip uyuşmazlığıKaynak veriyi veya mapping’i düzeltin; ignore_malformed‘ı düşünün
version_conflict_engine_exceptionEşzamanlı güncelleme yarışı (race)retry_on_conflict ile tekrar deneyin, veya optimistic concurrency’yi doğru kullanın
search_phase_execution_exceptionAlttaki shard hataları_cluster/health, node loglarını, allocate edilmemiş shard’ları kontrol edin
Cluster yellow/red’de takılı kalıyorUnassigned shard’larGET _cluster/allocation/explain
GET /_cluster/allocation/explain

Bu, shard’ların neden allocate olmadığını (disk watermark, node filtreleme, eksik node, vb.) teşhis etmenin #1 aracıdır.


18. Yaygın Anti-Pattern’ler

Anti-PatternNeden kötüBunun yerine yapın
Binlerce index, her biri küçük tek shard’lıCluster state şişmesi, shard başına overheadData stream’ler / ILM rollover kullanın, konsolide edin
text alanında term sorgusu kullanmakSessizce beklenen şekilde asla eşleşmez.keyword alt-alanını kullanın
Derin from/size sayfalamaPahalı, coordinating node’da bellek yoğunsearch_after + PIT
Öngörülemeyen girdi ile production’da dynamic mappingAlan patlaması, mapping çakışmaları, index bozulma riskiAçık mapping’ler + dynamic: strict, veya flattened tipi
Kullanıcıya yönelik sayfalama için scroll kullanmakKaynak sızıntısı, stateful, canlı veriyi yansıtmazsearch_after / PIT
Devasa sınırsız array’ler / nested nesneler saklamakDoküman başına ciddi overheadYeniden yapılandırın, array boyutlarını sınırlayın, veya ayrı index kullanın
Başında * olan wildcard sorgusuTam index taramasına benzer maliyetngram alanı kullanın veya yeniden yapılandırın
Bulk yükleme sırasında refresh_interval‘ı görmezden gelmekGereksiz segment oluşturma, daha yavaş indexlemeYükleme sırasında -1 ayarlayın
ES’i sistem-of-record / birincil DB olarak kullanmakGerçek ACID transaction/join yok; yanlış yapılandırmada veri kaybı riskiBir source-of-truth DB tutun; ES’i arama/analitik katmanı olarak kullanın
Disk watermark’larını izlememekCluster beklenmedik şekilde read-only’e geçer%85/%90/%95 watermark’larda alert kurun
Her şeyin must içinde olduğu tek dev bir bool sorgusuCache yeniden kullanımı yok, gereksiz skorlamaSkorlama gerekmeyen yerlerde filter‘a ayırın
_all alanını veya tüm alanlarda aşırı geniş multi_match kullanmakYavaş, düşük relevanceUygun boost’larla aranabilir alanları açıkça tanımlayın

19. Client Kod Örnekleri

Python (elasticsearch-py, 8.x client)

from elasticsearch import Elasticsearch

es = Elasticsearch(
    "https://localhost:9200",
    api_key="base64_api_key",
)

# Doküman indexleme
es.index(index="products", id="1", document={"name": "Mouse", "price": 25.99})

# Arama
resp = es.search(
    index="products",
    query={"bool": {"must": [{"match": {"name": "mouse"}}], "filter": [{"range": {"price": {"lte": 50}}}]}},
    size=10,
)
for hit in resp["hits"]["hits"]:
    print(hit["_source"])

# Bulk
from elasticsearch.helpers import bulk

actions = [
    {"_index": "products", "_id": str(i), "_source": {"name": f"Item {i}", "price": i}}
    for i in range(1000)
]
bulk(es, actions)

Node.js (@elastic/elasticsearch)

const { Client } = require('@elastic/elasticsearch');
const client = new Client({ node: 'https://localhost:9200', auth: { apiKey: 'base64_api_key' } });

await client.index({
  index: 'products',
  id: '1',
  document: { name: 'Mouse', price: 25.99 },
});

const result = await client.search({
  index: 'products',
  query: {
    bool: {
      must: [{ match: { name: 'mouse' } }],
      filter: [{ range: { price: { lte: 50 } } }],
    },
  },
});
console.log(result.hits.hits);

Java (Java API Client, 8.x)

ElasticsearchClient client = new ElasticsearchClient(transport);

IndexResponse response = client.index(i -> i
    .index("products")
    .id("1")
    .document(new Product("Mouse", 25.99))
);

SearchResponse<Product> search = client.search(s -> s
    .index("products")
    .query(q -> q.match(m -> m.field("name").query("mouse"))),
    Product.class
);

20. Hızlı Referans (Cheat Sheet)

# Cluster
GET /_cluster/health
GET /_cat/indices?v
GET /_cat/nodes?v
GET /_cluster/allocation/explain

# Index yönetimi
PUT /my_index
DELETE /my_index
POST /my_index/_close
POST /my_index/_open
GET /my_index/_mapping
PUT /my_index/_settings

# Alias'lar
POST /_aliases
GET /_alias/my_alias

# CRUD
PUT /my_index/_doc/1
GET /my_index/_doc/1
POST /my_index/_update/1
DELETE /my_index/_doc/1
POST /_bulk

# Arama
GET /my_index/_search
GET /my_index/_search?q=title:laptop
POST /my_index/_search { query, aggs, sort, size, from, _source }

# Reindex
POST /_reindex
POST /my_index/_update_by_query
POST /my_index/_delete_by_query

# ILM
GET /_ilm/policy
PUT /_ilm/policy/my_policy
POST /my_index/_ilm/retry

# Analyze
POST /_analyze { "analyzer": "standard", "text": "..." }

Daha Fazla Okuma


Bu rehber, Elasticsearch 8.x ile tutarlı pattern ve API’leri yansıtır. Çalıştırdığınız versiyona göre tam sözdizimini her zaman doğrulayın — bazı seçenekler (örn. _type, eski scroll varsayılanları) 7.x öncesi versiyonlardan önemli ölçüde farklıdır.