EElasticsearch Handbook

TEMEL

Mapping & Veri Tipleri

Mapping, bir index'teki dokümanların yapısını tanımlar — hangi field var, tipi ne, nasıl indexlenmeli. İlişkisel veritabanlarındaki schema tanımına benzer ama daha esnektir.

Kod örneği tercihiBu sayfadaki istemci örneklerini birlikte değiştirir.
Dynamic Mapping (Otomatik) "price": 29.99 > float "name": "Nike" > text+kw "date": "2026-01" > date "active": true > boolean Production'da kullanma! Mapping explosion riski Explicit Mapping (Önerilen) price scaled_float name text + keyword created_at date (ISO8601) status keyword (enum) Tip güvenliği Performans optimizasyonu Temel Veri Tipleri textFull-text arama keywordExact match, agg long/integerTam sayı float/doubleOndalık dateTarih/zaman booleantrue/false objectJSON objesi nestedBağımsız objeler dense_vectorML embeddings

Karar Rehberi

DurumÖneriÖrnek veya gerekçe
Explicit mapping Uygun: Production index'ler E-ticaret products index
Dynamic mapping Uygun değil: Production Sadece prototyping
Strict mode Uygun: Veri tutarlılığı kritik Fintech işlem kayıtları
Runtime fields Uygun: Geçici analiz, maliyet düşürme Log analiz sırasında
Multi-field Uygun: Aynı veriyi farklı arama İsim: text + keyword
Flattened Uygun: Bilinmeyen key yapıları Kullanıcı metadata

Temel Veri Tipleri

Tip Kullanım Indexleme Örnek
text Full-text arama Analyzed (tokenize) Ürün açıklaması
keyword Exact match, sort, aggregation Not analyzed Status, email, SKU
long / integer Tam sayılar Numeric Stok miktarı
scaled_float Para birimi (scaling_factor) Numeric Fiyat (factor: 100)
date Tarih/saat Date ISO 8601 formatı
boolean True/false Boolean is_active
object İç içe JSON Flattened internally Adres bilgisi
nested Bağımsız alt dokümanlar Separate Lucene docs Ürün varyantları
geo_point Enlem/boylam Geo Mağaza konumu
dense_vector ML embeddings HNSW/flat Semantic search
completion Autocomplete FST Arama önerileri

Mapping Tanımlama

# Explicit mapping ile index oluşturma
curl -X PUT "http://localhost:9200/products" -H "Content-Type: application/json" -d'
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 1,
    "analysis": {
      "analyzer": {
        "turkish_custom": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "turkish_stop", "turkish_stemmer"]
        }
      },
      "filter": {
        "turkish_stop": { "type": "stop", "stopwords": "_turkish_" },
        "turkish_stemmer": { "type": "stemmer", "language": "turkish" }
      }
    }
  },
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "turkish_custom",
        "fields": {
          "keyword": { "type": "keyword", "ignore_above": 256 },
          "suggest": { "type": "completion" }
        }
      },
      "description": {
        "type": "text",
        "analyzer": "turkish_custom"
      },
      "category": { "type": "keyword" },
      "price": { "type": "scaled_float", "scaling_factor": 100 },
      "stock": { "type": "integer" },
      "is_active": { "type": "boolean" },
      "created_at": { "type": "date" },
      "location": { "type": "geo_point" },
      "tags": { "type": "keyword" },
      "variants": {
        "type": "nested",
        "properties": {
          "size": { "type": "keyword" },
          "color": { "type": "keyword" },
          "sku": { "type": "keyword" },
          "stock": { "type": "integer" }
        }
      }
    }
  }
}'

# Mapping görüntüleme
curl -s "http://localhost:9200/products/_mapping?pretty"

