← Kursa Dön
📄 Text · 25 min

Sorting ve Source Filtering

Giriş — Kütüphane Rafları

Bir kütüphaneyi düşünün. Kitaplar farklı şekillerde sıralanabilir: yazarın soyadına göre, yayın tarihine göre, popülerliğe göre. Ama kitabı raftan aldığınızda her zaman tüm sayfalarını okumazsınız — belki sadece kapağına bakarsınız, belki sadece içindekiler kısmını okursunuz. İhtiyacınız kadar bilgi alırsınız.

Elasticsearch'te de arama sonuçları farklı kriterlere göre sıralanabilir ve dönen veride sadece ihtiyacınız olan alanlar seçilebilir. Gereksiz veri taşımak hem ağ trafiğini artırır hem de uygulamanızı yavaşlatır. Bu derste sorting ve source filtering konularını derinlemesine işleyeceğiz.


1. Varsayılan Sıralama — Relevance Score

Hiçbir sort parametresi belirtmezseniz, Elasticsearch sonuçları `_score` değerine göre sıralar — en alakalı sonuç en üstte:

GET products/_search
{
  "query": {
    "match": {
      "description": "akıllı telefon"
    }
  }
}

Yanıt:

{
  "hits": {
    "hits": [
      { "_score": 3.45, "_source": { "name": "Samsung Galaxy S24..." } },
      { "_score": 2.87, "_source": { "name": "Apple iPhone 15..." } },
      { "_score": 1.12, "_source": { "name": "Xiaomi Redmi Note..." } }
    ]
  }
}

_score değeri BM25 algoritması ile hesaplanır. Arama teriminin dokümanda ne kadar sık ve ne kadar özel geçtiğine bağlıdır.

💡 İpucu: sort parametresi belirtildiğinde, Elasticsearch _score hesaplamasını atlar (performans kazancı). Score gerekmiyorsa sorting kullanmak daha hızlıdır.


2. Temel Sorting

2.1 Tek Alan Sıralama

// Fiyata göre artan sıralama
GET products/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "price": "asc" }
  ]
}

// Tarihe göre azalan (en yeni önce)
GET products/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "created_at": "desc" }
  ]
}

2.2 Çoklu Sıralama

Birden fazla alan ile sıralama — ilk kriter eşit olduğunda ikinci kriter devreye girer:

GET products/_search
{
  "query": { "match_all": {} },
  "sort": [
    { "category": "asc" },
    { "price": "desc" },
    { "_id": "asc" }
  ]
}

Sonuç: Önce kategori A-Z, aynı kategoride fiyat büyükten küçüğe, aynı fiyatta ID'ye göre sıralanır.

2.3 sort Yanıtı

Sort kullanıldığında, her hit'te sort dizisi döner:

{
  "hits": {
    "hits": [
      {
        "_id": "1",
        "_score": null,
        "_source": { "name": "Samsung Galaxy", "category": "Telefon", "price": 54999 },
        "sort": ["Telefon", 54999, "1"]
      }
    ]
  }
}

_score: null — sort kullanıldığında score hesaplanmaz (performans avantajı). search_after pagination'ı bu sort dizisini kullanır.


3. Sort Parametreleri ve Modları

3.1 Detaylı Sort Yapılandırması

GET products/_search
{
  "sort": [
    {
      "price": {
        "order": "desc",
        "missing": "_last",
        "unmapped_type": "float",
        "mode": "avg"
      }
    }
  ]
}
ParametreDeğerlerAçıklama
orderasc, descSıralama yönü
missing_last, _first, özel değerEksik değer olan dokümanlar nereye konur
unmapped_typeField tipiIndex'te olmayan field tipi (multi-index aramalarda)
modemin, max, avg, sum, medianMulti-valued field'larda hangi değer kullanılır

3.2 missing Parametresi

Bazı dokümanların sıralama alanı boş olabilir:

// Fiyatı olmayan ürünler en sona
GET products/_search
{
  "sort": [
    {
      "price": {
        "order": "asc",
        "missing": "_last"
      }
    }
  ]
}

// Fiyatı olmayan ürünler en başa
GET products/_search
{
  "sort": [
    {
      "price": {
        "order": "asc",
        "missing": "_first"
      }
    }
  ]
}

