← Kursa Dön
📄 Text · 30 min

Highlighting ve Suggesters

Giriş — Sarı Fosforlu Kalem

Üniversitede ders çalışırken önemli yerleri sarı fosforlu kalemle işaretlemiş olabilirsiniz. Bir bakışta neyin önemli olduğunu görürsünüz. Google'da arama yaptığınızda da arama teriminiz sonuçlarda kalın olarak gösterilir — bu highlighting'dir. Ayrıca "elasticseach" yazdığınızda Google "elasticsearch mi demek istediniz?" diye sorar — bu da suggestion'dır.

Bu iki özellik arama deneyimini dramatik şekilde iyileştirir. Kullanıcı hem sonuçların neden döndüğünü anlar hem de yazım hatası yapsa bile doğru sonuca ulaşır. Bu derste Elasticsearch'ün highlighting ve suggester özelliklerini derinlemesine inceleyeceğiz.


1. Highlighting — Eşleşen Metni Vurgulama

1.1 Temel Kullanım

PUT articles
{
  "mappings": {
    "properties": {
      "title": { "type": "text", "analyzer": "turkish" },
      "content": { "type": "text", "analyzer": "turkish" },
      "author": { "type": "keyword" },
      "tags": { "type": "keyword" }
    }
  }
}

POST articles/_bulk
{"index":{"_id":"1"}}
{"title":"Elasticsearch ile Full-Text Arama","content":"Elasticsearch modern web uygulamalarında arama işlevselliği için vazgeçilmez bir araçtır. Full-text search özelliği sayesinde milyonlarca doküman arasında milisaniyeler içinde arama yapabilirsiniz. Elasticsearch'ün inverted index yapısı bu hızın temel sebebidir.","author":"Ahmet Yılmaz","tags":["elasticsearch","arama"]}
{"index":{"_id":"2"}}
{"title":"Spring Boot ve Elasticsearch Entegrasyonu","content":"Spring Data Elasticsearch ile Java uygulamalarınızda Elasticsearch kullanabilirsiniz. Repository pattern sayesinde CRUD işlemleri çok kolaydır. Spring Boot auto-configuration desteği ile minimum konfigürasyon gerekir.","author":"Mehmet Kaya","tags":["spring-boot","elasticsearch","java"]}
{"index":{"_id":"3"}}
{"title":"Kibana ile Veri Görselleştirme","content":"Kibana, Elasticsearch verilerini görselleştirmek için kullanılan güçlü bir araçtır. Dashboard oluşturarak verilerinizi gerçek zamanlı izleyebilirsiniz. Elasticsearch cluster sağlığını Kibana üzerinden takip edebilirsiniz.","author":"Ayşe Demir","tags":["kibana","elasticsearch","görselleştirme"]}

GET articles/_search
{
  "query": {
    "match": {
      "content": "elasticsearch arama"
    }
  },
  "highlight": {
    "fields": {
      "content": {}
    }
  }
}

Yanıt:

{
  "hits": {
    "hits": [
      {
        "_source": { "title": "Elasticsearch ile Full-Text Arama", ... },
        "highlight": {
          "content": [
            "<em>Elasticsearch</em> modern web uygulamalarında <em>arama</em> işlevselliği için vazgeçilmez bir araçtır.",
            "Full-text search özelliği sayesinde milyonlarca doküman arasında milisaniyeler içinde <em>arama</em> yapabilirsiniz.",
            "<em>Elasticsearch</em>'ün inverted index yapısı bu hızın temel sebebidir."
          ]
        }
      }
    ]
  }
}

Eşleşen terimler <em> etiketleriyle sarıldı. Frontend'de bu etiketleri CSS ile sarı arka plan yapabilirsiniz.

1.2 Highlight Etiketlerini Özelleştirme

