← Kursa Dön
📄 Text · 35 min

Text Analysis Pipeline — Tokenizer, Filter, Char Filter

Giriş — Bir Mektubun Yolculuğu

Bir postaneye mektup gönderdiğinizi düşünün. Mektup önce zarftan çıkarılır ve üzerindeki lekeler temizlenir (character filter). Sonra kelime kelime ayrılıp etiketlenir (tokenizer). En sonunda da her kelime standart bir formata sokulur — büyük harfler küçültülür, gereksiz ekler atılır (token filter). İşte Elasticsearch'ün metinleri arama için hazırlarken yaptığı şey tam olarak budur.

Elasticsearch'te bir text alanına veri yazdığınızda, o metin doğrudan saklanmaz. Önce bir analysis pipeline'dan geçer. Bu pipeline'ın çıktısı, inverted index'e yazılan token'lardır. Aynı şekilde siz bir arama yaptığınızda, arama metniniz de (varsayılan olarak) aynı pipeline'dan geçer. İşte bu yüzden "Elasticsearch" yazdığınızda "elasticsearch" yazılmış bir dokümanı bulabilirsiniz — ikisi de aynı token'a dönüşür.

Bu ders, text analysis pipeline'ın üç katmanını derinlemesine inceleyecek. Bu kavramı anlamadan custom analyzer yazmak, mapping tasarlamak ya da arama kalitesini artırmak imkansızdır.


1. Text Analysis Nedir?

Text analysis, ham metni aranabilir token'lara dönüştürme sürecidir. Elasticsearch'te bu süreç her text tipindeki alana uygulanır.

Pipeline'ın Üç Katmanı

Ham Metin
    │
    ▼
┌─────────────────────┐
│  Character Filters   │  ← Karakter seviyesinde dönüşüm
│  (0 veya daha fazla) │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│     Tokenizer        │  ← Metni token'lara böler
│  (tam olarak 1 adet) │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│    Token Filters     │  ← Token seviyesinde dönüşüm
│  (0 veya daha fazla) │
└─────────────────────┘
          │
          ▼
    Token'lar → Inverted Index

Kritik kural: Bir analyzer tam olarak 1 tokenizer içermek zorundadır. Character filter ve token filter ise opsiyoneldir — 0, 1 veya birden fazla olabilir.

Neden Önemli?

Eğer analysis sürecini anlamazsanız, şu sorunlarla karşılaşırsınız:

  • "iPhone" araması "iphone" sonuçlarını getirmez

  • Türkçe "çalışıyor" araması "çalış" kökünü bulamaz

  • HTML içerikli alanlar <b> gibi etiketleri aranabilir token olarak saklar

  • E-posta adresleri yanlış yerlerde bölünür


2. Character Filters — Karakter Düzeyinde Temizlik

Character filter'lar, tokenizer'a gitmeden önce ham metin üzerinde karakter düzeyinde dönüşüm yapar. HTML etiketlerini temizlemek, özel karakterleri dönüştürmek veya karakter eşlemeleri yapmak için kullanılır.

2.1 Built-in Character Filters

Elasticsearch üç adet built-in character filter sunar:

a) html_strip — HTML Etiketlerini Temizler

Web scraping verileri ya da CMS içerikleri genellikle HTML etiketleri içerir. Bu filter onları temizler:

POST _analyze
{
  "char_filter": ["html_strip"],
  "tokenizer": "standard",
  "text": "<p>Elasticsearch <b>çok</b> hızlı bir <em>arama motoru</em>dur.</p>"
}

Çıktı token'ları: ["elasticsearch", "çok", "hızlı", "bir", "arama", "motoru", "dur"]

HTML entity'leri de çözümlenir:

POST _analyze
{
  "char_filter": ["html_strip"],
  "tokenizer": "keyword",
  "text": "Fiyat: 100 &amp; 200 &euro;"
}

Çıktı: "Fiyat: 100 & 200 €"

b) mapping — Karakter Eşleme