# Field ekleme (mevcut index'e)
curl -X PUT "http://localhost:9200/products/_mapping" -H "Content-Type: application/json" -d'
{
  "properties": {
    "brand": { "type": "keyword" }
  }
}'
// Index oluşturma + mapping (Fluent API)
public async Task CreateProductsIndexAsync()
{
    var response = await _client.Indices.CreateAsync("products", c => c
        .Settings(s => s
            .NumberOfShards(2)
            .NumberOfReplicas(1)
            .Analysis(a => a
                .Analyzers(an => an
                    .Custom("turkish_custom", ca => ca
                        .Tokenizer("standard")
                        .Filter("lowercase", "turkish_stop", "turkish_stemmer")))
                .TokenFilters(tf => tf
                    .Stop("turkish_stop", st => st.Stopwords("_turkish_"))
                    .Stemmer("turkish_stemmer", st => st.Language("turkish")))))
        .Mappings(m => m
            .Dynamic(DynamicMapping.Strict)
            .Properties<Product>(p => p
                .Text(t => t.Name, t => t
                    .Analyzer("turkish_custom")
                    .Fields(f => f
                        .Keyword(k => k.Name("keyword"), k => k.IgnoreAbove(256))
                        .Completion(comp => comp.Name("suggest"))))
                .Text(t => t.Description, t => t.Analyzer("turkish_custom"))
                .Keyword(t => t.Category)
                .ScaledFloat(t => t.Price, sf => sf.ScalingFactor(100))
                .IntegerNumber(t => t.Stock)
                .Boolean(t => t.IsActive)
                .Date(t => t.CreatedAt)
                .GeoPoint(t => t.Location)
                .Keyword(t => t.Tags)
                .Nested(t => t.Variants, n => n
                    .Properties<ProductVariant>(vp => vp
                        .Keyword(v => v.Size)
                        .Keyword(v => v.Color)
                        .Keyword(v => v.Sku)
                        .IntegerNumber(v => v.Stock))))));

    if (!response.IsValidResponse)
        throw new InvalidOperationException(
            "Index creation failed: " + response.DebugInformation);
}

// Mapping güncelleme (yeni field ekleme)
public async Task AddBrandFieldAsync()
{
    await _client.Indices.PutMappingAsync("products", m => m
        .Properties<Product>(p => p
            .Keyword(t => t.Brand)));
}

text vs keyword Karşılaştırması

Özellik text keyword
Indexleme Analyzed (tokenize edilir) Olduğu gibi saklanır
Arama Full-text (match, fuzzy) Exact match (term)
Sorting Hayır Doğrudan sort yapılamaz Evet Sort yapılabilir
Aggregation Hayır Agg yapılamaz Evet Agg yapılabilir
Kullanım Uzun metin, açıklama ID, status, email, enum
Wildcard match_phrase_prefix wildcard query

Örnek: E-ticaret'te ürün adı genellikle multi-field olarak tanımlanır: text olarak full-text arama için, keyword olarak exact match ve facet filtreleme için, completion olarak autocomplete için. Tek field'dan 3 farklı arama davranışı elde edersiniz.

Mapping değiştirilemez! Bir field'ın tipini değiştiremezsiniz (text → keyword). Tek yol: yeni index oluştur + reindex. Bu yüzden production'da mapping'i en baştan doğru tasarlayın. dynamic: strict kullanarak beklenmeyen field'ların indexlenmesini engelleyin.

Anti-Pattern: Dynamic Mapping in Production

# Mapping tanımlamadan doküman indexleme — TEHLİKELİ!
curl -X POST "http://localhost:9200/products/_doc/1" -H "Content-Type: application/json" -d'
{
  "name": "Nike Air",
  "price": "3499.99",
  "metadata": { "key1": "val1", "key2": "val2", "key3": "val3" }
}'
# Sorunlar:
# 1. price STRING olarak algılanır (text+keyword) → range query çalışmaz!
# 2. metadata altındaki her yeni key yeni field oluşturur → mapping explosion
# 3. 1000 field limiti aşılınca index KIRILIR
curl -X PUT "http://localhost:9200/products" -H "Content-Type: application/json" -d'
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "name": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },
      "price": { "type": "scaled_float", "scaling_factor": 100 },
      "metadata": { "type": "flattened" }
    }
  }
}'
# dynamic:strict → bilinmeyen field gelirse REJECT (400 error)
# price → doğru tip, range query çalışır
# metadata → flattened tip, mapping explosion olmaz