GET articles/_search
{
  "query": {
    "match": { "content": "elasticsearch" }
  },
  "highlight": {
    "pre_tags": ["<mark>"],
    "post_tags": ["</mark>"],
    "fields": {
      "content": {}
    }
  }
}

Artık <mark>Elasticsearch</mark> şeklinde döner.

Farklı field'lar için farklı etiketler:

GET articles/_search
{
  "query": {
    "multi_match": {
      "query": "elasticsearch",
      "fields": ["title", "content"]
    }
  },
  "highlight": {
    "fields": {
      "title": {
        "pre_tags": ["<strong>"],
        "post_tags": ["</strong>"]
      },
      "content": {
        "pre_tags": ["<em>"],
        "post_tags": ["</em>"]
      }
    }
  }
}

1.3 Fragment Boyutu ve Sayısı

Varsayılan olarak 100 karakterlik fragment'lar döner. Bunu ayarlayabilirsiniz:

GET articles/_search
{
  "query": {
    "match": { "content": "elasticsearch" }
  },
  "highlight": {
    "fields": {
      "content": {
        "fragment_size": 200,
        "number_of_fragments": 3,
        "no_match_size": 150
      }
    }
  }
}
ParametreVarsayılanAçıklama
fragment_size100Her fragment'ın karakter uzunluğu
number_of_fragments5Döndürülecek fragment sayısı
no_match_size0Eşleşme yoksa kaç karakter döndürülür
ordernone"score" ile en alakalı fragment önce

1.4 Tüm Alanı Döndürme (Fragment Yok)

Kısa alanlar (başlık gibi) için fragment'lama istemeyebilirsiniz:

GET articles/_search
{
  "query": {
    "match": { "title": "elasticsearch" }
  },
  "highlight": {
    "fields": {
      "title": {
        "number_of_fragments": 0
      }
    }
  }
}

number_of_fragments: 0 tüm alanı tek parça olarak döndürür.


2. Highlighter Tipleri

Elasticsearch üç farklı highlighter tipi sunar:

2.1 unified (Varsayılan — Önerilen)

GET articles/_search
{
  "query": {
    "match": { "content": "elasticsearch arama" }
  },
  "highlight": {
    "type": "unified",
    "fields": {
      "content": {}
    }
  }
}
  • BM25 ile fragment scoring yapar

  • Phrase ve multi-term highlight desteği

  • En çok önerilen tip

2.2 plain

GET articles/_search
{
  "query": {
    "match": { "content": "elasticsearch" }
  },
  "highlight": {
    "type": "plain",
    "fields": {
      "content": {}
    }
  }
}
  • Standard Lucene highlighter

  • Basit senaryolarda iyi çalışır

  • Büyük dokümanlar için yavaş olabilir

2.3 fvh (Fast Vector Highlighter)

PUT fvh_articles
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "term_vector": "with_positions_offsets"
      }
    }
  }
}

GET fvh_articles/_search
{
  "query": {
    "match": { "content": "elasticsearch" }
  },
  "highlight": {
    "type": "fvh",
    "fields": {
      "content": {}
    }
  }
}
  • term_vector: with_positions_offsets gerektirir

  • Büyük dokümanlar için en hızlı

  • Daha fazla disk alanı kullanır (term vector saklanır)

Highlighter Karşılaştırması

Özellikunifiedplainfvh
Hız (küçük dok.)HızlıHızlıHızlı
Hız (büyük dok.)İyiYavaşÇok hızlı
Disk kullanımıNormalNormalYüksek
Phrase highlight
ÖnerilenBasit durumlarBüyük dokümanlar

3. Gelişmiş Highlight Özellikleri

3.1 Birden Fazla Field'da Highlight

GET articles/_search
{
  "query": {
    "multi_match": {
      "query": "elasticsearch java",
      "fields": ["title^2", "content"]
    }
  },
  "highlight": {
    "pre_tags": ["<mark>"],
    "post_tags": ["</mark>"],
    "fields": {
      "title": {
        "number_of_fragments": 0
      },
      "content": {
        "fragment_size": 150,
        "number_of_fragments": 2
      }
    }
  }
}

3.2 require_field_match

Varsayılan olarak highlight sadece sorguyla eşleşen field'larda çalışır. Bunu değiştirebilirsiniz:

GET articles/_search
{
  "query": {
    "match": {
      "title": "elasticsearch"
    }
  },
  "highlight": {
    "require_field_match": false,
    "fields": {
      "title": {},
      "content": {}
    }
  }
}

require_field_match: false ile sorgu title'da yapılsa bile content'te de highlight uygulanır.

3.3 highlight_query — Farklı Sorgu ile Highlight

GET articles/_search
{
  "query": {
    "bool": {
      "must": { "match": { "content": "elasticsearch" } },
      "filter": { "term": { "tags": "java" } }
    }
  },
  "highlight": {
    "fields": {
      "content": {
        "highlight_query": {
          "bool": {
            "must": { "match": { "content": "elasticsearch" } },
            "should": { "match": { "content": "java spring" } }
          }
        }
      }
    }
  }
}

Arama sorgusu ve highlight sorgusu farklı olabilir. Filtrelenen terimleri de highlight etmek için yararlıdır.

3.4 matched_fields — Çoklu Field Birleştirme

PUT matched_articles
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "turkish",
        "fields": {
          "plain": {
            "type": "text",
            "analyzer": "standard"
          }
        }
      }
    }
  }
}

GET matched_articles/_search
{
  "query": {
    "multi_match": {
      "query": "elasticsearch geliştiriciler",
      "fields": ["content", "content.plain"],
      "type": "most_fields"
    }
  },
  "highlight": {
    "type": "fvh",
    "fields": {
      "content": {
        "matched_fields": ["content", "content.plain"]
      }
    }
  }
}

4. Suggesters — Öneri Sistemi

Elasticsearch dört tip suggester sunar:

4.1 Term Suggester — Yazım Düzeltme

En basit suggester. Yanlış yazılmış kelimelere doğru alternatifleri önerir:

GET articles/_search
{
  "suggest": {
    "spell_check": {
      "text": "elasticsearh arma",
      "term": {
        "field": "content",
        "suggest_mode": "always",
        "min_word_length": 3,
        "max_edits": 2
      }
    }
  }
}

Yanıt:

{
  "suggest": {
    "spell_check": [
      {
        "text": "elasticsearh",
        "options": [
          { "text": "elasticsearch", "score": 0.9166666, "freq": 5 }
        ]
      },
      {
        "text": "arma",
        "options": [
          { "text": "arama", "score": 0.75, "freq": 3 }
        ]
      }
    ]
  }
}

`suggest_mode` değerleri:

ModDavranış
missingSadece index'te olmayan terimler için öneri
popularDaha sık geçen alternatif varsa öner
alwaysHer zaman öneri sun

4.2 Phrase Suggester — Cümle Düzeltme

Term suggester'ın gelişmiş hali — kelime çiftlerini ve cümle yapısını da dikkate alır:

GET articles/_search
{
  "suggest": {
    "phrase_suggestion": {
      "text": "elastcsearch arma motoru",
      "phrase": {
        "field": "content",
        "size": 3,
        "gram_size": 2,
        "direct_generator": [
          {
            "field": "content",
            "suggest_mode": "always",
            "min_word_length": 3
          }
        ],
        "highlight": {
          "pre_tag": "<em>",
          "post_tag": "</em>"
        }
      }
    }
  }
}

Yanıt:

{
  "suggest": {
    "phrase_suggestion": [
      {
        "text": "elastcsearch arma motoru",
        "options": [
          {
            "text": "elasticsearch arama motoru",
            "highlighted": "<em>elasticsearch</em> <em>arama</em> motoru",
            "score": 0.085
          }
        ]
      }
    ]
  }
}

Phrase suggester, kelimeler arası bağlamı anlayarak daha doğru öneriler sunar.

4.3 Completion Suggester — Autocomplete

Completion suggester, autocomplete için özel olarak tasarlanmıştır. FST (Finite State Transducer) veri yapısını kullanır ve son derece hızlıdır:

PUT autocomplete_articles
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "title_suggest": {
        "type": "completion",
        "analyzer": "standard",
        "search_analyzer": "standard"
      }
    }
  }
}

POST autocomplete_articles/_bulk
{"index":{"_id":"1"}}
{"title":"Elasticsearch Temelleri","title_suggest":{"input":["Elasticsearch Temelleri","ES Temelleri","Elastic Temelleri"],"weight":10}}
{"index":{"_id":"2"}}
{"title":"Elasticsearch ile Full-Text Arama","title_suggest":{"input":["Elasticsearch ile Full-Text Arama","ES Full-Text","Elastic Arama"],"weight":8}}
{"index":{"_id":"3"}}
{"title":"Spring Boot Elasticsearch Entegrasyonu","title_suggest":{"input":["Spring Boot Elasticsearch","Spring Data ES","Spring Elastic"],"weight":6}}
{"index":{"_id":"4"}}
{"title":"Kibana Dashboard Oluşturma","title_suggest":{"input":["Kibana Dashboard","Kibana Görselleştirme"],"weight":5}}

Autocomplete sorgusu:

GET autocomplete_articles/_search
{
  "suggest": {
    "article_suggest": {
      "prefix": "ela",
      "completion": {
        "field": "title_suggest",
        "size": 5,
        "skip_duplicates": true
      }
    }
  }
}

Yanıt: "Elasticsearch Temelleri", "Elasticsearch ile Full-Text Arama", vb. — weight'e göre sıralı.

Fuzzy Completion — Yazım Toleransı

GET autocomplete_articles/_search
{
  "suggest": {
    "article_suggest": {
      "prefix": "elastiksearch",
      "completion": {
        "field": "title_suggest",
        "size": 5,
        "fuzzy": {
          "fuzziness": "AUTO"
        }
      }
    }
  }
}

"elastiksearch" yazım hatası olsa bile "Elasticsearch" önerilir.

Context Suggester — Bağlamsal Öneri

Kategori veya lokasyona göre filtrelenmiş öneriler:

PUT context_articles
{
  "mappings": {
    "properties": {
      "title_suggest": {
        "type": "completion",
        "contexts": [
          {
            "name": "category",
            "type": "category"
          }
        ]
      }
    }
  }
}

POST context_articles/_doc/1
{
  "title_suggest": {
    "input": "Elasticsearch Kurulumu",
    "contexts": {
      "category": ["backend", "devops"]
    }
  }
}

POST context_articles/_doc/2
{
  "title_suggest": {
    "input": "React Kurulumu",
    "contexts": {
      "category": ["frontend"]
    }
  }
}

// Sadece backend önerileri
GET context_articles/_search
{
  "suggest": {
    "filtered_suggest": {
      "prefix": "kur",
      "completion": {
        "field": "title_suggest",
        "size": 5,
        "contexts": {
          "category": ["backend"]
        }
      }
    }
  }
}

4.4 Suggester Karşılaştırması

ÖzellikTermPhraseCompletion
AmaçYazım düzeltmeCümle düzeltmeAutocomplete
HızNormalNormalÇok hızlı
Veri yapısıInverted indexInverted indexFST (in-memory)
Context desteği
Fuzzy desteği
Özel field gerekli✅ (completion type)

5. Highlight + Suggestion Birlikte Kullanımı

Gerçek bir arama deneyimi için ikisini birleştirin:

GET articles/_search
{
  "query": {
    "match": {
      "content": "elasticsearch arama"
    }
  },
  "highlight": {
    "pre_tags": ["<mark>"],
    "post_tags": ["</mark>"],
    "fields": {
      "title": { "number_of_fragments": 0 },
      "content": { "fragment_size": 150, "number_of_fragments": 2 }
    }
  },
  "suggest": {
    "spell_check": {
      "text": "elasticsearch arama",
      "term": {
        "field": "content",
        "suggest_mode": "popular"
      }
    }
  }
}

6. "Did You Mean?" Implementasyonu

Google tarzı "Bunu mu demek istediniz?" özelliği:

PUT search_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "trigram": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "shingle_filter"]
        }
      },
      "filter": {
        "shingle_filter": {
          "type": "shingle",
          "min_shingle_size": 2,
          "max_shingle_size": 3
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "turkish",
        "fields": {
          "trigram": {
            "type": "text",
            "analyzer": "trigram"
          }
        }
      }
    }
  }
}

// "Did you mean?" sorgusu
GET search_index/_search
{
  "suggest": {
    "did_you_mean": {
      "text": "elastiksörc arma motru",
      "phrase": {
        "field": "title.trigram",
        "size": 1,
        "gram_size": 3,
        "direct_generator": [
          {
            "field": "title.trigram",
            "suggest_mode": "always"
          }
        ],
        "collate": {
          "query": {
            "source": {
              "match": {
                "{{field_name}}": "{{suggestion}}"
              }
            }
          },
          "params": {
            "field_name": "title"
          },
          "prune": true
        }
      }
    }
  }
}

collate özelliği, önerinin gerçekten sonuç döndürüp döndürmeyeceğini kontrol eder — boş sonuç döndürecek önerileri eler.


7. Java ile Highlighting ve Suggesters

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.*;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.util.List;
import java.util.Map;

public class HighlightSuggesterJava {

    // Highlighting
    public static void searchWithHighlight(ElasticsearchClient client) throws Exception {
        SearchResponse<ObjectNode> response = client.search(s -> s
            .index("articles")
            .query(q -> q
                .match(m -> m
                    .field("content")
                    .query("elasticsearch arama")
                )
            )
            .highlight(h -> h
                .preTags("<mark>")
                .postTags("</mark>")
                .fields("title", f -> f.numberOfFragments(0))
                .fields("content", f -> f
                    .fragmentSize(150)
                    .numberOfFragments(2)
                )
            ),
            ObjectNode.class
        );

        for (Hit<ObjectNode> hit : response.hits().hits()) {
            System.out.println("Title: " + hit.source().get("title").asText());

            Map<String, List<String>> highlights = hit.highlight();
            if (highlights.containsKey("content")) {
                for (String fragment : highlights.get("content")) {
                    System.out.println("  >> " + fragment);
                }
            }
        }
    }

    // Completion Suggester
    public static void autocomplete(ElasticsearchClient client, String prefix) throws Exception {
        SearchResponse<ObjectNode> response = client.search(s -> s
            .index("autocomplete_articles")
            .suggest(su -> su
                .suggesters("article_suggest", sg -> sg
                    .prefix(prefix)
                    .completion(c -> c
                        .field("title_suggest")
                        .size(5)
                        .skipDuplicates(true)
                        .fuzzy(f -> f.fuzziness("AUTO"))
                    )
                )
            ),
            ObjectNode.class
        );

        var suggestions = response.suggest().get("article_suggest");
        if (suggestions != null) {
            for (var suggestion : suggestions) {
                for (var option : suggestion.completion().options()) {
                    System.out.printf("Öneri: %s (score: %.2f)%n",
                        option.text(), option.score());
                }
            }
        }
    }

