Elasticsearch ile Uygulama Arama: Autocomplete ve Fuzzy Arama Rehberi

Bir e-ticaret sitesi yönetiyorsunuz ve kullanıcılar arama kutusuna “laptp” yazıyor ama hiçbir sonuç gelmiyor. Ya da “samsung” yerine “samsng” yazıyorlar ve sistem elleri boş dönüyor. Bu tür durumlar kullanıcı deneyimini doğrudan mahvediyor ve dönüşüm oranlarınızı düşürüyor. İşte tam bu noktada Elasticsearch’ün autocomplete ve fuzzy search özellikleri devreye giriyor. Bu yazıda gerçek dünya senaryoları üzerinden bu iki kritik özelliği nasıl kurup yöneteceğinizi ele alacağız.

Elasticsearch Neden Bu İş İçin Doğru Araç?

Geleneksel SQL veritabanlarında LIKE '%laptop%' sorguları performans felaketi yaratır, üstelik yazım hatalarını hiç tolere etmez. Elasticsearch ise Lucene tabanlı yapısı sayesinde tam metin aramasını, yaklaşık eşleşmeyi ve gerçek zamanlı önerileri milisaniyeler içinde sunabilir.

Elasticsearch’ün bu alanda öne çıkan özellikleri şunlardır:

  • Inverted index: Her kelimeyi ve bu kelimenin hangi dokümanlarda geçtiğini tutar
  • Analyzer pipeline: Metni tokenize eder, normalize eder, stemming uygular
  • Edge N-gram tokenizer: Autocomplete için biçilmiş kaftan
  • Fuzziness parametresi: Levenshtein mesafesine göre yazım hatalarını tolere eder
  • Search-as-you-type field tipi: 8.x versiyonuyla gelen hazır autocomplete çözümü

Elasticsearch Kurulumu ve Temel Yapılandırma

Önce çalışma ortamımızı hazırlayalım. Ubuntu 22.04 üzerinde Elasticsearch 8.x kuruyoruz.

# GPG anahtarını ekle
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg

# Repository tanımla
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list

# Kur ve başlat
sudo apt update && sudo apt install elasticsearch -y
sudo systemctl enable --now elasticsearch

# Kurulum sırasında oluşturulan şifreyi al
sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic

Geliştirme ortamında güvenliği geçici olarak devre dışı bırakmak için /etc/elasticsearch/elasticsearch.yml dosyasını düzenleyin:

sudo nano /etc/elasticsearch/elasticsearch.yml

Dosyaya şu satırları ekleyin:

# Geliştirme ortamı için - production'da kullanmayın!
xpack.security.enabled: false
xpack.security.http.ssl:
  enabled: false
network.host: localhost
http.port: 9200

Kurulumu doğrulayalım:

curl -X GET "localhost:9200"
# Ya da güvenlik açıksa:
curl -X GET "localhost:9200" -u elastic:SIFRENIZ

Autocomplete için Index Tasarımı

Autocomplete’in kalbi doğru index mapping’de gizlidir. İki farklı yaklaşım var: edge n-gram kullanmak ya da search_as_you_type field tipini kullanmak. Her ikisini de inceleyelim.

Edge N-gram ile Klasik Yaklaşım

Edge n-gram, bir kelimenin başından itibaren artan uzunluklarda parçalar üretir. “laptop” kelimesi için “l”, “la”, “lap”, “lapt”, “lapto”, “laptop” token’larını oluşturur.

curl -X PUT "localhost:9200/urunler" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "analysis": {
      "analyzer": {
        "autocomplete_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "autocomplete_filter"
          ]
        },
        "autocomplete_search_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      },
      "filter": {
        "autocomplete_filter": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 20
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "urun_adi": {
        "type": "text",
        "analyzer": "autocomplete_analyzer",
        "search_analyzer": "autocomplete_search_analyzer"
      },
      "kategori": {
        "type": "keyword"
      },
      "fiyat": {
        "type": "float"
      },
      "stok": {
        "type": "integer"
      }
    }
  }
}'

Burada kritik bir nokta var: search_analyzer olarak standart analyzer kullanıyoruz. Eğer arama sırasında da edge n-gram uygularsak, “lap” kelimesi “l”, “la”, “lap” token’larına bölünür ve bu gereksiz eşleşmeler yaratır.

Search-as-you-type Field Tipi (Modern Yaklaşım)

