← Kursa Dön
📄 Text · 30 min

Analyzer Test ve Debug Rehberi

Giriş — Kara Kutuyu Açmak

Bir custom analyzer yazdınız. Güzel görünüyor. Index'e veri eklediniz, arama yaptınız ve... sonuç boş geldi. Neden? "evlerin" araması "ev" içeren dokümanı neden bulamadı? Stemmer çalışmıyor mu? Yoksa stop filter "bir" yerine "bir" kelimesini mi yedi?

Bu an, Elasticsearch'teki en sinir bozucu andır. Hata mapping'de mi, analyzer'da mı, sorguda mı — hiçbir fikriniz yok.

İşte _analyze API tam bu an için var. Bu API, Elasticsearch'ün kara kutusunu açar ve analiz sürecinin her adımını görünür kılar: hangi character filter ne dönüştürdü, tokenizer metni nasıl böldü, her token filter ne yaptı — hepsini adım adım görebilirsiniz.

Bu ders, _analyze API'nin her parametresini, debug tekniklerini, search-time vs index-time farklarını pratikte nasıl test edeceğinizi ve analyzer geliştirme workflow'unu öğretecek.


1. _analyze API Derinlemesine

1.1 Temel Kullanım Formları

_analyze API'nin üç temel kullanım şekli vardır:

Form 1: Ad-hoc — Bileşenleri elle belirt

POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase", "stop"],
  "char_filter": ["html_strip"],
  "text": "<p>The Quick Brown Fox</p>"
}

Bu form, hızlı test için idealdir. Index oluşturmadan direkt test edersiniz.

Form 2: Built-in analyzer adıyla

POST _analyze
{
  "analyzer": "turkish",
  "text": "İstanbul'daki evlerin fiyatları"
}

Form 3: Index'e tanımlı analyzer ile

POST my_index/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": "Test metni"
}

Form 4: Field adıyla — o field'ın analyzer'ını kullan

POST my_index/_analyze
{
  "field": "title",
  "text": "Bu metnin title field'ının analyzer'ı ile analiz et"
}

1.2 Çıktıyı Anlamak

POST _analyze
{
  "analyzer": "standard",
  "text": "Elasticsearch güçlü bir arama motoru"
}

Çıktı:

{
  "tokens": [
    {
      "token": "elasticsearch",
      "start_offset": 0,
      "end_offset": 13,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "güçlü",
      "start_offset": 14,
      "end_offset": 19,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "bir",
      "start_offset": 20,
      "end_offset": 23,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

Her token şu bilgileri taşır:

AlanAçıklama
tokenAnaliz sonrası oluşan token (inverted index'e yazılan değer)
start_offsetOrijinal metindeki başlangıç pozisyonu (karakter)
end_offsetOrijinal metindeki bitiş pozisyonu
typeToken tipi (<ALPHANUM>, <NUM>, <HANGUL>, <KATAKANA> vb.)
positionToken'ın sıra pozisyonu (phrase query ve proximity search'te kritik)

1.3 explain: true — Her Adımı Görmek

Bu parametreyi kullandığınızda, her token'ın hangi adımdan geçtiğini ve nasıl dönüştüğünü görürsünüz:

POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase", {"type": "stop", "stopwords": ["bir", "ve"]}],
  "text": "Güçlü bir Arama ve Analiz",
  "explain": true
}

Çıktı (kısaltılmış):

{
  "detail": {
    "custom_analyzer": true,
    "tokenizer": {
      "name": "standard",
      "tokens": [
        { "token": "Güçlü", "position": 0, "start_offset": 0, "end_offset": 5 },
        { "token": "bir", "position": 1, "start_offset": 6, "end_offset": 9 },
        { "token": "Arama", "position": 2, "start_offset": 10, "end_offset": 15 },
        { "token": "ve", "position": 3, "start_offset": 16, "end_offset": 18 },
        { "token": "Analiz", "position": 4, "start_offset": 19, "end_offset": 25 }
      ]
    },
    "tokenfilters": [
      {
        "name": "lowercase",
        "tokens": [
          { "token": "güçlü", "position": 0 },
          { "token": "bir", "position": 1 },
          { "token": "arama", "position": 2 },
          { "token": "ve", "position": 3 },
          { "token": "analiz", "position": 4 }
        ]
      },
      {
        "name": "__anonymous_stop",
        "tokens": [
          { "token": "güçlü", "position": 0 },
          { "token": "arama", "position": 2 },
          { "token": "analiz", "position": 4 }
        ]
      }
    ]
  }
}

Her adımda ne olduğu net görünüyor:

  1. Tokenizer: 5 token üretildi

  2. lowercase: Tüm harfler küçüldü

  3. stop: "bir" ve "ve" silindi, 3 token kaldı

Position değerlerine dikkat edin: "arama" hâlâ position 2'de, "analiz" position 4'te. Stop filter pozisyonları değiştirmez — bu phrase query'ler için önemlidir.

1.4 Birden Fazla Metin Test Etme

text alanına array geçerek birden fazla metni tek seferde test edebilirsiniz:

POST _analyze
{
  "analyzer": "standard",
  "text": [
    "Birinci metin",
    "İkinci metin",
    "Üçüncü metin"
  ]
}

Her metin ayrı ayrı analiz edilir, token'lar aynı array'de birleşir. Pozisyon numaraları her metinde sıfırdan başlar.


2. Analyzer Karşılaştırma Testi

Farklı analyzer'ların aynı metin üzerindeki etkisini karşılaştırmak, doğru analyzer'ı seçmenin en güvenilir yoludur.

2.1 Built-in Analyzer Karşılaştırması

// Standard analyzer
POST _analyze
{
  "analyzer": "standard",
  "text": "İstanbul'daki 3 katlı evlerin fiyatları arttı mı?"
}
// Sonuç: ["istanbul'daki", "3", "katlı", "evlerin", "fiyatları", "arttı", "mı"]

// Turkish analyzer
POST _analyze
{
  "analyzer": "turkish",
  "text": "İstanbul'daki 3 katlı evlerin fiyatları arttı mı?"
}
// Sonuç: ["istanbul", "3", "kat", "ev", "fiyat", "art"]

// Whitespace analyzer
POST _analyze
{
  "analyzer": "whitespace",
  "text": "İstanbul'daki 3 katlı evlerin fiyatları arttı mı?"
}
// Sonuç: ["İstanbul'daki", "3", "katlı", "evlerin", "fiyatları", "arttı", "mı?"]

// Simple analyzer
POST _analyze
{
  "analyzer": "simple",
  "text": "İstanbul'daki 3 katlı evlerin fiyatları arttı mı?"
}
// Sonuç: ["istanbul", "daki", "katlı", "evlerin", "fiyatları", "arttı", "mı"]

Karşılaştırma tablosu:

AnalyzerToken sayısıApostrofStemmingStopLowercase
standard7Kelime içi
turkish6Atılır✓ (TR)
whitespace7Kelime içi
simple7Böler

2.2 Side-by-Side Karşılaştırma Script'i

Production'da birden fazla analyzer'ı karşılaştırmak için bir test index'i oluşturun:

PUT analyzer_lab
{
  "settings": {
    "analysis": {
      "filter": {
        "tr_lower": { "type": "lowercase", "language": "turkish" },
        "tr_stop": { "type": "stop", "stopwords": "_turkish_" },
        "tr_stem": { "type": "stemmer", "language": "turkish" },
        "apost": { "type": "apostrophe" }
      },
      "analyzer": {
        "v1_basic": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["tr_lower"]
        },
        "v2_with_stop": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["tr_lower", "tr_stop"]
        },
        "v3_with_stem": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["apost", "tr_lower", "tr_stop", "tr_stem"]
        },
        "v4_aggressive": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "apost", "tr_lower", "tr_stop", "tr_stem",
            {"type": "length", "min": 3}
          ]
        }
      }
    }
  }
}

// Her analyzer'ı aynı metinle test et
POST analyzer_lab/_analyze
{ "analyzer": "v1_basic", "text": "Türkiye'deki büyük şehirlerin bir listesi" }
// ["türkiye'deki", "büyük", "şehirlerin", "bir", "listesi"]

POST analyzer_lab/_analyze
{ "analyzer": "v2_with_stop", "text": "Türkiye'deki büyük şehirlerin bir listesi" }
// ["türkiye'deki", "büyük", "şehirlerin", "listesi"]