    // Term Suggester — Yazım Düzeltme
    public static void spellCheck(ElasticsearchClient client, String text) throws Exception {
        SearchResponse<ObjectNode> response = client.search(s -> s
            .index("articles")
            .suggest(su -> su
                .text(text)
                .suggesters("spell_check", sg -> sg
                    .term(t -> t
                        .field("content")
                        .suggestMode(SuggestMode.Always)
                    )
                )
            ),
            ObjectNode.class
        );

        var suggestions = response.suggest().get("spell_check");
        if (suggestions != null) {
            for (var suggestion : suggestions) {
                if (!suggestion.term().options().isEmpty()) {
                    System.out.printf("'%s' → '%s'%n",
                        suggestion.term().text(),
                        suggestion.term().options().get(0).text());
                }
            }
        }
    }
}

8. Best Practices

✅ Yapın

UygulamaNeden
Highlight'ta fragment_size'ı UI'a göre ayarlayınMobil = 100, masaüstü = 200
Completion suggester için weight kullanınPopüler önerileri üste çıkarır
skip_duplicates: true kullanınTekrar eden önerileri engeller
Fuzzy autocomplete ekleyinYazım hatalarını tolere eder
unified highlighter kullanınEn iyi genel performans

❌ Yapmayın

UygulamaNeden
Çok büyük fragment_size kullanmayınAğ trafiği artar
plain highlighter'ı büyük dokümanlarla kullanmayınYavaş
Completion field'ı çok fazla input ile doldurmayınBellek kullanımı artar
Suggestion sonuçlarını doğrulamadan göstermeyinAnlamsız öneriler UI'a sızabilir

9. Yaygın Hatalar

Hata 1: keyword Field'da Highlight

// ❌ keyword field highlight edilemez (analyze edilmediği için)
"highlight": {
  "fields": {
    "category": {}  // keyword type
  }
}

// ✅ text field veya text sub-field kullanın
"highlight": {
  "fields": {
    "content": {}  // text type
  }
}

Hata 2: Completion Field'ı Sorguda Kullanmak

// ❌ completion field normal query'de kullanılamaz
GET my_index/_search
{
  "query": {
    "match": {
      "title_suggest": "elasticsearch"  // completion type!
    }
  }
}

// ✅ suggest API kullanın
GET my_index/_search
{
  "suggest": {
    "my_suggest": {
      "prefix": "elasticsearch",
      "completion": { "field": "title_suggest" }
    }
  }
}

Hata 3: Highlight ile _source: false

// ❌ _source kapalıyken highlight çalışmaz (unified/plain)
GET my_index/_search
{
  "_source": false,
  "highlight": {
    "fields": { "content": {} }
  }
}

// ✅ _source açık bırakın veya source filtering kullanın
GET my_index/_search
{
  "_source": ["title"],
  "highlight": {
    "fields": { "content": {} }
  }
}

10. Performans Notları

İşlemMaliyetOptimizasyon
Highlight (küçük dok.)Düşük
Highlight (büyük dok.)Yüksekfvh kullanın
Term suggestDüşük
Phrase suggestOrtagram_size ayarlayın
Completion suggestÇok düşükFST in-memory

Completion suggester'ın hızı olağanüstüdür çünkü FST yapısı tamamen bellekte tutulur. Milyonlarca öneri bile milisaniyeler içinde döner.


Özet

  • Highlighting arama terimlerinin sonuçlarda nerede eşleştiğini gösterir — <em> etiketleri özelleştirilebilir

  • Üç highlighter tipi vardır: unified (önerilen), plain (basit), fvh (büyük dokümanlar)

  • fragment_size ve number_of_fragments ile highlight çıktısı kontrol edilir

  • Term Suggester kelime bazlı yazım düzeltme yapar — "elasticsearh" → "elasticsearch"

  • Phrase Suggester cümle bağlamını dikkate alarak düzeltme önerir

  • Completion Suggester autocomplete için tasarlanmıştır — FST yapısıyla son derece hızlı, completion field type gerektirir

  • Context Suggester kategori veya lokasyona göre filtrelenmiş öneriler sunar

  • Highlight ve suggest birlikte kullanılarak zengin bir arama deneyimi oluşturulabilir