Elasticsearch 7.2+ ile gelen bu field tipi, birden fazla alt field otomatik olarak oluşturur:

curl -X PUT "localhost:9200/urunler_v2" -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "urun_adi": {
        "type": "search_as_you_type",
        "max_shingle_size": 3
      },
      "marka": {
        "type": "search_as_you_type"
      },
      "aciklama": {
        "type": "text"
      },
      "fiyat": {
        "type": "float"
      }
    }
  }
}'

search_as_you_type field’ı otomatik olarak şu alt field’ları oluşturur:

  • urun_adi: Ana field, standard analyzer
  • urun_adi._2gram: İki kelimelik shingle kombinasyonları
  • urun_adi._3gram: Üç kelimelik shingle kombinasyonları
  • urun_adi._index_prefix: Edge n-gram prefix için

Test Verisi Ekleyelim

curl -X POST "localhost:9200/urunler/_bulk" -H 'Content-Type: application/json' -d'
{"index": {"_id": "1"}}
{"urun_adi": "Samsung Galaxy S23 Ultra Akıllı Telefon", "kategori": "telefon", "fiyat": 45000, "stok": 50}
{"index": {"_id": "2"}}
{"urun_adi": "Apple MacBook Pro 14 inç M3", "kategori": "laptop", "fiyat": 85000, "stok": 20}
{"index": {"_id": "3"}}
{"urun_adi": "Lenovo ThinkPad X1 Carbon Laptop", "kategori": "laptop", "fiyat": 62000, "stok": 15}
{"index": {"_id": "4"}}
{"urun_adi": "Samsung 65 inç QLED 4K Televizyon", "kategori": "tv", "fiyat": 38000, "stok": 8}
{"index": {"_id": "5"}}
{"urun_adi": "Apple iPhone 15 Pro Max", "kategori": "telefon", "fiyat": 52000, "stok": 35}
{"index": {"_id": "6"}}
{"urun_adi": "Sony WH-1000XM5 Kablosuz Kulaklık", "kategori": "kulaklık", "fiyat": 12000, "stok": 45}
{"index": {"_id": "7"}}
{"urun_adi": "Logitech MX Master 3 Mouse", "kategori": "aksesuar", "fiyat": 3500, "stok": 100}
'

Autocomplete Sorgusu Yazmak

Edge n-gram index üzerinde autocomplete sorgusu çalıştıralım:

curl -X GET "localhost:9200/urunler/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "urun_adi": {
              "query": "sam",
              "boost": 1
            }
          }
        },
        {
          "match_phrase_prefix": {
            "urun_adi": {
              "query": "sam",
              "boost": 2
            }
          }
        }
      ]
    }
  },
  "_source": ["urun_adi", "fiyat", "kategori"],
  "size": 5
}'

search_as_you_type kullanan index için sorgu biraz farklı:

curl -X GET "localhost:9200/urunler_v2/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "multi_match": {
      "query": "macbook pro",
      "type": "bool_prefix",
      "fields": [
        "urun_adi",
        "urun_adi._2gram",
        "urun_adi._3gram",
        "urun_adi._index_prefix"
      ]
    }
  },
  "_source": ["urun_adi", "fiyat"],
  "size": 5
}'

bool_prefix tipi, son token’ı prefix olarak değerlendirir. Yani “macbook p” yazıldığında “macbook” tam eşleşme, “p” prefix araması olarak işlenir. Bu autocomplete için mükemmel.

Fuzzy Search: Yazım Hatalarını Tolere Etmek

Şimdi asıl eğlenceli kısma geliyoruz. Kullanıcı “samsng” yazdığında “Samsung” ürünlerini nasıl bulacağız?

Fuzzy search, Levenshtein edit distance algoritmasını kullanır. Bu mesafe iki string arasındaki minimum karakter ekleme, silme ve değiştirme sayısını ifade eder. “samsng” ile “samsung” arasındaki Levenshtein mesafesi 1’dir (bir karakter ekleme).

curl -X GET "localhost:9200/urunler/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "urun_adi": {
        "query": "samsng",
        "fuzziness": "AUTO",
        "fuzzy_transpositions": true
      }
    }
  },
  "_source": ["urun_adi", "fiyat"],
  "size": 5
}'