Belirli karakterleri başka karakterlere dönüştürür. Özellikle özel semboller veya kısaltmalar için kullanışlıdır:

PUT my_mapping_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        "emoticon_filter": {
          "type": "mapping",
          "mappings": [
            ":) => _mutlu_",
            ":( => _uzgun_",
            "<3 => _kalp_",
            "& => ve"
          ]
        }
      },
      "analyzer": {
        "emoticon_analyzer": {
          "type": "custom",
          "char_filter": ["emoticon_filter"],
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  }
}

Test edelim:

POST my_mapping_index/_analyze
{
  "analyzer": "emoticon_analyzer",
  "text": "Bugün hava güzel :) ve güneşli <3"
}

Çıktı token'ları: ["bugün", "hava", "güzel", "_mutlu_", "ve", "güneşli", "_kalp_"]

Dikkat edin: :) artık _mutlu_ token'ına dönüştü ve aranabilir hale geldi!

c) pattern_replace — Regex ile Değiştirme

Regular expression kullanarak karakter dönüşümü yapar:

PUT my_pattern_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        "phone_normalizer": {
          "type": "pattern_replace",
          "pattern": "[\\-\\(\\)\\s]",
          "replacement": ""
        }
      },
      "analyzer": {
        "phone_analyzer": {
          "type": "custom",
          "char_filter": ["phone_normalizer"],
          "tokenizer": "keyword",
          "filter": []
        }
      }
    }
  }
}

Test:

POST my_pattern_index/_analyze
{
  "analyzer": "phone_analyzer",
  "text": "+90 (532) 123-45-67"
}

Çıktı: ["+905321234567"]

Telefon numarası boşluk, tire ve parantezden arındı — artık hangi formatta yazılırsa yazılsın aynı token'a dönüşecek.

2.2 Birden Fazla Character Filter Zinciri

Character filter'lar sıralı çalışır — biri diğerinin çıktısını alır:

PUT multi_char_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        "html_cleaner": {
          "type": "html_strip"
        },
        "special_chars": {
          "type": "mapping",
          "mappings": [
            "& => ve",
            "@ => at"
          ]
        }
      },
      "analyzer": {
        "clean_analyzer": {
          "type": "custom",
          "char_filter": ["html_cleaner", "special_chars"],
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      }
    }
  }
}

Sıra önemlidir: Önce HTML temizlenir, sonra &ve dönüşümü yapılır. Sırayı tersine çevirirseniz, &amp; entity'si henüz çözümlenmediği için mapping çalışmaz.


3. Tokenizer — Metni Token'lara Bölme

Tokenizer, analysis pipeline'ın kalbidir. Character filter'lardan geçmiş metni alır ve onu parçalara (token'lara) böler. Her analyzer tam olarak bir tokenizer içerir.

3.1 Word-Oriented Tokenizer'lar

a) standard Tokenizer

En yaygın kullanılan tokenizer. Unicode Text Segmentation algoritmasını kullanır, çoğu dilde iyi çalışır:

POST _analyze
{
  "tokenizer": "standard",
  "text": "Elasticsearch'ün 8.x sürümü çok güçlü!"
}

Çıktı: ["Elasticsearch'ün", "8.x", "sürümü", "çok", "güçlü"]

Noktalama işaretlerini kaldırır ama apostrof ve nokta gibi kelime içi karakterleri korur.

b) letter Tokenizer

Sadece harflere göre böler — harf olmayan her karakter ayırıcıdır:

POST _analyze
{
  "tokenizer": "letter",
  "text": "user@email.com IP:192.168.1.1"
}

Çıktı: ["user", "email", "com", "IP", "x"] — rakamlar bile ayırıcı olarak işlenir (beklenti dışı olabilir).

c) whitespace Tokenizer

Yalnızca boşluk karakterlerine göre böler — noktalama, özel karakter dahil her şeyi korur:

POST _analyze
{
  "tokenizer": "whitespace",
  "text": "Fiyat: $99.99 (KDV dahil)"
}

Çıktı: ["Fiyat:", "$99.99", "(KDV", "dahil)"]

