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"
}
}
]
}| Parametre | Değerler | Açıklama |
|---|---|---|
order | asc, desc | Sıralama yönü |
missing | _last, _first, özel değer | Eksik değer olan dokümanlar nereye konur |
unmapped_type | Field tipi | Index'te olmayan field tipi (multi-index aramalarda) |
mode | min, max, avg, sum, median | Multi-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 alanlarspecs.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 | _source | fields |
|---|---|---|
| Veri kaynağı | Orijinal JSON | doc_values / stored fields |
| Format | Orijinal format | Elasticsearch formatı |
| Performans | Hızlı (tek okuma) | Biraz yavaş (field bazlı) |
| Multi-value | Orijinal yapı | Her zaman dizi |
| Runtime fields | Hayır | Evet |
| Kullanım | Genel 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
| Uygulama | Neden |
|---|---|
| Liste görünümünde source filtering kullanın | Ağ trafiğini %70-90 azaltır |
Sıralama alanlarını keyword veya numeric yapın | text field'da sıralama çalışmaz |
| Çoklu sıralamada tiebreaker ekleyin | Deterministik sonuç |
| Score gerekmiyorsa sort kullanın | _score hesaplanmaz, daha hızlı |
Aggregation sorgularında _source: false yapın | Doküman gövdesi gereksiz |
❌ Yapmayın
| Uygulama | Neden |
|---|---|
| text field'da sort yapmayın | Hata verir, fielddata açmayın |
| Script sorting'i her sorguda kullanmayın | Performans maliyeti yüksek |
| Tüm alanları her sorguda döndürmeyin | Gereksiz ağ trafiği |
_source'u index seviyesinde kapatmayın | Update 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.sortparametresi verilince_scorehesaplanmaz (performans kazancı)Çoklu sıralama ile birden fazla kriter belirlenebilir — ilk kriter eşit olduğunda sonraki devreye girer
textfield doğrudan sıralanamaz — keyword sub-field veya doc_values kullanınScript 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
missingparametresi ile eksik değerlerin sıralamada nereye konacağı belirlenirAggregation sorgularında
_source: falseyaparak gereksiz veri transferini engelleyin
AI Asistan
Sorularını yanıtlamaya hazır