POST analyzer_lab/_analyze
{ "analyzer": "v3_with_stem", "text": "Türkiye'deki büyük şehirlerin bir listesi" }
// ["türkiye", "büyük", "şehir", "list"]

POST analyzer_lab/_analyze
{ "analyzer": "v4_aggressive", "text": "Türkiye'deki büyük şehirlerin bir listesi" }
// ["türkiye", "büyük", "şehir", "list"]

Her adımda nelerin değiştiğini net görebilirsiniz. Bu yaklaşımla production'a almadan önce ideal kombinasyonu bulursunuz.


3. Index-time vs Search-time Analyzer Farkları

Bu konu, Elasticsearch'teki en kafa karıştırıcı konulardan biridir. Yanlış anlaşılması, "arama çalışmıyor" sorunlarının en yaygın nedenidir.

3.1 Temel Kavram

Index-time analyzer: Doküman index'lenirken text field'larını analiz eder. Çıkan token'lar inverted index'e yazılır.

Search-time analyzer: Arama sorgusundaki metni analiz eder. Çıkan token'lar inverted index'te aranır.

Index-time:
  "Evlerin fiyatları arttı" 
    → turkish analyzer → ["ev", "fiyat", "art"]
    → inverted index'e yazılır

Search-time:
  "evlerin fiyatı"
    → turkish analyzer → ["ev", "fiyat"]
    → inverted index'te bu token'lar aranır
    → EŞLEŞME! ✓

3.2 Farklı Analyzer Kullanma

Bazı senaryolarda farklı analyzer kullanmak gerekir:

PUT different_analyzers
{
  "settings": {
    "analysis": {
      "filter": {
        "autocomplete_filter": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 10
        }
      },
      "analyzer": {
        "autocomplete_index": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "autocomplete_filter"]
        },
        "autocomplete_search": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "autocomplete_index",
        "search_analyzer": "autocomplete_search"
      }
    }
  }
}

3.3 Hangi Analyzer Kullanılıyor — Nasıl Anlaşılır?

Mapping'den doğrulama:

GET my_index/_mapping

Field'ın analyzer bilgisi çıktıda görünür. Eğer search_analyzer belirtilmemişse, analyzer hem index hem search'te kullanılır. Hiçbiri belirtilmemişse standard kullanılır.

3.4 Index-time vs Search-time Test

Aynı field'ın her iki analyzer'ını da _analyze ile test edin:

// Index-time analyzer testi
POST different_analyzers/_analyze
{
  "field": "title",
  "text": "Elasticsearch"
}
// Sonuç: ["el", "ela", "elas", "elast", "elasti", "elastic", "elastics", "elasticse", "elasticsearch"]

// Search-time analyzer testi — normalizer parametresi ile
POST _analyze
{
  "analyzer": "autocomplete_search",
  "text": "Ela"
}
// Sonuç: ["ela"]

⚠️ Dikkat: POST my_index/_analyze + field parametresi her zaman index-time analyzer'ı kullanır. Search-time analyzer'ı test etmek için analyzer adını doğrudan verin.


4. Explain API ile Scoring Debug

_analyze API metin analizini debug eder. _explain API ise bir dokümanın neden belli bir skor aldığını debug eder. İkisi birlikte güçlü bir debug toolkit oluşturur.

4.1 Explain API Kullanımı

GET my_index/_explain/1
{
  "query": {
    "match": {
      "title": "elasticsearch güçlü"
    }
  }
}

Çıktı (kısaltılmış):

{
  "_index": "my_index",
  "_id": "1",
  "matched": true,
  "explanation": {
    "value": 1.8754,
    "description": "sum of:",
    "details": [
      {
        "value": 1.2345,
        "description": "weight(title:elasticsearch in 0) [PerFieldSimilarity]",
        "details": [
          {
            "value": 1.2345,
            "description": "score(freq=1.0), computed as boost * idf * tf",
            "details": [
              { "value": 2.2, "description": "boost" },
              { "value": 0.6931, "description": "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5))" },
              { "value": 0.8103, "description": "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl))" }
            ]
          }
        ]
      }
    ]
  }
}

Bu çıktıdan anlayabileceğiniz bilgiler:

  • matched: true/false — Doküman sorguyla eşleşti mi?

  • value — BM25 skoru

  • idf — Terimin nadir olup olmadığı (nadir = yüksek skor)

  • tf — Terimin dokümanda kaç kez geçtiği

  • boost — Uygulanan boost değeri

  • dl/avgdl — Doküman uzunluğu / ortalama doküman uzunluğu

