← Kursa Dön
📄 Text · 35 min

Custom Analyzer Oluşturma — Synonym, Autocomplete

Giriş — Terzi İşi Çözüm

Hazır takım elbiseler çoğu insan için yeterlidir, ama profesyonel bir sanatçının sahneye çıkacağı kıyafet özel dikilmelidir. Her dikiş, her ölçü, o kişiye özel olmalıdır. Elasticsearch'te de built-in analyzer'lar çoğu senaryo için işe yarar; ancak Türkçe e-ticaret araması, autocomplete, eşanlamlı destekli arama gibi özel ihtiyaçlar için custom analyzer oluşturmanız gerekir.

Bu derste, sıfırdan custom analyzer tasarlamayı öğreneceğiz. Türkçe arama optimizasyonu, synonym desteği ve autocomplete (search-as-you-type) gibi gerçek dünya senaryolarını adım adım inşa edeceğiz.


1. Custom Analyzer Anatomisi

Bir custom analyzer, üç bileşenin birleşiminden oluşur:

{
  "type": "custom",
  "char_filter": [...],     // 0 veya daha fazla
  "tokenizer": "...",       // tam olarak 1
  "filter": [...]           // 0 veya daha fazla
}

Custom analyzer'lar, index settings altındaki analysis bölümünde tanımlanır:

PUT my_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        // Özel character filter tanımları
      },
      "tokenizer": {
        // Özel tokenizer tanımları
      },
      "filter": {
        // Özel token filter tanımları
      },
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom",
          "char_filter": ["my_char_filter"],
          "tokenizer": "my_tokenizer",
          "filter": ["my_filter_1", "my_filter_2"]
        }
      }
    }
  }
}

Önemli: Custom bileşenler ve built-in bileşenler karıştırılabilir. Örneğin custom bir char_filter + built-in standard tokenizer + custom bir token filter kullanabilirsiniz.


2. Türkçe Arama Optimizasyonu

Turkish analyzer iyi bir başlangıçtır, ama gerçek dünya projeleri için yeterli olmayabilir. Özelleştirmemiz gereken noktalar:

  • Özel stop words listesi

  • Karakter normalizasyonu (noktalama, özel karakterler)

  • Türkçe stemmer fine-tuning

  • Lowercase sorununun çözümü

2.1 Gelişmiş Türkçe Analyzer

PUT turkish_search_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        "turkce_karakter_fix": {
          "type": "mapping",
          "mappings": [
            "İ => i",
            "I => ı",
            "â => a",
            "û => u",
            "î => i",
            "& => ve"
          ]
        }
      },
      "filter": {
        "turkish_lowercase": {
          "type": "lowercase",
          "language": "turkish"
        },
        "turkish_stop": {
          "type": "stop",
          "stopwords": [
            "acaba", "ama", "ancak", "arada", "aslında",
            "bazı", "belki", "ben", "beni", "benim",
            "bir", "birçok", "birkaç", "birşey", "biz",
            "bu", "buna", "bunda", "bundan", "bunlar",
            "bunu", "bunun", "burada", "çok", "çünkü",
            "da", "daha", "de", "defa", "değil",
            "diğer", "diye", "dolayı", "eğer", "en",
            "fakat", "filan", "gibi", "göre", "hala",
            "hatta", "hem", "henüz", "hep", "hepsi",
            "her", "hiç", "için", "ile", "ise",
            "işte", "kadar", "karşın", "kendi", "kim",
            "nasıl", "ne", "neden", "nerde", "nerede",
            "nereye", "niçin", "niye", "o", "ona",
            "ondan", "onlar", "onun", "onu", "oysa",
            "öyle", "sen", "seni", "senin", "siz",
            "şey", "şu", "tüm", "ve", "veya",
            "ya", "yani"
          ]
        },
        "turkish_stemmer": {
          "type": "stemmer",
          "language": "turkish"
        },
        "word_min_length": {
          "type": "length",
          "min": 2
        }
      },
      "analyzer": {
        "turkce_analyzer": {
          "type": "custom",
          "char_filter": ["turkce_karakter_fix"],
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase",
            "turkish_stop",
            "turkish_stemmer",
            "word_min_length"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "baslik": {
        "type": "text",
        "analyzer": "turkce_analyzer"
      },
      "icerik": {
        "type": "text",
        "analyzer": "turkce_analyzer"
      }
    }
  }
}

Test edelim:

POST turkish_search_index/_analyze
{
  "analyzer": "turkce_analyzer",
  "text": "Türkiye'deki yazılımcılar İstanbul'da buluşuyorlar"
}

Çıktı: ["türkiye'deki", "yazılımcı", "istanbul'da", "buluş"]

Stemmer "yazılımcılar" → "yazılımcı" ve "buluşuyorlar" → "buluş" dönüşümünü yaptı. Stop words ("da") çıkarıldı.

2.2 Türkçe Normalization Filter — Ascifolding Alternatifi

Bazen kullanıcılar Türkçe karakterleri yanlış yazar (örneğin "calisma" yerine "çalışma"). Bunu tolere etmek için:

PUT tolerant_turkish_index
{
  "settings": {
    "analysis": {
      "filter": {
        "turkish_lowercase": {
          "type": "lowercase",
          "language": "turkish"
        },
        "ascii_fold": {
          "type": "asciifolding",
          "preserve_original": true
        },
        "turkish_stop": {
          "type": "stop",
          "stopwords": "_turkish_"
        },
        "turkish_stemmer": {
          "type": "stemmer",
          "language": "turkish"
        }
      },
      "analyzer": {
        "tolerant_turkce": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase",
            "ascii_fold",
            "turkish_stop",
            "turkish_stemmer"
          ]
        }
      }
    }
  }
}

preserve_original: true sayesinde hem orijinal hem ASCII versiyonu saklanır:

POST tolerant_turkish_index/_analyze
{
  "analyzer": "tolerant_turkce",
  "text": "çalışma"
}

Çıktı: ["çalış", "calis"] — Hem "çalış" hem "calis" token'ı üretildi. Artık "calisma" araması da sonuç döndürür.

⚠️ Dikkat: asciifolding Türkçe bağlamda dikkatli kullanılmalıdır. "ı" → "i" dönüşümü anlam kaymasına yol açabilir ("sıla" vs "sila").


3. Synonym (Eşanlamlı) Desteği

Kullanıcılar aynı şeyi farklı kelimelerle arayabilir: "telefon" = "cep telefonu" = "mobil", "bilgisayar" = "pc" = "laptop". Synonym filter bu sorunu çözer.

3.1 Inline Synonym Tanımı

PUT synonym_demo_index
{
  "settings": {
    "analysis": {
      "filter": {
        "turkish_lowercase": {
          "type": "lowercase",
          "language": "turkish"
        },
        "my_synonyms": {
          "type": "synonym",
          "synonyms": [
            "telefon, cep telefonu, mobil, handset",
            "bilgisayar, pc, laptop, dizüstü",
            "araba, otomobil, araç",
            "ev, konut, daire, residence",
            "hızlı, süratli, çabuk, jet gibi",
            "ucuz, ekonomik, uygun fiyatlı, bütçe dostu"
          ]
        }
      },
      "analyzer": {
        "synonym_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["turkish_lowercase", "my_synonyms"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "synonym_analyzer"
      }
    }
  }
}

Test edelim:

// Veri ekle
POST synonym_demo_index/_doc/1
{ "title": "Samsung Galaxy akıllı telefon" }

POST synonym_demo_index/_doc/2
{ "title": "Uygun fiyatlı dizüstü bilgisayar" }

// "mobil" ile ara — "telefon" içeren dokümanı bulur
GET synonym_demo_index/_search
{
  "query": {
    "match": {
      "title": "mobil"
    }
  }
}

// "laptop" ile ara — "bilgisayar" içeren dokümanı bulur
GET synonym_demo_index/_search
{
  "query": {
    "match": {
      "title": "laptop"
    }
  }
}

3.2 Synonym Dosyası Kullanımı

Production ortamında synonym'leri dosyadan yüklemek daha pratiktir:

PUT synonym_file_index
{
  "settings": {
    "analysis": {
      "filter": {
        "file_synonyms": {
          "type": "synonym",
          "synonyms_path": "analysis/synonyms_tr.txt",
          "updateable": true
        }
      },
      "analyzer": {
        "search_synonym_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "file_synonyms"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "standard",
        "search_analyzer": "search_synonym_analyzer"
      }
    }
  }
}

config/analysis/synonyms_tr.txt dosyası:

# Eşanlamlılar (virgülle ayrılmış = çift yönlü)
telefon, cep telefonu, mobil
bilgisayar, pc, laptop, dizüstü

# Tek yönlü eşanlamlılar (=> ile)
ES => Elasticsearch
K8s => Kubernetes
JS => JavaScript

3.3 Index-Time vs Search-Time Synonym

Bu ayrım çok kritik bir karardır:

Index-time synonym:

"title": {
  "type": "text",
  "analyzer": "synonym_analyzer"  // Synonym index-time'da uygulanır
}
  • ✅ Arama hızlıdır (token'lar önceden genişletilmiş)

  • ❌ Yeni synonym ekleyince tüm veriyi reindex etmeniz gerekir

  • ❌ Term frequency etkilenir, scoring bozulabilir

Search-time synonym (önerilen):

"title": {
  "type": "text",
  "analyzer": "standard",
  "search_analyzer": "synonym_analyzer"  // Synonym sadece arama sırasında
}
  • ✅ Yeni synonym ekleyince reindex gerekmez

  • ✅ Scoring daha doğru olur

  • ❌ Arama biraz daha yavaş (sorgu genişletme maliyeti)

💡 İpucu: Çoğu durumda search-time synonym tercih edin. Synonym listeniz sık değişiyorsa bu yaklaşım zorunludur.

3.4 synonym_graph — Çok Kelimeli Eşanlamlılar

synonym filter tek kelimeli eşanlamlılarda iyi çalışır, ama "New York" = "NYC" gibi çok kelimeli durumlarda synonym_graph gerekir:

PUT graph_synonym_index
{
  "settings": {
    "analysis": {
      "filter": {
        "graph_synonyms": {
          "type": "synonym_graph",
          "synonyms": [
            "New York, NYC, NY",
            "cep telefonu, mobil cihaz, smartphone",
            "yapay zeka, artificial intelligence, AI"
          ]
        }
      },
      "analyzer": {
        "graph_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "graph_synonyms"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "standard",
        "search_analyzer": "graph_search_analyzer"
      }
    }
  }
}

⚠️ Önemli: synonym_graph sadece search-time'da kullanılmalıdır. Index-time'da kullanmak pozisyon grafik sorunlarına yol açar.


4. Autocomplete (Search-as-You-Type) Analyzer

Kullanıcı henüz kelimeyi bitirmeden sonuç göstermek modern aramanın olmazsa olmazıdır. "Elast" yazıldığında "Elasticsearch" önerilmelidir.

4.1 Edge N-gram Yaklaşımı

PUT autocomplete_index
{
  "settings": {
    "analysis": {
      "filter": {
        "autocomplete_filter": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 15
        },
        "turkish_lowercase": {
          "type": "lowercase",
          "language": "turkish"
        }
      },
      "analyzer": {
        "autocomplete_index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase",
            "autocomplete_filter"
          ]
        },
        "autocomplete_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "autocomplete_index_analyzer",
        "search_analyzer": "autocomplete_search_analyzer"
      }
    }
  }
}

Neden iki farklı analyzer?

  • Index-time: "Samsung" → ["sa", "sam", "sams", "samsu", "samsun", "samsung"]

  • Search-time: "sam" → ["sam"] (edge_ngram uygulanmaz!)

Eğer search-time'da da edge_ngram kullansaydınız: "Samsung" araması → ["sa", "sam", "sams", ...] token'larına bölünür → "sa" prefix'li her şeyi bulur (saatler, sandalet, vb.).

Test edelim:

// Veri ekle
POST autocomplete_index/_bulk
{"index":{}}
{"product_name": "Samsung Galaxy S24 Ultra"}
{"index":{}}
{"product_name": "Samsung Galaxy Tab S9"}
{"index":{}}
{"product_name": "Apple iPhone 15 Pro"}
{"index":{}}
{"product_name": "Apple MacBook Air M3"}
{"index":{}}
{"product_name": "Sony PlayStation 5"}

// "sam" yaz → Samsung ürünleri gelir
GET autocomplete_index/_search
{
  "query": {
    "match": {
      "product_name": "sam"
    }
  }
}

// "apple mac" yaz → MacBook gelir
GET autocomplete_index/_search
{
  "query": {
    "match": {
      "product_name": "apple mac"
    }
  }
}

4.2 Completion Suggester Alternatifi

Edge ngram dışında, Elasticsearch'ün completion field type'ı da autocomplete için kullanılabilir:

PUT suggest_index
{
  "mappings": {
    "properties": {
      "suggest": {
        "type": "completion",
        "analyzer": "standard",
        "contexts": [
          {
            "name": "category",
            "type": "category"
          }
        ]
      },
      "title": {
        "type": "text"
      }
    }
  }
}

// Veri ekle
POST suggest_index/_doc/1
{
  "title": "Elasticsearch Temelleri",
  "suggest": {
    "input": ["Elasticsearch", "ES", "Elastic"],
    "contexts": {
      "category": ["teknoloji"]
    },
    "weight": 10
  }
}

// Suggestion al
POST suggest_index/_search
{
  "suggest": {
    "product_suggest": {
      "prefix": "ela",
      "completion": {
        "field": "suggest",
        "size": 5,
        "contexts": {
          "category": ["teknoloji"]
        }
      }
    }
  }
}

Edge N-gram vs Completion Suggester:

ÖzellikEdge N-gramCompletion Suggester
HızHızlıÇok hızlı (FST yapısı)
EsneklikYüksek (scoring, fuzzy)Sınırlı
Index boyutuBüyük (çok token)Küçük
Fuzzy matchQuery-time fuzzyBuilt-in fuzzy
ScoringTF-IDF scoringWeight-based

5. E-Ticaret Arama Analyzer'ı — Kapsamlı Örnek

Gerçek bir e-ticaret sitesi için farklı ihtiyaçları karşılayan multiple analyzer tanımlayalım:

PUT ecommerce_v2
{
  "settings": {
    "analysis": {
      "char_filter": {
        "html_cleaner": {
          "type": "html_strip"
        },
        "special_chars": {
          "type": "mapping",
          "mappings": [
            "& => ve",
            "₺ => TL",
            "\" => ",
            "' => "
          ]
        }
      },
      "filter": {
        "turkish_lowercase": {
          "type": "lowercase",
          "language": "turkish"
        },
        "turkish_stop": {
          "type": "stop",
          "stopwords": "_turkish_"
        },
        "turkish_stemmer": {
          "type": "stemmer",
          "language": "turkish"
        },
        "product_synonyms": {
          "type": "synonym_graph",
          "synonyms": [
            "telefon, cep telefonu, mobil, smartphone",
            "bilgisayar, pc, laptop, dizüstü, notebook",
            "tv, televizyon, ekran",
            "kulaklık, earphone, headphone, earbuds",
            "şarj, charging, powerbank",
            "kılıf, case, kapak, cover"
          ]
        },
        "autocomplete_edge": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 12
        },
        "min_length": {
          "type": "length",
          "min": 2
        }
      },
      "analyzer": {
        "product_index_analyzer": {
          "type": "custom",
          "char_filter": ["html_cleaner", "special_chars"],
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase",
            "turkish_stop",
            "turkish_stemmer",
            "min_length"
          ]
        },
        "product_search_analyzer": {
          "type": "custom",
          "char_filter": ["special_chars"],
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase",
            "turkish_stop",
            "turkish_stemmer",
            "product_synonyms",
            "min_length"
          ]
        },
        "autocomplete_index_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase",
            "autocomplete_edge"
          ]
        },
        "autocomplete_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "turkish_lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "product_index_analyzer",
        "search_analyzer": "product_search_analyzer",
        "fields": {
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_index_analyzer",
            "search_analyzer": "autocomplete_search_analyzer"
          },
          "exact": {
            "type": "keyword"
          }
        }
      },
      "description": {
        "type": "text",
        "analyzer": "product_index_analyzer",
        "search_analyzer": "product_search_analyzer"
      },
      "category": {
        "type": "keyword"
      },
      "brand": {
        "type": "keyword"
      },
      "price": {
        "type": "float"
      }
    }
  }
}

Bu tasarımda:

  • product_name → Türkçe stemming + stop words (ana arama)

  • product_name.autocomplete → Edge ngram (autocomplete)

  • product_name.exact → Keyword (tam eşleşme, aggregation)

  • Search-time'da synonym desteği (reindex gerekmez)

Veri ve Test

// Veri ekle
POST ecommerce_v2/_bulk
{"index":{"_id":"1"}}
{"product_name":"Samsung Galaxy S24 Ultra Akıllı Telefon","description":"<p>Samsung'un en güçlü <b>akıllı telefon</b> modeli</p>","category":"Elektronik","brand":"Samsung","price":54999.99}
{"index":{"_id":"2"}}
{"product_name":"Apple iPhone 15 Pro Max Cep Telefonu","description":"<p>Apple'ın en gelişmiş <em>mobil cihazı</em></p>","category":"Elektronik","brand":"Apple","price":64999.99}
{"index":{"_id":"3"}}
{"product_name":"Lenovo ThinkPad X1 Carbon Dizüstü Bilgisayar","description":"<p>İş dünyası için en iyi <b>laptop</b></p>","category":"Bilgisayar","brand":"Lenovo","price":42999.99}

// "mobil" araması → telefon dokümanlarını bulur (synonym)
GET ecommerce_v2/_search
{
  "query": {
    "match": {
      "product_name": "mobil"
    }
  }
}

// Autocomplete: "sams" → Samsung ürünleri
GET ecommerce_v2/_search
{
  "query": {
    "match": {
      "product_name.autocomplete": "sams"
    }
  }
}

// "laptop" araması → "bilgisayar" ve "dizüstü" dokümanlarını bulur
GET ecommerce_v2/_search
{
  "query": {
    "match": {
      "product_name": "laptop"
    }
  }
}

6. N-gram Analyzer — Fuzzy-Like Arama

N-gram, kelimenin ortasından da eşleşme sağlar. "elastic" araması "Elasticsearch" içinde de eşleşir. Autocomplete'ten farkı: edge_ngram sadece baştan eşleşirken, ngram her yerden eşleşir.

6.1 N-gram Analyzer Oluşturma

PUT ngram_index
{
  "settings": {
    "analysis": {
      "filter": {
        "ngram_filter": {
          "type": "ngram",
          "min_gram": 3,
          "max_gram": 4
        }
      },
      "analyzer": {
        "ngram_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "ngram_filter"
          ]
        },
        "ngram_search": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "ngram_analyzer",
        "search_analyzer": "ngram_search"
      }
    }
  }
}
POST ngram_index/_analyze
{
  "analyzer": "ngram_analyzer",
  "text": "Elasticsearch"
}

Çıktı (kısaltılmış): ["ela", "elas", "las", "last", "ast", "asti", ...]

⚠️ Dikkat: N-gram index boyutunu dramatik şekilde artırır. min_gram: 3, max_gram: 4 bile 10+ kelimelik bir metni yüzlerce token'a dönüştürür. Sadece kısa alanlar (isim, ürün kodu) için kullanın.


7. Normalizer — Keyword Alanları İçin Hafif Analiz

keyword field type'ı analyze edilmez, ama bazen case-insensitive veya accent-insensitive karşılaştırma istersiniz. Bunun için normalizer kullanılır:

PUT normalizer_index
{
  "settings": {
    "analysis": {
      "normalizer": {
        "lowercase_normalizer": {
          "type": "custom",
          "char_filter": [],
          "filter": ["lowercase", "asciifolding"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "brand": {
        "type": "keyword",
        "normalizer": "lowercase_normalizer"
      },
      "email": {
        "type": "keyword",
        "normalizer": "lowercase_normalizer"
      }
    }
  }
}

Artık:

POST normalizer_index/_doc/1
{ "brand": "Samsung", "email": "User@Email.COM" }

// Küçük harfle arama yapabilirsiniz
GET normalizer_index/_search
{
  "query": {
    "term": {
      "brand": "samsung"
    }
  }
}
// ✅ Bulur! Çünkü normalizer "Samsung" → "samsung" dönüşümü yaptı

Normalizer vs Analyzer:

  • Normalizer: Keyword field'lar için, tokenize etmez, sadece normalize eder

  • Analyzer: Text field'lar için, tokenize eder ve transform eder


8. Analyzer'ı Güncelleme ve Yönetim

8.1 Mevcut Index'e Analyzer Ekleme

Mevcut bir index'in settings'ini değiştirmek için index'i önce kapatmanız gerekir:

// 1. Index'i kapat
POST my_index/_close

// 2. Analyzer ekle/güncelle
PUT my_index/_settings
{
  "analysis": {
    "filter": {
      "new_synonym_filter": {
        "type": "synonym",
        "synonyms": ["hızlı, çabuk, süratli"]
      }
    },
    "analyzer": {
      "new_analyzer": {
        "type": "custom",
        "tokenizer": "standard",
        "filter": ["lowercase", "new_synonym_filter"]
      }
    }
  }
}

// 3. Index'i aç
POST my_index/_open

⚠️ Dikkat: Analyzer eklenmesi mevcut dokümanları etkilemez. Yeni analyzer'la index'lenmesi için dokümanlar yeniden index'lenmelidir.

8.2 Reloadable Synonym

Synonym dosyası değiştiğinde reindex yerine sadece reload yapabilirsiniz:

// Synonym filter'ı "updateable: true" ile tanımla (search_analyzer'da)
// Sonra reload:
POST my_index/_reload_search_analyzers

Bu sadece search-time analyzer'lar için çalışır — index-time analyzer'lar reload edilemez.


9. Java ile Custom Analyzer

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.*;

public class CustomAnalyzerJava {

    public static void createCustomAnalyzerIndex(ElasticsearchClient client) throws Exception {
        client.indices().create(c -> c
            .index("products_custom")
            .settings(s -> s
                .analysis(a -> a
                    // Character filter
                    .charFilter("html_strip_filter", cf -> cf
                        .definition(d -> d
                            .htmlStrip(hs -> hs)
                        )
                    )
                    // Token filters
                    .filter("tr_lowercase", f -> f
                        .definition(d -> d
                            .lowercase(l -> l.language("turkish"))
                        )
                    )
                    .filter("tr_stop", f -> f
                        .definition(d -> d
                            .stop(st -> st.stopwords("_turkish_"))
                        )
                    )
                    .filter("tr_stemmer", f -> f
                        .definition(d -> d
                            .stemmer(st -> st.language("turkish"))
                        )
                    )
                    .filter("edge_ngram_filter", f -> f
                        .definition(d -> d
                            .edgeNGram(e -> e
                                .minGram(2)
                                .maxGram(12)
                            )
                        )
                    )
                    // Analyzers
                    .analyzer("turkish_custom", an -> an
                        .custom(cu -> cu
                            .charFilter("html_strip_filter")
                            .tokenizer("standard")
                            .filter("tr_lowercase", "tr_stop", "tr_stemmer")
                        )
                    )
                    .analyzer("autocomplete_idx", an -> an
                        .custom(cu -> cu
                            .tokenizer("standard")
                            .filter("tr_lowercase", "edge_ngram_filter")
                        )
                    )
                    .analyzer("autocomplete_srch", an -> an
                        .custom(cu -> cu
                            .tokenizer("standard")
                            .filter("tr_lowercase")
                        )
                    )
                )
            )
            .mappings(m -> m
                .properties("name", p -> p
                    .text(t -> t
                        .analyzer("turkish_custom")
                        .fields("autocomplete", f -> f
                            .text(t2 -> t2
                                .analyzer("autocomplete_idx")
                                .searchAnalyzer("autocomplete_srch")
                            )
                        )
                    )
                )
            )
        );

        System.out.println("Custom analyzer index oluşturuldu.");
    }
}

10. Best Practices

✅ Yapın

UygulamaNeden
Synonym'leri search-time'da uygulayınYeni synonym ekleyince reindex gerekmez
Autocomplete için farklı index/search analyzer kullanınIndex'te edge_ngram, search'te normal analyzer
Türkçe için turkish_lowercase filter kullanınIı dönüşümü doğru yapılır
_analyze API ile her adımı test edinBeklenmedik sonuçları erken yakalayın
Synonym dosyalarını versiyon kontrolünde tutunDeğişiklik geçmişi önemlidir

❌ Yapmayın

UygulamaNeden
N-gram'ı uzun metin alanlarında kullanmayınIndex boyutu patlar
synonym_graph'ı index-time'da kullanmayınPozisyon grafik sorunları yaratır
asciifolding'i Türkçe'de dikkatli kullanın"ı" → "i" anlam kaybına yol açabilir
Analyzer'ı production'da sık değiştirmeyinReindex gerektirir

11. Yaygın Hatalar

Hata 1: Synonym Filter Sırası

// ❌ Synonym'den sonra stemmer — synonym token'ları stem edilir
"filter": ["lowercase", "my_synonyms", "stemmer"]

// ✅ Stemmer'dan sonra synonym (veya synonym'i search-time'a taşıyın)
"filter": ["lowercase", "stemmer", "my_synonyms"]

Hata 2: Edge N-gram Search Analyzer'ı

// ❌ Search-time'da da edge_ngram kullanmak
"product_name": {
  "type": "text",
  "analyzer": "autocomplete_analyzer"  // Hem index hem search aynı
}

// ✅ Farklı search analyzer
"product_name": {
  "type": "text",
  "analyzer": "autocomplete_index_analyzer",
  "search_analyzer": "autocomplete_search_analyzer"
}

Hata 3: Çok Fazla Token Filter

// ❌ Her şeyi bir analyzer'a tıkmak
"filter": [
  "lowercase", "stop", "stemmer", "synonym",
  "ngram", "edge_ngram", "asciifolding", "unique",
  "trim", "truncate", "word_delimiter"
]

Bu hem performansı düşürür hem de beklenmedik token dönüşümlerine yol açar. Analyzer'ları amaca göre ayrı tutun.

Hata 4: max_gram Değerini Çok Yüksek Tutmak

// ❌ 20 karakterlik edge_ngram
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20

// ✅ Makul aralık
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 12

min_gram: 1 tek harflik token üretir — çoğu durumda anlamsızdır ve index'i gereksiz büyütür.


12. Performans Karşılaştırması

YaklaşımIndex Boyutu (1M doküman)Index HızıArama Hızı
Standard analyzer1x (baz)HızlıHızlı
Turkish analyzer0.8xNormalHızlı
Edge ngram (2-12)3-5xYavaşÇok hızlı
Full ngram (3-5)8-15xÇok yavaşHızlı
Synonym (search-time)1xHızlıBiraz yavaş
Synonym (index-time)1.2-1.5xBiraz yavaşHızlı

💡 İpucu: Autocomplete için edge_ngram kullanıyorsanız, sadece kısa alanlar (başlık, isim) için uygulayın. Uzun açıklama alanlarına uygulamayın.


Özet

  • Custom analyzer = char_filter(s) + tokenizer + token_filter(s) kombinasyonu — built-in'ler yetmediğinde devreye girer

  • Türkçe arama için turkish_lowercase + custom stop words + turkish_stemmer kombinasyonu kullanın

  • Synonym desteği için synonym_graph filter'ı tercih edin ve search-time'da uygulayın — yeni eşanlamlı ekleyince reindex gerekmez

  • Autocomplete için edge_ngram kullanın — index-time'da edge_ngram, search-time'da normal analyzer

  • Normalizer, keyword field'lar için hafif normalizasyon sağlar (case-insensitive tam eşleşme)

  • Analyzer güncellemesi index kapatma/açma gerektirir — production'da dikkatli planlayın

  • Her analyzer'ı `_analyze` API ile test edin — beklenmedik token dönüşümlerini erken yakalayın