Parantez ve iki nokta token'ın parçası olarak kalır. Bu genellikle istenmeyen bir durumdur, ama log analizi gibi bazı senaryolarda faydalıdır.

d) uax_url_email Tokenizer

URL ve e-posta adreslerini bölmeden tek token olarak tanır:

POST _analyze
{
  "tokenizer": "uax_url_email",
  "text": "Bize info@example.com adresinden veya https://example.com/contact sayfasından ulaşın."
}

Çıktı: ["Bize", "info@example.com", "adresinden", "veya", "https://example.com/contact", "sayfasından", "ulaşın"]

3.2 Partial Word Tokenizer'lar

a) ngram Tokenizer

Metni n-gram'lara böler. Autocomplete veya fuzzy matching için kullanılır:

POST _analyze
{
  "tokenizer": {
    "type": "ngram",
    "min_gram": 3,
    "max_gram": 5,
    "token_chars": ["letter", "digit"]
  },
  "text": "Arama"
}

Çıktı: ["Ara", "Aram", "Arama", "ram", "rama", "ama"]

⚠️ Dikkat: N-gram tokenizer index boyutunu ciddi şekilde artırır. min_gram ile max_gram arasındaki fark büyüdükçe token sayısı patlar.

b) edge_ngram Tokenizer

Sadece kelimenin başından itibaren n-gram oluşturur. Autocomplete (search-as-you-type) için idealdir:

POST _analyze
{
  "tokenizer": {
    "type": "edge_ngram",
    "min_gram": 2,
    "max_gram": 8,
    "token_chars": ["letter", "digit"]
  },
  "text": "Elasticsearch"
}

Çıktı: ["El", "Ela", "Elas", "Elast", "Elasti", "Elastic", "Elastics"]

Kullanıcı "Ela" yazdığında bile "Elasticsearch" bulunur.

3.3 Structured Text Tokenizer'lar

a) keyword Tokenizer

Metni hiç bölmez — tüm girdiyi tek token olarak döndürür:

POST _analyze
{
  "tokenizer": "keyword",
  "text": "New York City"
}

Çıktı: ["New York City"]

Normalde tam eşleşme için keyword field type kullanılır, ama bazı durumlarda keyword tokenizer + lowercase filter kombinasyonu tercih edilir.

b) pattern Tokenizer

Regex pattern'a göre böler:

POST _analyze
{
  "tokenizer": {
    "type": "pattern",
    "pattern": "[,;|]"
  },
  "text": "elma,armut;portakal|muz"
}

Çıktı: ["elma", "armut", "portakal", "muz"]

c) path_hierarchy Tokenizer

Dosya yolu yapılarını hiyerarşik olarak token'lara böler:

POST _analyze
{
  "tokenizer": "path_hierarchy",
  "text": "/var/log/elasticsearch/cluster.log"
}

Çıktı: ["/var", "/var/log", "/var/log/elasticsearch", "/var/log/elasticsearch/cluster.log"]

Bu sayede /var/log araması, alt dizinlerdeki tüm dosyaları bulur.


4. Token Filters — Token Düzeyinde Dönüşüm

Token filter'lar, tokenizer'dan çıkan token'ları alır ve üzerlerinde çeşitli dönüşümler yapar: küçük harfe çevirme, köke indirgeme (stemming), eşanlamlı ekleme, durdurma kelimeleri çıkarma vb.

4.1 Temel Token Filters

a) lowercase

Tüm harfleri küçük harfe çevirir:

POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "Elasticsearch GÜÇLÜ bir Arama Motoru"
}

Çıktı: ["elasticsearch", "güçlü", "bir", "arama", "motoru"]

b) uppercase

Tüm harfleri büyük harfe çevirir (nadiren kullanılır):

POST _analyze
{
  "tokenizer": "standard",
  "filter": ["uppercase"],
  "text": "hello world"
}

Çıktı: ["HELLO", "WORLD"]

c) stop — Durdurma Kelimeleri

"ve", "bir", "bu" gibi anlamsız kelimeleri kaldırır:

POST _analyze
{
  "tokenizer": "standard",
  "filter": [
    "lowercase",
    {
      "type": "stop",
      "stopwords": ["ve", "bir", "bu", "ile", "için", "de", "da"]
    }
  ],
  "text": "Bu bir Elasticsearch ve Kibana için güzel bir kaynak"
}

Çıktı: ["elasticsearch", "kibana", "güzel", "kaynak"]

Durdurma kelimeleri çıkarılınca geriye sadece anlamlı terimler kalır.

d) stemmer — Kök Bulma

Kelimeleri köklerine indirger:

POST _analyze
{
  "tokenizer": "standard",
  "filter": [
    "lowercase",
    {
      "type": "stemmer",
      "language": "turkish"
    }
  ],
  "text": "çalışanlar çalışıyorlardı çalışmak"
}

Çıktı: ["çalış", "çalış", "çalış"] — Hepsi aynı köke indirgendi.

4.2 İleri Token Filters

a) synonym — Eşanlamlılar

Eşanlamlı kelimeleri birbirine bağlar:

PUT synonym_index
{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonyms": {
          "type": "synonym",
          "synonyms": [
            "hızlı, çabuk, süratli",
            "büyük, iri, kocaman",
            "ES => Elasticsearch"
          ]
        }
      },
      "analyzer": {
        "synonym_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "my_synonyms"]
        }
      }
    }
  }
}

Test:

POST synonym_index/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "ES çok hızlı"
}

"hızlı" araması artık "çabuk" veya "süratli" içeren dokümanları da bulur.

b) trim — Boşluk Temizleme

Token'ların başındaki ve sonundaki boşlukları temizler:

POST _analyze
{
  "tokenizer": "keyword",
  "filter": ["trim"],
  "text": "  Elasticsearch  "
}

Çıktı: ["Elasticsearch"]

c) unique — Tekrar Eden Token'ları Kaldırma

Aynı pozisyondaki tekrar eden token'ları kaldırır:

POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase", "unique"],
  "text": "hızlı hızlı çok hızlı"
}

Çıktı: ["hızlı", "çok"]

d) truncate — Token Uzunluğu Sınırlama

Token'ları belirli bir uzunlukta keser:

POST _analyze
{
  "tokenizer": "standard",
  "filter": [
    {
      "type": "truncate",
      "length": 5
    }
  ],
  "text": "Elasticsearch Analizi"
}

Çıktı: ["Elast", "Anali"]

e) length — Uzunluğa Göre Filtreleme

Belirli uzunluk aralığında olmayan token'ları tamamen kaldırır:

POST _analyze
{
  "tokenizer": "standard",
  "filter": [
    {
      "type": "length",
      "min": 3,
      "max": 10
    }
  ],
  "text": "Bu bir ES arama motoru uygulamasıdır"
}

Çıktı: ["bir", "arama", "motoru"] — 3'ten kısa ve 10'dan uzun token'lar çıkarıldı.

4.3 Token Filter Sıralaması

Token filter'lar sıralı çalışır ve sıralama sonucu etkiler:

// ❌ Yanlış sıralama — önce stemmer, sonra stop
"filter": ["stemmer", "stop"]

// ✅ Doğru sıralama — önce stop, sonra stemmer
"filter": ["lowercase", "stop", "stemmer"]

Neden? Eğer stemmer önce çalışırsa, "running" kelimesini "run" yapabilir ama "the" kelimesi hâlâ orada kalır ve stop filter onu yakalayamayabilir (çünkü stemmer "the" gibi kısa kelimeleri değiştirmez — ama daha karmaşık dillerde fark yaratabilir).

Genel doğru sıralama:

  1. lowercase — Önce hepsini küçük harfe çevir

  2. stop — Durdurma kelimelerini çıkar

  3. stemmer — Kalan kelimeleri köke indirge

  4. synonym — Eşanlamlıları ekle (genelde en sonda)


5. _analyze API — Pipeline'ı Test Etme

Elasticsearch, analysis sürecini test etmek için güçlü bir _analyze API sunar. Bu API'yi anlamak, debug sürecinin temelidir.

5.1 Basit Kullanım

Tokenizer ile:

POST _analyze
{
  "tokenizer": "standard",
  "text": "Merhaba Dünya!"
}

Analyzer ile:

POST _analyze
{
  "analyzer": "standard",
  "text": "Merhaba Dünya!"
}

5.2 Index-Specific Analyzer Test

Bir index'e tanımlı custom analyzer'ı test etmek:

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

Veya belirli bir field'ın analyzer'ını test etmek:

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

5.3 Detaylı Çıktı

explain: true parametresi ile her adımı detaylı görürsünüz:

POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase", "stop"],
  "text": "The Quick Brown Fox",
  "explain": true
}

Bu çıktı, her token'ın hangi filter'dan nasıl geçtiğini adım adım gösterir. Debug için çok değerlidir.


6. Index-Time vs Search-Time Analysis

Analysis iki farklı anda gerçekleşir ve bu ayrımı anlamak kritik öneme sahiptir.

Index-Time Analysis

Bir doküman index'lenirken, text alanlarındaki değerler analyzer'dan geçirilir ve inverted index'e token'lar olarak yazılır:

Orijinal: "Elasticsearch çok hızlı bir arama motoru"

Index-time analysis sonucu (inverted index):
- elasticsearch → doc1
- çok → doc1
- hızlı → doc1
- bir → doc1
- arama → doc1
- motoru → doc1

Search-Time Analysis

Bir arama yapıldığında, sorgu metni de aynı (veya farklı) analyzer'dan geçer:

Sorgu: "Elasticsearch Arama"
Search-time analysis sonucu: ["elasticsearch", "arama"]

Inverted index'te bu token'lar aranır ve doc1 bulunur.

Farklı Analyzer Kullanımı

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

PUT my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "my_index_analyzer",
        "search_analyzer": "my_search_analyzer"
      }
    }
  }
}

Tipik kullanım senaryosu — Autocomplete:

  • Index-time: Edge ngram analyzer kullanılır → "Elastic" → ["El", "Ela", "Elas", "Elast", "Elasti", "Elastic"]

  • Search-time: Standard analyzer kullanılır → "Ela" → ["ela"]

  • "ela" token'ı, index'teki "Ela" token'ıyla eşleşir ✓

Eğer search-time'da da edge ngram kullansaydınız, "Elasticsearch" araması "El", "Ela" vb. token'lara bölünür ve istenmeyen sonuçlar gelirdi.


7. Java ile Analysis API

Bölüm 8'de Java client detaylı işlenecek, ama analysis API'nin Java karşılığını görelim:

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;

// Analyzer test etme
AnalyzeRequest request = AnalyzeRequest.of(a -> a
    .analyzer("standard")
    .text("Elasticsearch çok hızlı bir arama motoru")
);

AnalyzeResponse response = client.indices().analyze(request);

for (AnalyzeToken token : response.tokens()) {
    System.out.printf("Token: %-20s | Pozisyon: %d | Offset: %d-%d%n",
        token.token(),
        token.position(),
        token.startOffset(),
        token.endOffset()
    );
}

Custom analyzer ile index oluşturma:

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

// Custom analyzer tanımlı index oluşturma
client.indices().create(c -> c
    .index("products_tr")
    .settings(s -> s
        .analysis(a -> a
            .charFilter("html_cleaner", cf -> cf
                .definition(d -> d
                    .htmlStrip(h -> h)
                )
            )
            .analyzer("turkish_analyzer", an -> an
                .custom(cu -> cu
                    .tokenizer("standard")
                    .charFilter("html_cleaner")
                    .filter("lowercase", "turkish_stop", "turkish_stemmer")
                )
            )
            .filter("turkish_stop", f -> f
                .definition(d -> d
                    .stop(st -> st.stopwords("_turkish_"))
                )
            )
            .filter("turkish_stemmer", f -> f
                .definition(d -> d
                    .stemmer(st -> st.language("turkish"))
                )
            )
        )
    )
    .mappings(m -> m
        .properties("description", p -> p
            .text(t -> t.analyzer("turkish_analyzer"))
        )
    )
);

8. Gerçek Dünya Örneği: E-Ticaret Ürün Arama

Bir e-ticaret sitesinin ürün açıklamalarını doğru şekilde index'lemek için tam bir pipeline oluşturalım:

PUT ecommerce_products
{
  "settings": {
    "analysis": {
      "char_filter": {
        "html_cleaner": {
          "type": "html_strip"
        },
        "special_symbols": {
          "type": "mapping",
          "mappings": [
            "& => ve",
            "% => yüzde",
            "₺ => TL",
            "+ => artı"
          ]
        }
      },
      "tokenizer": {
        "product_tokenizer": {
          "type": "standard",
          "max_token_length": 50
        }
      },
      "filter": {
        "turkish_stop": {
          "type": "stop",
          "stopwords": "_turkish_"
        },
        "turkish_stemmer": {
          "type": "stemmer",
          "language": "turkish"
        },
        "product_length": {
          "type": "length",
          "min": 2,
          "max": 30
        }
      },
      "analyzer": {
        "product_analyzer": {
          "type": "custom",
          "char_filter": ["html_cleaner", "special_symbols"],
          "tokenizer": "product_tokenizer",
          "filter": [
            "lowercase",
            "turkish_stop",
            "turkish_stemmer",
            "product_length"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "product_name": {
        "type": "text",
        "analyzer": "product_analyzer"
      },
      "description": {
        "type": "text",
        "analyzer": "product_analyzer"
      },
      "category": {
        "type": "keyword"
      },
      "price": {
        "type": "float"
      }
    }
  }
}

Pipeline'ı test edelim:

POST ecommerce_products/_analyze
{
  "analyzer": "product_analyzer",
  "text": "<p>Samsung Galaxy S24 Ultra <b>akıllı telefon</b> &amp; kılıfı — 256GB depolama</p>"
}

Adım adım:

  1. html_cleaner: HTML etiketleri temizlenir → "Samsung Galaxy S24 Ultra akıllı telefon & kılıfı — 256GB depolama"

  2. special_symbols: &ve dönüşür → "Samsung Galaxy S24 Ultra akıllı telefon ve kılıfı — 256GB depolama"

  3. product_tokenizer: Kelimelere bölünür

  4. lowercase: Küçük harfe çevrilir

  5. turkish_stop: Durdurma kelimeleri çıkarılır (ve)

  6. turkish_stemmer: Köklerine indirgenir (kılıfıkılıf, akıllıakıl)

  7. product_length: 2'den kısa token'lar çıkarılır

Veri ekleyip test edelim:

POST ecommerce_products/_doc/1
{
  "product_name": "Samsung Galaxy S24 Ultra",
  "description": "<p>Samsung Galaxy S24 Ultra <b>akıllı telefon</b> &amp; kılıfı — 256GB depolama</p>",
  "category": "Elektronik",
  "price": 54999.99
}

POST ecommerce_products/_doc/2
{
  "product_name": "Apple iPhone 15 Pro Max",
  "description": "<p>Apple iPhone 15 Pro Max <em>akıllı telefon</em> — 512GB depolama alanı</p>",
  "category": "Elektronik",
  "price": 64999.99
}

GET ecommerce_products/_search
{
  "query": {
    "match": {
      "description": "akıllı telefonlar"
    }
  }
}

"akıllı telefonlar" araması → stemmer sayesinde "akıl" ve "telefon" köklerini bulur → her iki dokümanı da döndürür.


9. Best Practices

✅ Yapın

UygulamaNeden
_analyze API ile her zaman test edinBeklenmedik token'ları önceden yakalarsınız
Filter sıralamasına dikkat edinlowercase → stop → stemmer sırası idealdir
HTML içerik varsa html_strip kullanınEtiketler aranabilir token olmamalı
Index-time ve search-time analyzer'ı ayrı düşününAutocomplete gibi senaryolarda farklı analyzer gerekir
Token filter'ları minimum tutunHer eklenen filter index boyutunu ve performansı etkiler

❌ Yapmayın

UygulamaNeden
Aynı field'a hem ngram hem stemmer uygulamayınToken patlaması yaratır, index büyür
max_gram değerini çok yüksek tutmayınN-gram ile 20+ gram = performans felaketi
Stop words listesini aşırı genişletmeyinBazı "durdurma" kelimeleri anlamlı sorgu parçası olabilir
Synonym'leri sadece index-time'da kullanmayınYeni eşanlamlı ekleyince reindex gerekir; synonym_graph + search-time düşünün

10. Yaygın Hatalar ve Çözümleri

Hata 1: Analyzer Tanımlamayı Unutmak

// ❌ Varsayılan standard analyzer kullanılır
PUT my_index
{
  "mappings": {
    "properties": {
      "content": { "type": "text" }
    }
  }
}

Türkçe içerik varsa, standard analyzer yetersiz kalır — stemming ve stop words uygulanmaz.

// ✅ Türkçe analyzer belirtin
PUT my_index
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "turkish"
      }
    }
  }
}

Hata 2: Keyword Field'a Analyzer Uygulamak

// ❌ keyword tipi zaten analiz edilmez, analyzer anlamsız
"email": {
  "type": "keyword",
  "analyzer": "standard"
}
// ✅ keyword tipi olduğu gibi saklanır — normalizer kullanabilirsiniz
"email": {
  "type": "keyword",
  "normalizer": "lowercase_normalizer"
}

Hata 3: Character Filter ve Token Filter Karıştırmak

Character filter karakter seviyesinde, tokenizer'dan önce çalışır. Token filter token seviyesinde, tokenizer'dan sonra çalışır. HTML temizleme → character filter. Küçük harfe çevirme → token filter.

Hata 4: Token Sayısı Limitini Bilmemek

Elasticsearch varsayılan olarak bir field'da maksimum 10.000 token kabul eder. Çok uzun metinler kesilir:

// Limiti artırma (dikkatli kullanın)
PUT my_index/_settings
{
  "index.analyze.max_token_count": 20000
}

💡 İpucu: Limiti artırmak yerine, metni daha kısa parçalara bölmeyi düşünün.


11. Performans Notları

Analiz Maliyeti

Her text field'ı index-time'da analiz edilir. Bu şu anlama gelir:

  • Daha fazla character filter = daha fazla CPU (ama genelde ihmal edilebilir)

  • Daha karmaşık tokenizer (ngram vs standard) = daha fazla token = daha büyük index

  • Daha fazla token filter = her token için daha fazla işlem

Bellek Kullanımı

Synonym ve stop words listeleri bellekte tutulur. Binlerce eşanlamlı tanımlıyorsanız, bellek kullanımını izleyin.

Token Sayısı ve Index Boyutu

Tokenizer"Elasticsearch" için token sayısı
standard1
ngram (2-4)11
ngram (2-8)32
edge_ngram (2-8)7

N-gram kullanıyorsanız, index boyutunun 5-10x artmasına hazır olun.


Özet

  • Text analysis pipeline üç katmandan oluşur: Character Filter → Tokenizer → Token Filter

  • Character filter'lar karakter seviyesinde çalışır (HTML temizleme, karakter eşleme, regex)

  • Tokenizer metni token'lara böler — her analyzer'da tam olarak 1 adet olmalıdır

  • Token filter'lar token seviyesinde dönüşüm yapar (lowercase, stemmer, synonym, stop)

  • Filter sırası kritiktir — lowercase → stop → stemmer ideal sıralamadır

  • `_analyze` API pipeline'ı test etmek için vazgeçilmez araçtır — her zaman test edin

  • Index-time vs search-time analysis farklıdır — autocomplete gibi senaryolarda farklı analyzer kullanılır