fuzziness parametresi değerleri:

  • 0: Fuzzy yok, tam eşleşme gerekir
  • 1: 1 edit distance, kısa kelimeler için yeterli
  • 2: 2 edit distance, daha toleranslı
  • AUTO: Kelime uzunluğuna göre otomatik seçer (1-2 karakter için 0, 3-5 karakter için 1, 6+ karakter için 2)

fuzzy_transpositions: “ab” yerine “ba” gibi yer değiştirmeleri tek edit olarak sayar. Genellikle açık bırakmanız önerilir.

Fuzzy ile Autocomplete Birleştirme

Gerçek dünya uygulamalarında ikisini birden kullanmak gerekir. Kullanıcı hem hızlı yazıyor hem de yazım hatası yapıyor olabilir:

curl -X GET "localhost:9200/urunler/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "urun_adi": {
              "query": "lapt",
              "fuzziness": "AUTO",
              "boost": 1,
              "operator": "and"
            }
          }
        },
        {
          "match_phrase_prefix": {
            "urun_adi": {
              "query": "lapt",
              "boost": 3,
              "max_expansions": 50
            }
          }
        }
      ],
      "minimum_should_match": 1
    }
  },
  "highlight": {
    "fields": {
      "urun_adi": {}
    }
  },
  "_source": ["urun_adi", "fiyat", "kategori"],
  "size": 10
}'

Burada boost parametresiyle prefix eşleşmesine daha yüksek ağırlık veriyoruz. Yani “lapt” yazan birisine önce gerçekten “lapt” ile başlayan ürünler, sonra fuzzy eşleşmeler gelsin.

Türkçe Karakter Sorunlarını Çözmek

Türkçe arama yaparken “i/İ” ve “ı/I” karışıklığı ciddi sorunlara yol açar. “istanbul” aramasıyla “İstanbul” bulunamayabilir. Bunun için özel analyzer tanımlamak gerekir:

curl -X PUT "localhost:9200/urunler_tr" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "analysis": {
      "analyzer": {
        "turkish_autocomplete": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "turkish_stop",
            "autocomplete_ngram"
          ],
          "char_filter": ["turkish_char_filter"]
        }
      },
      "char_filter": {
        "turkish_char_filter": {
          "type": "mapping",
          "mappings": [
            "ı => i",
            "İ => i",
            "ş => s",
            "Ş => s",
            "ğ => g",
            "Ğ => g",
            "ü => u",
            "Ü => u",
            "ö => o",
            "Ö => o",
            "ç => c",
            "Ç => c"
          ]
        }
      },
      "filter": {
        "turkish_stop": {
          "type": "stop",
          "stopwords": ["ve", "ile", "için", "bu", "bir", "da", "de", "ki"]
        },
        "autocomplete_ngram": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 25
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "urun_adi": {
        "type": "text",
        "analyzer": "turkish_autocomplete",
        "search_analyzer": "standard",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}'

Bu yaklaşım Türkçe karakterleri normalize ederek arama tutarlılığını artırır. Ancak ticari bir uygulama için Elasticsearch’ün resmi turkish analyzer’ını da değerlendirin, çünkü stemming (kök bulma) desteği de içerir.

Completion Suggester ile Profesyonel Autocomplete

Elasticsearch’ün completion suggester’ı, özellikle yüksek hacimli autocomplete için optimize edilmiş bir FST (Finite State Transducer) yapısı kullanır. RAM’e tamamen yüklenir, bu yüzden çok hızlıdır.

curl -X PUT "localhost:9200/urunler_suggest" -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "urun_adi": {
        "type": "text"
      },
      "suggest": {
        "type": "completion",
        "analyzer": "simple",
        "preserve_separators": true,
        "preserve_position_increments": true,
        "max_input_length": 50
      },
      "fiyat": {
        "type": "float"
      }
    }
  }
}'

# Veri eklerken suggest field'ını da doldurun
curl -X POST "localhost:9200/urunler_suggest/_doc/1" -H 'Content-Type: application/json' -d'
{
  "urun_adi": "Samsung Galaxy S23 Ultra",
  "suggest": {
    "input": ["Samsung Galaxy S23 Ultra", "Galaxy S23", "S23 Ultra", "samsung s23"],
    "weight": 10
  },
  "fiyat": 45000
}'

Completion suggester ile arama yapmak için:

curl -X GET "localhost:9200/urunler_suggest/_search" -H 'Content-Type: application/json' -d'
{
  "suggest": {
    "urun-onerisi": {
      "prefix": "sam",
      "completion": {
        "field": "suggest",
        "size": 5,
        "skip_duplicates": true,
        "fuzzy": {
          "fuzziness": 1
        }
      }
    }
  }
}'