4.2 Profile API — Sorgu Performans Analizi

GET my_index/_search
{
  "profile": true,
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "elasticsearch" } }
      ],
      "filter": [
        { "term": { "category": "Teknoloji" } }
      ]
    }
  }
}

Profile çıktısı her query bileşeninin süresini gösterir:

{
  "profile": {
    "shards": [
      {
        "searches": [
          {
            "query": [
              {
                "type": "BooleanQuery",
                "description": "+title:elasticsearch #category:Teknoloji",
                "time_in_nanos": 285430,
                "children": [
                  {
                    "type": "TermQuery",
                    "description": "title:elasticsearch",
                    "time_in_nanos": 142356
                  },
                  {
                    "type": "TermQuery",
                    "description": "#category:Teknoloji",
                    "time_in_nanos": 52841
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}

Bu çıktı, hangi sorgu bileşeninin yavaş olduğunu gösterir. Optimizasyon yapacağınız yeri bulursunuz.


5. Tokenization Debug Teknikleri

5.1 Problem: Arama Sonuç Getirmiyor

En yaygın debug senaryosu budur. Sistematik yaklaşım:

Adım 1: Dokümanın index-time token'larını kontrol edin:

POST my_index/_analyze
{
  "field": "title",
  "text": "İstanbul'daki güzel evler"
}
// Sonuç: ["istanbul", "güzel", "ev"]

Adım 2: Sorgunun search-time token'larını kontrol edin:

POST _analyze
{
  "analyzer": "my_search_analyzer",
  "text": "İstanbul ev"
}
// Sonuç: ["istanbul", "ev"]  — Eşleşmeli!

Adım 3: Eğer token'lar eşleşiyor ama sonuç gelmiyorsa, _explain kullanın:

GET my_index/_explain/1
{
  "query": { "match": { "title": "İstanbul ev" } }
}

5.2 Problem: Beklenmeyen Sonuçlar Geliyor

Alakasız dokümanlar geliyorsa, o dokümanın neden eşleştiğini anlamak için:

// Dokümanın token'larını görmek
POST my_index/_analyze
{
  "field": "title",
  "text": "Problematik dokümanın title değeri buraya"
}

5.3 Token Position Analizi

Phrase query ve span query'lerde pozisyon önemlidir:

POST _analyze
{
  "analyzer": "standard",
  "text": "quick brown fox",
  "explain": true
}

match_phrase sorgusu, token'ların ardışık pozisyonlarda olmasını gerektirir. Stop filter bir token'ı silerse, pozisyon boşluğu oluşur ve phrase query etkilenebilir.

// Stop filter pozisyon koruması
POST _analyze
{
  "tokenizer": "standard",
  "filter": [
    "lowercase",
    { "type": "stop", "stopwords": ["the"] }
  ],
  "text": "the quick brown fox",
  "explain": true
}
// "quick" pozisyon 1'de kalır (0 değil!) — "the"nin yeri korunur

Bu yüzden match_phrase sorgusu slop: 0 ile yapılıyorsa, stop words pozisyon boşluğu yaratabilir.


6. Custom Analyzer Geliştirme Workflow

Sıfırdan bir analyzer geliştirirken sistematik bir yaklaşım izleyin.

6.1 Adım 1: Gereksinimleri Tanımla

Sorulacak sorular:

  • Hangi dilde/dillerde arama yapılacak?

  • Autocomplete gerekli mi?

  • Diacritics (özel karakter) toleransı gerekli mi?

  • Synonym desteği gerekli mi?

  • HTML içerik var mı?

  • Exact match de gerekli mi (keyword field)?

6.2 Adım 2: Test Corpus Hazırla

En az 10-15 gerçek metin örneği toplayın. Bunlar edge case'leri de kapsamalı:

// Test corpus
[
  "İstanbul'daki 3+1 daireler",
  "ÇALIŞANLARIN hakları",
  "Samsung Galaxy S24 Ultra 256GB",
  "<p>HTML <b>içerikli</b> metin</p>",
  "Atatürk'ün Gençliğe Hitabesi",
  "100₺ ve üzeri ürünler",
  "user@email.com adresine gönderin",
  "COVID-19 aşı kampanyası",
  "e-ticaret ve e-devlet",
  "Fenerbahçe-Galatasaray derbi maçı"
]

6.3 Adım 3: İteratif Geliştirme

// İterasyon 1: Sadece tokenizer + lowercase
POST _analyze
{
  "tokenizer": "standard",
  "filter": [{"type": "lowercase", "language": "turkish"}],
  "text": "İstanbul'daki 3+1 daireler"
}
// Sonuç: ["istanbul'daki", "3", "1", "daireler"]
// Problem: apostrof, stemming yok

// İterasyon 2: Apostrof + stop ekle
POST _analyze
{
  "tokenizer": "standard",
  "filter": [
    {"type": "apostrophe"},
    {"type": "lowercase", "language": "turkish"},
    {"type": "stop", "stopwords": "_turkish_"}
  ],
  "text": "İstanbul'daki 3+1 daireler"
}
// Sonuç: ["istanbul", "3", "1", "daireler"]
// İyileşme: apostrof temiz! Stemming hâlâ yok

// İterasyon 3: Stemmer ekle
POST _analyze
{
  "tokenizer": "standard",
  "filter": [
    {"type": "apostrophe"},
    {"type": "lowercase", "language": "turkish"},
    {"type": "stop", "stopwords": "_turkish_"},
    {"type": "stemmer", "language": "turkish"}
  ],
  "text": "İstanbul'daki 3+1 daireler"
}
// Sonuç: ["istanbul", "3", "1", "dair"]
// İyileşme: stemming çalışıyor!

Her iterasyonda bir bileşen ekleyip test edin. Tüm corpus'u her iterasyonda geçirin.

6.4 Adım 4: Edge Case ve Finalize

Edge case'leri (e-posta, tireli kelimeler, URL'ler) test edin. uax_url_email tokenizer e-postaları bölmeden korur. Tireli kelimeler (e-ticaret) standard tokenizer'da bölünür — char filter mapping ile düzeltilebilir. Sonuçlardan memnunsanız, analyzer'ı index settings'e yazın.


7. Normalizer Test — Keyword Field'lar İçin

keyword field'lar analiz edilmez ama normalizer ile basit dönüşümler yapılabilir:

PUT normalizer_test
{
  "settings": {
    "analysis": {
      "normalizer": {
        "turkish_normalizer": {
          "type": "custom",
          "filter": [{"type": "lowercase", "language": "turkish"}]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "category": {
        "type": "keyword",
        "normalizer": "turkish_normalizer"
      }
    }
  }
}

Normalizer testi:

POST normalizer_test/_analyze
{
  "normalizer": "turkish_normalizer",
  "text": "ELEKTRONİK"
}
// Sonuç: "elektronik" — keyword olarak saklanır ama küçük harfe çevrilmiş

⚠️ Dikkat: Normalizer'a tokenizer ekleyemezsiniz. Sadece char_filter ve token_filter kullanabilirsiniz. Metin bölünmez — tek token olarak kalır.


8. Analyzer Benchmark — Performans Karşılaştırma

8.1 Basit Benchmark Yaklaşımı

Analyzer performansını ölçmek için büyük bir metin seti üzerinde _analyze çağrıları yapın:

// Basit performans testi — Kibana Console'da
// 1000 dokümanı indexleyip süreyi ölçmek daha gerçekçidir

// Analyzer 1: Standard
POST _analyze
{
  "analyzer": "standard",
  "text": "Bu çok uzun bir metin olsun ki tokenizer ve filter performansını ölçebilelim. Elasticsearch güçlü bir arama motorudur ve binlerce dokümanı saniyeler içinde tarar."
}

// Analyzer 2: Turkish (daha ağır)
POST _analyze
{
  "analyzer": "turkish",
  "text": "Bu çok uzun bir metin olsun ki tokenizer ve filter performansını ölçebilelim. Elasticsearch güçlü bir arama motorudur ve binlerce dokümanı saniyeler içinde tarar."
}

8.2 Token Sayısı Karşılaştırması

Token sayısı doğrudan index boyutunu etkiler. Bir benchmark index'i oluşturup test edin:

// Token sayıları — "Elasticsearch" kelimesi için: POST benchmark_index/_analyze { "analyzer": "standard_only", "text": "Elasticsearch" } // 1 token: ["elasticsearch"]

POST benchmark_index/_analyze { "analyzer": "with_ngram", "text": "Elasticsearch" } // ~30 token: ["el","ela","elas","las","last",...] — çok fazla!

POST benchmark_index/_analyze { "analyzer": "with_edge_ngram", "text": "Elasticsearch" } // 7 token: ["el","ela","elas","elast","elasti","elastic","elastics"]


| Analyzer | "Elasticsearch" token sayısı | Index boyutu etkisi |
|----------|------------------------------|---------------------|
| standard | 1 | 1x |
| edge_ngram (2-8) | 7 | ~7x |
| ngram (2-4) | ~30 | ~30x |

Gerçekçi benchmark için 10.000+ dokümanı farklı analyzer'larla indexleyip `GET my_index/_stats/store` ile `size_in_bytes` karşılaştırın. Indexing hızı farkı genelde %10-30 arasıdır.

---

## 9. Multi-field Analyzer Testi

Bir field'ın birden fazla analyzer ile nasıl çalıştığını test etmek:

```json
PUT multi_analyzer_test
{
  "settings": {
    "analysis": {
      "filter": {
        "tr_lower": { "type": "lowercase", "language": "turkish" },
        "tr_stem": { "type": "stemmer", "language": "turkish" },
        "tr_stop": { "type": "stop", "stopwords": "_turkish_" },
        "edge_filter": { "type": "edge_ngram", "min_gram": 2, "max_gram": 10 }
      },
      "char_filter": {
        "diacritics": {
          "type": "mapping",
          "mappings": [
            "ş => s", "ç => c", "ğ => g",
            "ü => u", "ö => o", "ı => i"
          ]
        }
      },
      "analyzer": {
        "tr_full": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["tr_lower", "tr_stop", "tr_stem"]
        },
        "tr_folded": {
          "type": "custom",
          "char_filter": ["diacritics"],
          "tokenizer": "standard",
          "filter": ["lowercase", "tr_stop", "tr_stem"]
        },
        "tr_autocomplete": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["tr_lower", "edge_filter"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "tr_full",
        "fields": {
          "folded": { "type": "text", "analyzer": "tr_folded" },
          "autocomplete": {
            "type": "text",
            "analyzer": "tr_autocomplete",
            "search_analyzer": "standard"
          },
          "raw": { "type": "keyword" }
        }
      }
    }
  }
}

// Her sub-field'ın token'larını görmek
POST multi_analyzer_test/_analyze
{ "field": "title", "text": "Şehirdeki evlerin güzelliği" }
// tr_full: ["şehir", "ev", "güzel"]

POST multi_analyzer_test/_analyze
{ "field": "title.folded", "text": "Şehirdeki evlerin güzelliği" }
// tr_folded: ["sehir", "ev", "guzel"]

POST multi_analyzer_test/_analyze
{ "field": "title.autocomplete", "text": "Şehirdeki" }
// tr_autocomplete: ["şe", "şeh", "şehi", "şehir", "şehird", ...]

10. Yaygın Debug Senaryoları

Senaryo 1: match_phrase Çalışmıyor

// Doküman: "güçlü bir arama motoru"
// Sorgu: match_phrase "güçlü arama"
// Sonuç: BOŞ

// Neden? match_phrase ardışık pozisyon ister
POST my_index/_analyze
{
  "field": "description",
  "text": "güçlü bir arama motoru",
  "explain": true
}
// güçlü: pos 0, bir: pos 1 (stop filter silse bile), arama: pos 2
// "güçlü arama" → pos 0,1 bekler ama gerçekte pos 0,2

// Çözüm: slop parametresi ekleyin
GET my_index/_search
{
  "query": {
    "match_phrase": {
      "description": {
        "query": "güçlü arama",
        "slop": 1
      }
    }
  }
}

Senaryo 2: Synonym Çalışmıyor

// "araba" ararken "otomobil" bulunamıyor
// Debug: synonym filter sırası kontrol

POST my_index/_analyze
{
  "analyzer": "my_search_analyzer",
  "text": "araba",
  "explain": true
}
// explain çıktısında synonym filter'ın token'ı genişletip genişletmediğini kontrol edin
// Eğer stemmer synonym'den ÖNCE geliyorsa, "araba" → "arab" oluyor
// ve "arab" synonym listesinde yok!

Senaryo 3: Case Sensitivity Sorunu

// "IŞIK" araması "ışık" bulamıyor
POST my_index/_analyze
{
  "field": "title",
  "text": "IŞIK"
}
// Eğer standard lowercase kullanıyorsanız: ["isik"] — yanlış!
// Turkish lowercase gerekli: ["ışık"] — doğru!

11. Java ile Analyzer Testi

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.AnalyzeRequest;
import co.elastic.clients.elasticsearch.indices.AnalyzeResponse;
import co.elastic.clients.elasticsearch.indices.analyze.AnalyzeToken;

// Basit analyzer testi
AnalyzeResponse response = client.indices().analyze(a -> a
    .analyzer("turkish")
    .text("İstanbul'daki evlerin fiyatları arttı mı?")
);

System.out.println("=== Token Listesi ===");
for (AnalyzeToken token : response.tokens()) {
    System.out.printf("  [%d] %-15s (offset: %d-%d, type: %s)%n",
        token.position(),
        token.token(),
        token.startOffset(),
        token.endOffset(),
        token.type()
    );
}

// Index-specific analyzer testi
AnalyzeResponse indexResponse = client.indices().analyze(a -> a
    .index("my_index")
    .field("title")
    .text("Test metni")
);

// Explain ile detaylı analiz
AnalyzeResponse explainResponse = client.indices().analyze(a -> a
    .analyzer("standard")
    .text("Quick Brown Fox")
    .explain(true)
);
// explainResponse.detail() ile adım adım bilgi alabilirsiniz

Karşılaştırma helper metodu:

public static void compareAnalyzers(
    ElasticsearchClient client,
    String indexName,
    String[] analyzerNames,
    String text
) throws Exception {
    System.out.println("Metin: \"" + text + "\"");
    System.out.println("─".repeat(60));

    for (String analyzer : analyzerNames) {
        AnalyzeResponse resp = client.indices().analyze(a -> a
            .index(indexName)
            .analyzer(analyzer)
            .text(text)
        );

        String tokens = resp.tokens().stream()
            .map(AnalyzeToken::token)
            .reduce((a1, b) -> a1 + ", " + b)
            .orElse("(boş)");

        System.out.printf("  %-25s → [%s] (%d token)%n",
            analyzer, tokens, resp.tokens().size());
    }
}

// Kullanım
compareAnalyzers(client, "analyzer_lab",
    new String[]{"v1_basic", "v2_with_stop", "v3_with_stem"},
    "Türkiye'deki büyük şehirlerin bir listesi"
);

12. Best Practices

✅ Yapın

UygulamaNeden
Her analyzer değişikliğinden sonra _analyze ile test edinBeklentinizi doğrularsınız
explain: true kullanınHer adımda ne olduğunu görürsünüz
Test corpus'u hazırlayın (10+ örnek)Edge case'leri önceden yakalarsınız
Index-time VE search-time'ı ayrı test edinEşleşme sorunlarını önlersiniz
Analyzer lab index'i oluşturunHızlı deney yapabilirsiniz

❌ Yapmayın

UygulamaNeden
Analyzer'ı test etmeden production'a almayınDebug çok zor olur
Tek bir metinle test edip bırakmayınO metin çalışır, edge case patlar
Token sayısını görmezden gelmeyin30 token/kelime = index boyutu felaketi
field parametresini search-time testi sanmayınHer zaman index-time analyzer'ı döndürür

Özet

  • `_analyze` API Elasticsearch'ün en önemli debug aracıdır — her zaman kullanın

  • `explain: true` parametresi her token'ın hangi adımdan geçtiğini gösterir

  • Index-time vs search-time analyzer'ı ayrı test edin — field parametresi her zaman index-time döndürür

  • `_explain` API bir dokümanın neden belli bir skor aldığını açıklar (TF, IDF, boost)

  • Profile API sorgu performansını bileşen bazında ölçer — yavaş kısmı bulursunuz

  • Analyzer lab index'i oluşturup iteratif geliştirme yapın — tek seferde doğru yazmaya çalışmayın

  • Token sayısı doğrudan index boyutunu etkiler — ngram vs edge_ngram farkı 4x olabilir