// Fiyatı olmayan ürünleri 0 olarak kabul et
GET products/_search
{
  "sort": [
    {
      "price": {
        "order": "asc",
        "missing": 0
      }
    }
  ]
}

3.3 mode — Dizi Alanlarında Sıralama

Bir doküman birden fazla değer içerebilir (array field):

// Doküman: { "ratings": [4.5, 3.2, 4.8, 2.1] }

// En yüksek rating'e göre sırala
GET reviews/_search
{
  "sort": [
    {
      "ratings": {
        "order": "desc",
        "mode": "max"
      }
    }
  ]
}

// Ortalama rating'e göre sırala
GET reviews/_search
{
  "sort": [
    {
      "ratings": {
        "order": "desc",
        "mode": "avg"
      }
    }
  ]
}

4. Farklı Tiplerde Sıralama

4.1 text Field'da Sıralama

text field'lar doğrudan sıralanamaz — token'lara bölündükleri için hangi token'a göre sıralanacağı belirsizdir:

// ❌ Hata verir
GET products/_search
{
  "sort": [{ "description": "asc" }]
}
// "Fielddata is disabled on [description] by default"

Çözüm: keyword sub-field kullanın:

PUT sortable_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

// ✅ keyword sub-field ile sıralama
GET sortable_index/_search
{
  "sort": [{ "title.keyword": "asc" }]
}

⚠️ Fielddata'yı açmayın: "fielddata": true ayarı text field'da sıralamaya izin verir ama çok yüksek bellek tüketir. Keyword sub-field her zaman daha iyi bir çözümdür.

4.2 Date Sıralama

GET logs/_search
{
  "sort": [
    {
      "timestamp": {
        "order": "desc",
        "format": "strict_date_optional_time"
      }
    }
  ]
}

4.3 Geo Distance Sıralama

Kullanıcının konumuna en yakın mağazalar:

GET stores/_search
{
  "sort": [
    {
      "_geo_distance": {
        "location": {
          "lat": 41.0082,
          "lon": 28.9784
        },
        "order": "asc",
        "unit": "km",
        "distance_type": "arc"
      }
    }
  ]
}

4.4 Script-Based Sorting

Hesaplanmış değere göre sıralama:

// İndirimli fiyata göre sırala (fiyat * (1 - indirim_oranı))
GET products/_search
{
  "sort": [
    {
      "_script": {
        "type": "number",
        "script": {
          "source": "doc['price'].value * (1 - doc['discount_rate'].value)",
          "lang": "painless"
        },
        "order": "asc"
      }
    }
  ]
}

Popülerlik + Güncellik hibrit sıralama:

GET articles/_search
{
  "sort": [
    {
      "_script": {
        "type": "number",
        "script": {
          "source": """
            double ageInDays = (System.currentTimeMillis() - doc['publish_date'].value.toInstant().toEpochMilli()) / 86400000.0;
            double freshness = Math.max(0, 1.0 - (ageInDays / 365.0));
            return doc['view_count'].value * freshness;
          """,
          "lang": "painless"
        },
        "order": "desc"
      }
    }
  ]
}

⚠️ Script sorting her doküman için script'i çalıştırır — büyük veri kümelerinde yavaş olabilir.


5. Score + Sort Birlikte Kullanımı

Bazen hem relevance score hem de başka bir alana göre sıralamak istersiniz:

// Önce score, sonra fiyat
GET products/_search
{
  "query": {
    "match": {
      "description": "akıllı telefon"
    }
  },
  "sort": [
    "_score",
    { "price": "asc" }
  ]
}

Veya score'u sort kriterlerinden biri olarak kullanıp track_scores ile zorlamak:

GET products/_search
{
  "query": {
    "match": {
      "description": "akıllı telefon"
    }
  },
  "sort": [
    { "price": "desc" }
  ],
  "track_scores": true
}

track_scores: true ile her dokümanın _score değeri hesaplanır — sort başka bir alana göre olsa bile.


6. Source Filtering — Gereksiz Veriyi Eleme

6.1 _source Nedir?

_source, dokümanın orijinal JSON'ıdır. Her sorgu sonucunda varsayılan olarak tüm _source döner:

{
  "_id": "1",
  "_source": {
    "name": "Samsung Galaxy S24 Ultra",
    "description": "Samsung'un en güçlü akıllı telefon modeli...(10KB metin)",
    "specs": { "ram": "12GB", "storage": "512GB", "camera": "200MP" },
    "price": 54999.99,
    "category": "Elektronik",
    "reviews": [ ... ],  // Yüzlerce review
    "images": [ ... ]     // Birçok URL
  }
}

10.000 doküman döndürüyorsanız ve her doküman 10KB ise, sadece _source 100MB veri transferi demektir. Çoğu zaman tüm alanlara ihtiyacınız yoktur.

6.2 _source'u Tamamen Kapatma

GET products/_search
{
  "_source": false,
  "query": { "match_all": {} }
}

Sadece _id ve _score döner — _source gelmez. Aggregation veya sadece sayım yapıyorsanız idealdir.

6.3 Include — Sadece Belirli Alanlar

GET products/_search
{
  "_source": ["name", "price", "category"],
  "query": { "match_all": {} }
}

Yanıt:

{
  "_source": {
    "name": "Samsung Galaxy S24 Ultra",
    "price": 54999.99,
    "category": "Elektronik"
  }
}

6.4 Include + Exclude

GET products/_search
{
  "_source": {
    "includes": ["name", "price", "specs.*"],
    "excludes": ["specs.internal_*"]
  },
  "query": { "match_all": {} }
}

Wildcard pattern'lar desteklenir:

  • specs.* — specs altındaki tüm alanlar

  • specs.internal_* — specs.internal_ ile başlayanlar hariç

6.5 Nested Object'lerde Source Filtering

GET products/_search
{
  "_source": {
    "includes": ["name", "price", "specs.ram", "specs.storage"],
    "excludes": ["reviews", "images"]
  },
  "query": { "match_all": {} }
}

7. fields Parametresi — Stored ve Runtime Fields

_source dışında fields parametresi ile de belirli alanlar çekilebilir:

GET products/_search
{
  "_source": false,
  "fields": ["name", "price", "category"],
  "query": { "match_all": {} }
}

Yanıt:

{
  "fields": {
    "name": ["Samsung Galaxy S24 Ultra"],
    "price": [54999.99],
    "category": ["Elektronik"]
  }
}

_source vs fields Farkı

Özellik_sourcefields
Veri kaynağıOrijinal JSONdoc_values / stored fields
FormatOrijinal formatElasticsearch formatı
PerformansHızlı (tek okuma)Biraz yavaş (field bazlı)
Multi-valueOrijinal yapıHer zaman dizi
Runtime fieldsHayırEvet
KullanımGenel amaçlıRuntime field, formatlanmış veri

Runtime Fields ile Hesaplanmış Alan

GET products/_search
{
  "_source": false,
  "runtime_mappings": {
    "discounted_price": {
      "type": "double",
      "script": {
        "source": "emit(doc['price'].value * 0.9)"
      }
    }
  },
  "fields": ["name", "price", "discounted_price"],
  "query": { "match_all": {} }
}

8. docvalue_fields — Doc Values'dan Okuma

Aggregation ve sorting için kullanılan doc_values'tan doğrudan okuma:

GET products/_search
{
  "_source": false,
  "docvalue_fields": [
    "price",
    {
      "field": "created_at",
      "format": "yyyy-MM-dd"
    }
  ],
  "query": { "match_all": {} }
}

Date field'lar için format belirleyebilirsiniz — _source'ta epoch millisecond olarak saklansa bile okunabilir formatta alırsınız.


9. Gerçek Dünya Senaryosu: Ürün Listeleme API'si

PUT product_catalog
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "turkish",
        "fields": { "keyword": { "type": "keyword" } }
      },
      "description": { "type": "text", "analyzer": "turkish" },
      "category": { "type": "keyword" },
      "brand": { "type": "keyword" },
      "price": { "type": "float" },
      "discount_rate": { "type": "float" },
      "rating": { "type": "float" },
      "review_count": { "type": "integer" },
      "created_at": { "type": "date" },
      "in_stock": { "type": "boolean" },
      "specs": { "type": "object" },
      "images": { "type": "keyword" }
    }
  }
}

POST product_catalog/_bulk
{"index":{"_id":"1"}}
{"name":"Samsung Galaxy S24 Ultra","description":"Samsung'un en güçlü akıllı telefon modeli. 200MP kamera, 12GB RAM.","category":"Telefon","brand":"Samsung","price":54999.99,"discount_rate":0.1,"rating":4.8,"review_count":1250,"created_at":"2024-01-15","in_stock":true,"specs":{"ram":"12GB","storage":"512GB","screen":"6.8 inch"},"images":["img1.jpg","img2.jpg","img3.jpg"]}
{"index":{"_id":"2"}}
{"name":"Apple iPhone 15 Pro Max","description":"Apple'ın en gelişmiş akıllı telefon modeli. A17 Pro çip.","category":"Telefon","brand":"Apple","price":64999.99,"discount_rate":0.05,"rating":4.9,"review_count":2340,"created_at":"2024-01-20","in_stock":true,"specs":{"ram":"8GB","storage":"1TB","screen":"6.7 inch"},"images":["img4.jpg","img5.jpg"]}
{"index":{"_id":"3"}}
{"name":"Xiaomi 14 Pro","description":"Xiaomi'nin amiral gemisi telefonu. Leica kamera sistemi.","category":"Telefon","brand":"Xiaomi","price":29999.99,"discount_rate":0.15,"rating":4.5,"review_count":890,"created_at":"2024-02-01","in_stock":true,"specs":{"ram":"12GB","storage":"256GB","screen":"6.73 inch"},"images":["img6.jpg"]}
{"index":{"_id":"4"}}
{"name":"Lenovo ThinkPad X1 Carbon","description":"İş dünyası için en iyi laptop. Ultra hafif, güçlü performans.","category":"Bilgisayar","brand":"Lenovo","price":42999.99,"discount_rate":0.0,"rating":4.7,"review_count":560,"created_at":"2024-02-15","in_stock":false,"specs":{"ram":"16GB","storage":"512GB SSD","screen":"14 inch"},"images":["img7.jpg","img8.jpg"]}

// Senaryo 1: Liste görünümü — sadece temel bilgiler
GET product_catalog/_search
{
  "_source": ["name", "brand", "price", "rating", "in_stock"],
  "query": {
    "bool": {
      "must": [
        { "match": { "category": "Telefon" } }
      ],
      "filter": [
        { "term": { "in_stock": true } }
      ]
    }
  },
  "sort": [
    { "price": "asc" }
  ]
}

// Senaryo 2: Detay sayfası — tüm bilgiler
GET product_catalog/_search
{
  "query": {
    "term": { "_id": "1" }
  }
}

// Senaryo 3: Popülerliğe göre sırala (review_count * rating)
GET product_catalog/_search
{
  "_source": ["name", "brand", "price", "rating", "review_count"],
  "query": {
    "match": { "category": "Telefon" }
  },
  "sort": [
    {
      "_script": {
        "type": "number",
        "script": {
          "source": "doc['review_count'].value * doc['rating'].value",
          "lang": "painless"
        },
        "order": "desc"
      }
    }
  ]
}

// Senaryo 4: Çoklu sıralama — stokta olanlar önce, sonra fiyat
GET product_catalog/_search
{
  "_source": ["name", "price", "in_stock"],
  "sort": [
    { "in_stock": "desc" },
    { "price": "asc" }
  ]
}

// Senaryo 5: İndirimli fiyata göre sırala (runtime field)
GET product_catalog/_search
{
  "_source": ["name", "price", "discount_rate"],
  "runtime_mappings": {
    "final_price": {
      "type": "double",
      "script": {
        "source": "emit(doc['price'].value * (1 - doc['discount_rate'].value))"
      }
    }
  },
  "fields": ["final_price"],
  "sort": [
    { "final_price": "asc" }
  ]
}

10. Java ile Sorting ve Source Filtering

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

public class SortingSourceFilteringJava {

    // Çoklu sıralama + source filtering
    public static void sortAndFilter(ElasticsearchClient client) throws Exception {
        SearchResponse<ObjectNode> response = client.search(s -> s
            .index("product_catalog")
            // Source filtering — sadece name, price, rating
            .source(src -> src
                .filter(f -> f
                    .includes("name", "price", "rating")
                )
            )
            .query(q -> q
                .bool(b -> b
                    .must(m -> m.match(mt -> mt.field("category").query("Telefon")))
                    .filter(fl -> fl.term(t -> t.field("in_stock").value(true)))
                )
            )
            // Çoklu sıralama
            .sort(so -> so.field(f -> f.field("price").order(SortOrder.Asc)))
            .sort(so -> so.field(f -> f.field("_id").order(SortOrder.Asc)))
            .size(10),
            ObjectNode.class
        );

        for (Hit<ObjectNode> hit : response.hits().hits()) {
            ObjectNode source = hit.source();
            System.out.printf("%-30s | ₺%.2f | ⭐%.1f%n",
                source.get("name").asText(),
                source.get("price").asDouble(),
                source.get("rating").asDouble()
            );
        }
    }