weight parametresi sıralama için kullanılır. Örneğin çok satan ya da öne çıkarmak istediğiniz ürünlere yüksek weight vererek arama sonuçlarında önce çıkmalarını sağlayabilirsiniz.

Aggregation ile Akıllı Filtreleme

Autocomplete ile birlikte kategori filtresi sunmak kullanıcı deneyimini büyük ölçüde iyileştirir:

curl -X GET "localhost:9200/urunler/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "urun_adi": {
        "query": "samsung",
        "fuzziness": "AUTO"
      }
    }
  },
  "aggs": {
    "kategoriler": {
      "terms": {
        "field": "kategori",
        "size": 10
      }
    },
    "fiyat_araligi": {
      "range": {
        "field": "fiyat",
        "ranges": [
          {"to": 10000, "key": "10000 altı"},
          {"from": 10000, "to": 50000, "key": "10000-50000"},
          {"from": 50000, "key": "50000 üzeri"}
        ]
      }
    }
  },
  "_source": ["urun_adi", "fiyat", "kategori"],
  "size": 10
}'

Performans Optimizasyonu

Prodüksiyon ortamında performans kritik. Şu noktalara dikkat edin:

Index ayarları için:

  • number_of_shards: Küçük veri setleri için 1-2 shard yeterli, büyük veri setleri için node sayısına göre artırın
  • number_of_replicas: Prodüksiyonda en az 1 replica
  • refresh_interval: Çok sık güncelleme olmayan index’lerde “30s” gibi yüksek değer indexing performansını artırır

Sorgu tarafında:

  • max_expansions: match_phrase_prefix sorgularında bu parametreyi makul bir değerde tutun (50-100), aksi halde çok fazla term expand edilir ve performans düşer
  • size: Autocomplete için genellikle 5-10 sonuç yeterli, gereksiz yere büyük size değerleri kullanmayın
  • _source filtering: Sadece ihtiyacınız olan alanları döndürün
# Index'in anlık durumunu ve istatistiklerini kontrol et
curl -X GET "localhost:9200/urunler/_stats?pretty"

# Sorgu profiling ile yavaş sorguları tespit et
curl -X GET "localhost:9200/urunler/_search" -H 'Content-Type: application/json' -d'
{
  "profile": true,
  "query": {
    "match": {
      "urun_adi": {
        "query": "samsung",
        "fuzziness": "AUTO"
      }
    }
  }
}'

Monitoring ve Log Yönetimi

Arama kalitesini izlemek için slow log ayarlarını yapın:

curl -X PUT "localhost:9200/urunler/_settings" -H 'Content-Type: application/json' -d'
{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.query.info": "500ms",
  "index.search.slowlog.threshold.fetch.warn": "500ms",
  "index.indexing.slowlog.threshold.index.warn": "2s"
}'

Loglar /var/log/elasticsearch/ dizininde tutulur. Sık sık yavaş gelen sorgular varsa bu logları analiz ederek sorunlu pattern’leri tespit edin.

Sonuç

Elasticsearch ile autocomplete ve fuzzy search implementasyonu birkaç kritik karardan oluşuyor: Doğru tokenizer seçimi, uygun fuzziness değeri ve Türkçe karakter normalizasyonu. Edge n-gram yöntemi esneklik sağlarken, search_as_you_type ve completion suggester daha az konfigürasyon gerektiriyor ve belirli senaryolarda daha iyi performans sunuyor.

Hangi yöntemi seçeceğiniz büyük ölçüde veri boyutunuza ve kullanım senaryonuza bağlı. Küçük ve orta ölçekli uygulamalar için edge n-gram yeterli. Çok yüksek trafikli autocomplete senaryolarında completion suggester’ın FST yapısı belirgin bir avantaj sağlıyor.

Prodüksiyona geçmeden önce mutlaka gerçek kullanıcı arama terimlerinizi analiz edin, slow log’ları aktif edin ve sorgu profillerini inceleyin. Elasticsearch güçlü bir araç ama kötü yapılandırılmış bir index ile potansiyelinin çok altında kalıyor. Doğru mapping, doğru analyzer ve doğru sorgu tipiyle kullanıcılarınıza Google kalitesinde arama deneyimi sunmak mümkün.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir