← Kursa Dön
📄 Text · 35 min

Multi-field Mapping — keyword + text Stratejileri

Giriş — Bir Fotoğrafın Birden Fazla Kullanımı

Bir düğün fotoğrafı çektirdiniz diyelim. O fotoğrafın orijinal halini albüme koyarsınız, küçültülmüş halini Instagram'a yüklersiniz, kırpılmış halini profil fotoğrafı yaparsınız. Aynı fotoğraf, farklı amaçlar için farklı şekillerde kullanılır — ama kaynak aynıdır.

Elasticsearch'te de bir alanın (field) değerini birden fazla şekilde index'leyebilirsiniz. Aynı title alanı hem full-text arama için text olarak, hem exact match ve aggregation için keyword olarak, hem de autocomplete için edge_ngram ile index'lenebilir. Buna multi-field mapping denir ve Elasticsearch'ün en güçlü mapping özelliklerinden biridir.


1. Multi-field Mapping Nedir?

Multi-field mapping, tek bir kaynak alanın birden fazla farklı konfigürasyonla index'lenmesini sağlar. Ana field'a ek olarak .fields altında alt alanlar tanımlanır:

PUT my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "turkish": {
            "type": "text",
            "analyzer": "turkish"
          }
        }
      }
    }
  }
}

Bu mapping'de title alanı üç farklı şekilde erişilebilir:

Erişim YoluTipAnalyzerKullanım
titletextstandardGenel full-text arama
title.keywordkeywordExact match, aggregation, sorting
title.turkishtextturkishTürkçe stemming ile arama

2. Neden Multi-field Gerekli?

Problem: Tek Field, Çoklu İhtiyaç

Bir e-ticaret sitesinde brand alanını düşünün:

  • Arama: "samsu" yazınca "Samsung" bulunmalı → text type gerekir

  • Filtreleme: Marka = "Samsung" (exact match) → keyword type gerekir

  • Aggregation: En popüler 10 marka → keyword type gerekir

  • Sıralama: Markaya göre A-Z sırala → keyword type gerekir

Tek bir field type ile tüm bunları yapamazsınız:

// ❌ Sadece text — aggregation ve exact match yapılamaz
"brand": { "type": "text" }

// ❌ Sadece keyword — full-text arama yapılamaz
"brand": { "type": "keyword" }

Çözüm: Multi-field

// ✅ Her iki ihtiyacı da karşılar
"brand": {
  "type": "text",
  "fields": {
    "raw": {
      "type": "keyword"
    }
  }
}

Artık:

  • brand → full-text arama için

  • brand.raw → aggregation, sorting, exact match için


3. Dynamic Mapping ve Multi-field

Elasticsearch'ün dynamic mapping özelliği, string tipindeki alanları otomatik olarak multi-field yapar:

// Doküman ekle (mapping yok)
POST auto_index/_doc/1
{
  "title": "Elasticsearch Temelleri",
  "category": "Teknoloji"
}

// Mapping'i kontrol et
GET auto_index/_mapping

Sonuç:

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

Elasticsearch otomatik olarak her string alanı text + keyword multi-field olarak oluşturdu. ignore_above: 256 parametresi, 256 karakterden uzun değerlerin keyword olarak index'lenmemesini sağlar (çok uzun string'ler keyword'de anlamsızdır).

⚠️ Dikkat: Dynamic mapping prototipler için uygundur ama production'da explicit mapping tanımlayın. Dynamic mapping gereksiz field'lar oluşturabilir ve mapping explosion'a yol açabilir.


4. Multi-field Kullanım Kalıpları

4.1 text + keyword (En Yaygın)

PUT products_index
{
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "turkish",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      }
    }
  }
}

Kullanım:

// Full-text arama
GET products_index/_search
{
  "query": {
    "match": {
      "product_name": "akıllı telefon"
    }
  }
}

// Exact match
GET products_index/_search
{
  "query": {
    "term": {
      "product_name.keyword": "Samsung Galaxy S24 Ultra"
    }
  }
}

// Aggregation
GET products_index/_search
{
  "size": 0,
  "aggs": {
    "popular_products": {
      "terms": {
        "field": "product_name.keyword",
        "size": 10
      }
    }
  }
}

// Sorting
GET products_index/_search
{
  "sort": [
    { "product_name.keyword": "asc" }
  ]
}

4.2 text + text (Farklı Analyzer'lar)

PUT multilang_index
{
  "mappings": {
    "properties": {
      "description": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "tr": {
            "type": "text",
            "analyzer": "turkish"
          },
          "en": {
            "type": "text",
            "analyzer": "english"
          }
        }
      }
    }
  }
}

Kullanım — Çok dilli arama:

GET multilang_index/_search
{
  "query": {
    "multi_match": {
      "query": "geliştiriciler",
      "fields": ["description", "description.tr^2", "description.en"],
      "type": "best_fields"
    }
  }
}

description.tr^2 ile Türkçe field'a 2x boost verildi. Türkçe stemming eşleşmesi daha yüksek skor alır.

4.3 text + keyword + autocomplete (Üçlü Combo)

PUT complete_mapping_index
{
  "settings": {
    "analysis": {
      "filter": {
        "autocomplete_filter": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 12
        },
        "turkish_lowercase": {
          "type": "lowercase",
          "language": "turkish"
        }
      },
      "analyzer": {
        "autocomplete_index": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["turkish_lowercase", "autocomplete_filter"]
        },
        "autocomplete_search": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["turkish_lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "city": {
        "type": "text",
        "analyzer": "turkish",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_index",
            "search_analyzer": "autocomplete_search"
          }
        }
      }
    }
  }
}

Kullanım senaryoları:

// Full-text arama (Türkçe stemming)
GET complete_mapping_index/_search
{
  "query": { "match": { "city": "istanbullu" } }
}

// Autocomplete
GET complete_mapping_index/_search
{
  "query": { "match": { "city.autocomplete": "ist" } }
}

// Aggregation (kaç farklı şehir var?)
GET complete_mapping_index/_search
{
  "size": 0,
  "aggs": {
    "unique_cities": {
      "terms": { "field": "city.keyword", "size": 100 }
    }
  }
}

// Sorting
GET complete_mapping_index/_search
{
  "sort": [{ "city.keyword": "asc" }]
}

4.4 keyword + normalizer (Case-Insensitive Exact Match)

PUT normalized_index
{
  "settings": {
    "analysis": {
      "normalizer": {
        "lowercase_normalizer": {
          "type": "custom",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "email": {
        "type": "keyword",
        "fields": {
          "normalized": {
            "type": "keyword",
            "normalizer": "lowercase_normalizer"
          }
        }
      }
    }
  }
}

Artık:

  • email → Orijinal haliyle saklanır ("User@Email.COM")

  • email.normalized → Küçük harfe dönüştürülmüş ("user@email.com")

POST normalized_index/_doc/1
{ "email": "Ahmet.Yilmaz@Sirket.COM" }

// Orijinal halle exact match
GET normalized_index/_search
{
  "query": { "term": { "email": "Ahmet.Yilmaz@Sirket.COM" } }
}

// Küçük harfle arama
GET normalized_index/_search
{
  "query": { "term": { "email.normalized": "ahmet.yilmaz@sirket.com" } }
}

5. Index-Time vs Search-Time Analysis Derinlemesine

Bu kavram multi-field mapping'in temel taşıdır. Doğru anlaşılmazsa arama kalitesi düşer.

5.1 Index-Time Analysis

Bir doküman index'lendiğinde, her text field'ın değeri o field'ın analyzer'ından geçer ve oluşan token'lar inverted index'e yazılır.

Doküman: { "title": "Elasticsearch Kurulum Rehberi" }

title field (analyzer: turkish):
  "Elasticsearch Kurulum Rehberi"
      ↓ turkish analyzer
  ["elasticsearch", "kur", "rehber"]
      ↓
  Inverted Index:
    elasticsearch → doc1
    kur           → doc1
    rehber        → doc1

title.keyword field (no analyzer):
  "Elasticsearch Kurulum Rehberi" → doc1

Önemli: Index-time analysis sadece bir kez gerçekleşir — doküman index'lenirken. Token'lar inverted index'e yazıldıktan sonra orijinal metin _source'ta saklanır ama inverted index'teki token'lar artık değiştirilemez.

5.2 Search-Time Analysis

Bir arama yapıldığında, sorgu metni de analyze edilir — ama bu sefer search_analyzer kullanılır (tanımlanmadıysa analyzer kullanılır):

Sorgu: "Kurulum rehberleri"

search_analyzer (turkish):
  "Kurulum rehberleri"
      ↓ turkish analyzer
  ["kur", "rehber"]

Inverted index'te arama:
  "kur" → doc1 ✓
  "rehber" → doc1 ✓

Sonuç: doc1 bulundu!

"Kurulum" ve "Kurulumlar" aynı köke ("kur") indirgenir → ikisi de eşleşir.

5.3 Farklı Index ve Search Analyzer Kullanımı

Bazı senaryolarda index-time ve search-time'da farklı analyzer kullanmak gerekir:

PUT different_analyzers
{
  "settings": {
    "analysis": {
      "filter": {
        "edge_ngram_filter": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 10
        },
        "synonym_filter": {
          "type": "synonym_graph",
          "synonyms": [
            "telefon, mobil, cep telefonu",
            "bilgisayar, laptop, pc"
          ]
        }
      },
      "analyzer": {
        "index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "edge_ngram_filter"]
        },
        "search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "synonym_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "index_analyzer",
        "search_analyzer": "search_analyzer"
      }
    }
  }
}

Bu tasarımda:

  • Index-time: Edge ngram ile prefix matching desteği

  • Search-time: Synonym ile eşanlamlı genişletme

Kullanıcı "mobil" aradığında, search analyzer bunu ["mobil", "telefon", "cep telefonu"]'na genişletir. Bu token'lar inverted index'teki edge ngram token'larıyla eşleşir.

5.4 term Query — Analyze Edilmez!

term query sorgu metnini analyze etmez — olduğu gibi inverted index'te arar:

// ❌ Bulamaz! Çünkü inverted index'te "Elasticsearch" küçük harf ("elasticsearch") olarak kayıtlı
GET my_index/_search
{
  "query": {
    "term": {
      "title": "Elasticsearch"
    }
  }
}

// ✅ Küçük harfle yazın — inverted index'teki token ile eşleşir
GET my_index/_search
{
  "query": {
    "term": {
      "title": "elasticsearch"
    }
  }
}

// ✅ Veya match query kullanın — otomatik analyze edilir
GET my_index/_search
{
  "query": {
    "match": {
      "title": "Elasticsearch"
    }
  }
}

Bu, text field'larla term query kullanmanın neden tehlikeli olduğunu gösterir. text + match veya keyword + term kombinasyonlarını tercih edin.


6. Gerçek Dünya Senaryosu: Blog Platformu

Bir blog platformu için kapsamlı multi-field mapping tasarımı:

PUT blog_platform
{
  "settings": {
    "analysis": {
      "filter": {
        "turkish_lowercase": {
          "type": "lowercase",
          "language": "turkish"
        },
        "turkish_stop": {
          "type": "stop",
          "stopwords": "_turkish_"
        },
        "turkish_stemmer": {
          "type": "stemmer",
          "language": "turkish"
        },
        "edge_ngram_filter": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 10
        }
      },
      "analyzer": {
        "turkish_full": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["turkish_lowercase", "turkish_stop", "turkish_stemmer"]
        },
        "autocomplete_idx": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["turkish_lowercase", "edge_ngram_filter"]
        },
        "autocomplete_srch": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["turkish_lowercase"]
        }
      },
      "normalizer": {
        "lowercase_norm": {
          "type": "custom",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "turkish_full",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_idx",
            "search_analyzer": "autocomplete_srch"
          },
          "standard": {
            "type": "text",
            "analyzer": "standard"
          }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "turkish_full",
        "fields": {
          "standard": {
            "type": "text",
            "analyzer": "standard"
          }
        }
      },
      "author": {
        "type": "text",
        "analyzer": "standard",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_idx",
            "search_analyzer": "autocomplete_srch"
          }
        }
      },
      "tags": {
        "type": "keyword",
        "normalizer": "lowercase_norm"
      },
      "category": {
        "type": "keyword"
      },
      "publish_date": {
        "type": "date"
      },
      "view_count": {
        "type": "integer"
      },
      "status": {
        "type": "keyword"
      }
    }
  }
}

Veri Ekleme ve Sorgulama

// Veri ekle
POST blog_platform/_bulk
{"index":{"_id":"1"}}
{"title":"Elasticsearch ile Full-Text Arama","content":"Bu yazıda Elasticsearch'ün arama özelliklerini inceliyoruz. Full-text search modern uygulamaların vazgeçilmez bir parçasıdır.","author":"Ahmet Yılmaz","tags":["elasticsearch","arama","java"],"category":"Teknoloji","publish_date":"2024-01-15","view_count":1520,"status":"published"}
{"index":{"_id":"2"}}
{"title":"Spring Boot ve Elasticsearch Entegrasyonu","content":"Spring Data Elasticsearch ile Java uygulamalarınızda Elasticsearch kullanabilirsiniz. Repository pattern sayesinde CRUD işlemleri çok kolaydır.","author":"Mehmet Demir","tags":["spring-boot","elasticsearch","java"],"category":"Teknoloji","publish_date":"2024-02-20","view_count":2340,"status":"published"}
{"index":{"_id":"3"}}
{"title":"React ile Modern Web Geliştirme","content":"React kütüphanesi ile component-based web uygulamaları geliştirin. Hooks, context ve state management konularını ele alıyoruz.","author":"Ayşe Kaya","tags":["react","javascript","web"],"category":"Frontend","publish_date":"2024-03-10","view_count":3100,"status":"published"}

// 1. Full-text arama (Türkçe stemming)
GET blog_platform/_search
{
  "query": {
    "match": {
      "title": "aramaları"
    }
  }
}
// "aramaları" → stemmer → "ara" → "Arama" içeren doküman bulunur

// 2. Autocomplete
GET blog_platform/_search
{
  "query": {
    "match": {
      "title.autocomplete": "ela"
    }
  }
}
// "ela" → Elasticsearch içeren dokümanlar

// 3. Exact match (keyword)
GET blog_platform/_search
{
  "query": {
    "term": {
      "title.keyword": "Elasticsearch ile Full-Text Arama"
    }
  }
}

// 4. Aggregation — En popüler kategoriler
GET blog_platform/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "category",
        "size": 10
      }
    },
    "popular_tags": {
      "terms": {
        "field": "tags",
        "size": 20
      }
    }
  }
}

// 5. Sorting — Yayın tarihine göre sırala
GET blog_platform/_search
{
  "sort": [
    { "publish_date": "desc" }
  ]
}

// 6. Multi-match — Birden fazla field'da ara
GET blog_platform/_search
{
  "query": {
    "multi_match": {
      "query": "elasticsearch java",
      "fields": [
        "title^3",
        "title.standard",
        "content",
        "tags^2"
      ],
      "type": "best_fields"
    }
  }
}

7. copy_to — Birden Fazla Field'ı Birleştirme

copy_to parametresi, birden fazla field'ın değerini tek bir "catch-all" field'a kopyalar:

PUT search_all_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "copy_to": "search_all"
      },
      "description": {
        "type": "text",
        "copy_to": "search_all"
      },
      "tags": {
        "type": "keyword",
        "copy_to": "search_all"
      },
      "search_all": {
        "type": "text",
        "analyzer": "turkish"
      }
    }
  }
}

Artık tek bir field'da tüm içeriği arayabilirsiniz:

GET search_all_index/_search
{
  "query": {
    "match": {
      "search_all": "elasticsearch"
    }
  }
}

`copy_to` özellikleri:

  • Kaynak field'ların değerleri search_all'a kopyalanır (index-time)

  • _source'ta search_all görünmez (fiziksel olarak saklanmaz)

  • Birden fazla field aynı hedefe kopyalanabilir

  • Zincirleme kopya desteklenir (A → B → C)

⚠️ Dikkat: copy_to index boyutunu artırır çünkü aynı veri birden fazla kez index'lenir.


8. ignore_above Parametresi

keyword field'larda çok uzun string'lerin index'lenmesini engellemek için ignore_above kullanılır:

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