    // Script sorting
    public static void scriptSorting(ElasticsearchClient client) throws Exception {
        SearchResponse<ObjectNode> response = client.search(s -> s
            .index("product_catalog")
            .source(src -> src
                .filter(f -> f.includes("name", "price", "discount_rate"))
            )
            .sort(so -> so
                .script(sc -> sc
                    .type(ScriptSortType.Number)
                    .script(scr -> scr
                        .inline(i -> i
                            .source("doc['price'].value * (1 - doc['discount_rate'].value)")
                            .lang("painless")
                        )
                    )
                    .order(SortOrder.Asc)
                )
            ),
            ObjectNode.class
        );

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

    // _source false — sadece ID'ler
    public static void idsOnly(ElasticsearchClient client) throws Exception {
        SearchResponse<Void> response = client.search(s -> s
            .index("product_catalog")
            .source(src -> src.fetch(false))
            .query(q -> q.matchAll(m -> m))
            .size(100),
            Void.class
        );

        for (Hit<Void> hit : response.hits().hits()) {
            System.out.println("ID: " + hit.id());
        }
    }
}

11. Best Practices

✅ Yapın

UygulamaNeden
Liste görünümünde source filtering kullanınAğ trafiğini %70-90 azaltır
Sıralama alanlarını keyword veya numeric yapıntext field'da sıralama çalışmaz
Çoklu sıralamada tiebreaker ekleyinDeterministik sonuç
Score gerekmiyorsa sort kullanın_score hesaplanmaz, daha hızlı
Aggregation sorgularında _source: false yapınDoküman gövdesi gereksiz

❌ Yapmayın

UygulamaNeden
text field'da sort yapmayınHata verir, fielddata açmayın
Script sorting'i her sorguda kullanmayınPerformans maliyeti yüksek
Tüm alanları her sorguda döndürmeyinGereksiz ağ trafiği
_source'u index seviyesinde kapatmayınUpdate ve reindex çalışmaz

12. Yaygın Hatalar

Hata 1: text Field Sorting

// ❌ text field'da sort — hata
"sort": [{ "description": "asc" }]

// ✅ keyword sub-field kullanın
"sort": [{ "description.keyword": "asc" }]

Hata 2: _source Kapatma ve Update

// ❌ Index seviyesinde _source kapatma
PUT my_index
{
  "mappings": {
    "_source": { "enabled": false }
  }
}
// Sonuç: update, reindex, highlight ÇALIŞMAZ

_source kapatmak çok spesifik senaryolar içindir (sadece aggregation, asla update yok). Genelde yapmayın.

Hata 3: Source Filtering ile Güvenlik

// ❌ Source filtering güvenlik mekanizması DEĞİLDİR
// Kullanıcı doğrudan _source isteyebilir
GET my_index/_doc/1  // Tüm _source döner

// ✅ Gerçek alan seviyesi güvenlik için:
// - Elasticsearch Security (field-level security)
// - Application layer filtering

Özet

  • Varsayılan sıralama _score'dur — relevance score'a göre. sort parametresi verilince _score hesaplanmaz (performans kazancı)

  • Çoklu sıralama ile birden fazla kriter belirlenebilir — ilk kriter eşit olduğunda sonraki devreye girer

  • text field doğrudan sıralanamaz — keyword sub-field veya doc_values kullanın

  • Script sorting hesaplanmış değere göre sıralama sağlar ama performans maliyeti vardır

  • `_source` filtering ile sadece ihtiyaç duyulan alanlar döndürülür — ağ trafiğini dramatik azaltır

  • `fields` parametresi runtime field'lar ve formatlanmış değerler için kullanılır

  • missing parametresi ile eksik değerlerin sıralamada nereye konacağı belirlenir

  • Aggregation sorgularında _source: false yaparak gereksiz veri transferini engelleyin