256 karakterden uzun değerler keyword sub-field'da index'lenmez (ama _source'ta tam haliyle saklanır).

Neden önemli?

  • Keyword field'lar tek token olarak saklanır

  • Çok uzun keyword'ler gereksiz bellek tüketir

  • Aggregation'larda 10.000 karakterlik bir keyword anlamsızdır

  • Lucene'in term uzunluk limiti 32KB'dir

Değeri nasıl ayarlamalı?

Alan TipiÖnerilen ignore_above
E-posta254
URL2048
Ürün adı256
Kategori128
Ülke kodu3

9. enabled: false — Index'lemeyi Kapatma

Bazen bir field'ı saklamak ama index'lememek istersiniz:

PUT logs_index
{
  "mappings": {
    "properties": {
      "message": {
        "type": "text"
      },
      "raw_payload": {
        "type": "object",
        "enabled": false
      }
    }
  }
}

raw_payload _source'ta saklanır ama aranabilir değildir. Bu, index boyutunu küçültür ve index'leme hızını artırır.


10. Java ile Multi-field Mapping

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.mapping.*;

public class MultiFieldMappingJava {

    public static void createMultiFieldIndex(ElasticsearchClient client) throws Exception {
        client.indices().create(c -> c
            .index("products_multifield")
            .settings(s -> s
                .analysis(a -> a
                    .filter("tr_lowercase", f -> f
                        .definition(d -> d
                            .lowercase(l -> l.language("turkish"))
                        )
                    )
                    .filter("tr_stemmer", f -> f
                        .definition(d -> d
                            .stemmer(st -> st.language("turkish"))
                        )
                    )
                    .filter("edge_ngram_f", f -> f
                        .definition(d -> d
                            .edgeNGram(e -> e.minGram(2).maxGram(10))
                        )
                    )
                    .analyzer("turkish_custom", an -> an
                        .custom(cu -> cu
                            .tokenizer("standard")
                            .filter("tr_lowercase", "tr_stemmer")
                        )
                    )
                    .analyzer("autocomplete_i", an -> an
                        .custom(cu -> cu
                            .tokenizer("standard")
                            .filter("tr_lowercase", "edge_ngram_f")
                        )
                    )
                    .analyzer("autocomplete_s", an -> an
                        .custom(cu -> cu
                            .tokenizer("standard")
                            .filter("tr_lowercase")
                        )
                    )
                )
            )
            .mappings(m -> m
                .properties("product_name", p -> p
                    .text(t -> t
                        .analyzer("turkish_custom")
                        .fields("keyword", f -> f
                            .keyword(k -> k.ignoreAbove(256))
                        )
                        .fields("autocomplete", f -> f
                            .text(t2 -> t2
                                .analyzer("autocomplete_i")
                                .searchAnalyzer("autocomplete_s")
                            )
                        )
                    )
                )
                .properties("category", p -> p
                    .keyword(k -> k)
                )
                .properties("price", p -> p
                    .float_(fl -> fl)
                )
            )
        );

        System.out.println("Multi-field index oluşturuldu.");
    }

    public static void searchMultiField(ElasticsearchClient client) throws Exception {
        // Full-text arama (Türkçe stemming)
        var fullTextResponse = client.search(s -> s
            .index("products_multifield")
            .query(q -> q
                .match(m -> m
                    .field("product_name")
                    .query("telefonlar")
                )
            ),
            Object.class
        );
        System.out.println("Full-text sonuç: " + fullTextResponse.hits().total().value());

        // Autocomplete
        var autocompleteResponse = client.search(s -> s
            .index("products_multifield")
            .query(q -> q
                .match(m -> m
                    .field("product_name.autocomplete")
                    .query("sam")
                )
            ),
            Object.class
        );
        System.out.println("Autocomplete sonuç: " + autocompleteResponse.hits().total().value());

        // Aggregation
        var aggResponse = client.search(s -> s
            .index("products_multifield")
            .size(0)
            .aggregations("categories", a -> a
                .terms(t -> t
                    .field("category")
                    .size(10)
                )
            ),
            Object.class
        );
        System.out.println("Kategori sayısı: " +
            aggResponse.aggregations().get("categories").sterms().buckets().array().size());
    }
}

11. Best Practices

✅ Yapın

UygulamaNeden
Her text field'a keyword sub-field ekleyinSorting ve aggregation için gerekli
Multi-field isimlendirmede tutarlı oluntitle.keyword, title.autocomplete — her index'te aynı pattern
ignore_above değerini field'a göre ayarlayınGereksiz index boyutu artışını önler
Autocomplete sub-field'ını sadece kısa alanlara ekleyinUzun açıklama alanlarında gereksiz
copy_to ile catch-all field oluşturunTek field'da genel arama kolaylaşır

❌ Yapmayın

UygulamaNeden
Her field'a 5+ sub-field eklemeyinIndex boyutu ve performans kaybı
text field'a term query ile sorgulama yapmayınAnalyze edilmiş token'larla eşleşmez
keyword field'da full-text arama yapmayınMatch query keyword field'da exact match gibi çalışır
Dynamic mapping'e production'da güvenmeyinGereksiz field'lar oluşabilir

12. Yaygın Hatalar

Hata 1: text + term Query

// ❌ "Samsung Galaxy" term olarak inverted index'te YOKTUR
GET my_index/_search
{
  "query": {
    "term": {
      "product_name": "Samsung Galaxy"
    }
  }
}

// ✅ match query kullanın (analyze edilir) veya keyword sub-field kullanın
GET my_index/_search
{
  "query": {
    "match": {
      "product_name": "Samsung Galaxy"
    }
  }
}
// VEYA
GET my_index/_search
{
  "query": {
    "term": {
      "product_name.keyword": "Samsung Galaxy S24 Ultra"
    }
  }
}

Hata 2: keyword Field'da Aggregation Yerine text Field

// ❌ text field'da aggregation — her token ayrı bucket olur
GET my_index/_search
{
  "size": 0,
  "aggs": {
    "products": {
      "terms": {
        "field": "product_name"  // text field!
      }
    }
  }
}
// Sonuç: "samsung", "galaxy", "s24" ayrı ayrı bucket olarak gelir

// ✅ keyword sub-field kullanın
GET my_index/_search
{
  "size": 0,
  "aggs": {
    "products": {
      "terms": {
        "field": "product_name.keyword"  // keyword sub-field
      }
    }
  }
}
// Sonuç: "Samsung Galaxy S24 Ultra" tek bucket olarak gelir

Hata 3: Mapping'i Sonradan Değiştirmeye Çalışmak

// ❌ Mevcut field'a sub-field eklenemez (varolan dokümanlar etkilenmez)
// Ama aslında Elasticsearch buna izin verir — yeni sub-field ekleme mümkündür!
PUT my_index/_mapping
{
  "properties": {
    "title": {
      "type": "text",
      "fields": {
        "keyword": { "type": "keyword" },
        "new_subfield": { "type": "text", "analyzer": "turkish" }
      }
    }
  }
}
// ✅ Bu çalışır! Ama mevcut dokümanlar yeni sub-field'da index'lenmemiştir.
// Mevcut dokümanlar için _update_by_query gerekir:
POST my_index/_update_by_query?conflicts=proceed

13. Performans Etkileri

Multi-field SayısıIndex BoyutuIndex HızıArama Esnekliği
1 (sadece text)1xHızlıDüşük
2 (text + keyword)1.3-1.5xNormalOrta
3 (text + keyword + autocomplete)2-3xYavaşYüksek
4+3-5x+Çok yavaşÇok yüksek

Kural: Her ek sub-field index boyutunu ve index'leme süresini artırır. Sadece gerçekten ihtiyaç duyduğunuz sub-field'ları ekleyin.

Index Boyutunu Kontrol Etme

// Index boyutunu görme
GET my_index/_stats/store

// Field bazlı disk kullanımı (8.x+)
GET my_index/_disk_usage?run_expensive_tasks=true

Özet

  • Multi-field mapping, aynı alanı farklı amaçlar için birden fazla şekilde index'lemenizi sağlar

  • En yaygın pattern text + keyword: full-text arama + aggregation/sorting/exact match

  • Index-time analysis doküman yazılırken, search-time analysis sorgu yapılırken gerçekleşir — farklı analyzer'lar kullanılabilir

  • term query sorgu metnini analyze etmez — text field'larla dikkatli kullanın

  • `copy_to` birden fazla field'ı tek bir aranabilir alana birleştirir

  • `ignore_above` keyword field'larda çok uzun değerlerin index'lenmesini engeller

  • Dynamic mapping otomatik text + keyword multi-field oluşturur — ama production'da explicit mapping tercih edin

  • Her ek sub-field index boyutunu artırır — sadece ihtiyaç duyulanları ekleyin