Metric Aggregations — avg, sum, min, max, cardinality
Giriş — Karne Notu Hesaplama
Okul karnenizi hatırlayın. Öğretmen tüm sınav notlarınızı toplar, ortalamanızı hesaplar, en yüksek ve en düşük notunuzu belirler. Belki sınıf genelinde kaçıncı yüzdelik dilimde olduğunuzu bile söyler. Tüm bunlar birer metrik hesaplamadır — bir veri kümesinden tek bir sayısal değer çıkarmak.
Elasticsearch'ün metric aggregation'ları tam olarak bunu yapar: milyonlarca doküman üzerinde ortalama, toplam, minimum, maksimum, benzersiz sayı, yüzdelik dilim gibi hesaplamalar yapar — ve bunu milisaniyeler içinde gerçekleştirir. SQL'deki AVG(), SUM(), COUNT(DISTINCT) gibi fonksiyonların Elasticsearch karşılığıdır.
1. Aggregation Temelleri
1.1 Aggregation Yapısı
GET my_index/_search
{
"size": 0,
"aggs": {
"aggregation_adı": {
"aggregation_tipi": {
"field": "alan_adı"
}
}
}
}size: 0→ Doküman sonuçlarını getirme, sadece aggregation sonuçlarını döndüraggs(veyaaggregations) → Aggregation bloğuaggregation_adı→ Sonuçta bu isimle döner (istediğiniz ismi verebilirsiniz)
1.2 Test Verisi
Bu ders boyunca kullanacağımız veri seti:
PUT sales
{
"mappings": {
"properties": {
"product": { "type": "keyword" },
"category": { "type": "keyword" },
"brand": { "type": "keyword" },
"price": { "type": "float" },
"quantity": { "type": "integer" },
"revenue": { "type": "float" },
"rating": { "type": "float" },
"customer_id": { "type": "keyword" },
"city": { "type": "keyword" },
"sale_date": { "type": "date" }
}
}
}
POST sales/_bulk
{"index":{}}
{"product":"Samsung Galaxy S24","category":"Telefon","brand":"Samsung","price":54999.99,"quantity":2,"revenue":109999.98,"rating":4.8,"customer_id":"C001","city":"İstanbul","sale_date":"2024-01-15"}
{"index":{}}
{"product":"Apple iPhone 15","category":"Telefon","brand":"Apple","price":64999.99,"quantity":1,"revenue":64999.99,"rating":4.9,"customer_id":"C002","city":"Ankara","sale_date":"2024-01-20"}
{"index":{}}
{"product":"Xiaomi 14 Pro","category":"Telefon","brand":"Xiaomi","price":29999.99,"quantity":3,"revenue":89999.97,"rating":4.5,"customer_id":"C003","city":"İzmir","sale_date":"2024-02-01"}
{"index":{}}
{"product":"MacBook Air M3","category":"Bilgisayar","brand":"Apple","price":42999.99,"quantity":1,"revenue":42999.99,"rating":4.7,"customer_id":"C001","city":"İstanbul","sale_date":"2024-02-10"}
{"index":{}}
{"product":"Lenovo ThinkPad","category":"Bilgisayar","brand":"Lenovo","price":38999.99,"quantity":2,"revenue":77999.98,"rating":4.4,"customer_id":"C004","city":"İstanbul","sale_date":"2024-02-15"}
{"index":{}}
{"product":"Samsung Galaxy Tab","category":"Tablet","brand":"Samsung","price":34999.99,"quantity":1,"revenue":34999.99,"rating":4.6,"customer_id":"C005","city":"Ankara","sale_date":"2024-02-20"}
{"index":{}}
{"product":"iPad Air","category":"Tablet","brand":"Apple","price":27999.99,"quantity":2,"revenue":55999.98,"rating":4.8,"customer_id":"C002","city":"Ankara","sale_date":"2024-03-01"}
{"index":{}}
{"product":"Sony WH-1000XM5","category":"Kulaklık","brand":"Sony","price":8999.99,"quantity":5,"revenue":44999.95,"rating":4.9,"customer_id":"C006","city":"İzmir","sale_date":"2024-03-05"}
{"index":{}}
{"product":"AirPods Pro","category":"Kulaklık","brand":"Apple","price":7499.99,"quantity":3,"revenue":22499.97,"rating":4.7,"customer_id":"C003","city":"İzmir","sale_date":"2024-03-10"}
{"index":{}}
{"product":"Samsung Galaxy Watch","category":"Akıllı Saat","brand":"Samsung","price":12999.99,"quantity":2,"revenue":25999.98,"rating":4.5,"customer_id":"C007","city":"Bursa","sale_date":"2024-03-15"}2. avg — Ortalama
GET sales/_search
{
"size": 0,
"aggs": {
"ortalama_fiyat": {
"avg": {
"field": "price"
}
}
}
}Yanıt:
{
"aggregations": {
"ortalama_fiyat": {
"value": 32499.989
}
}
}Filtreli Ortalama
Query ile birlikte — sadece eşleşen dokümanlar üzerinde aggregation:
GET sales/_search
{
"size": 0,
"query": {
"term": { "category": "Telefon" }
},
"aggs": {
"telefon_ortalama_fiyat": {
"avg": {
"field": "price"
}
}
}
}Sadece "Telefon" kategorisindeki ürünlerin ortalama fiyatı.
missing Parametresi
Fiyatı olmayan dokümanlar için varsayılan değer:
GET sales/_search
{
"size": 0,
"aggs": {
"ortalama_fiyat": {
"avg": {
"field": "price",
"missing": 0
}
}
}
}3. sum — Toplam
GET sales/_search
{
"size": 0,
"aggs": {
"toplam_ciro": {
"sum": {
"field": "revenue"
}
},
"toplam_adet": {
"sum": {
"field": "quantity"
}
}
}
}Birden fazla aggregation aynı anda çalıştırılabilir.
4. min ve max — Minimum ve Maksimum
GET sales/_search
{
"size": 0,
"aggs": {
"en_ucuz": {
"min": {
"field": "price"
}
},
"en_pahali": {
"max": {
"field": "price"
}
},
"en_dusuk_rating": {
"min": {
"field": "rating"
}
},
"en_yuksek_rating": {
"max": {
"field": "rating"
}
}
}
}Date Field'da min/max
GET sales/_search
{
"size": 0,
"aggs": {
"ilk_satis": {
"min": {
"field": "sale_date"
}
},
"son_satis": {
"max": {
"field": "sale_date"
}
}
}
}Tarih alanlarında min/max milisaniye olarak döner. format parametresi ile okunabilir hale getirebilirsiniz:
"ilk_satis": {
"value": 1705276800000,
"value_as_string": "2024-01-15T00:00:00.000Z"
}5. stats — Tek Seferde Hepsi
stats aggregation min, max, avg, sum ve count değerlerini tek seferde döndürür:
GET sales/_search
{
"size": 0,
"aggs": {
"fiyat_istatistikleri": {
"stats": {
"field": "price"
}
}
}
}Yanıt:
{
"aggregations": {
"fiyat_istatistikleri": {
"count": 10,
"min": 7499.99,
"max": 64999.99,
"avg": 32499.989,
"sum": 324999.89
}
}
}extended_stats — Gelişmiş İstatistikler
GET sales/_search
{
"size": 0,
"aggs": {
"fiyat_detay": {
"extended_stats": {
"field": "price"
}
}
}
}Ek alanlar:
{
"count": 10,
"min": 7499.99,
"max": 64999.99,
"avg": 32499.989,
"sum": 324999.89,
"sum_of_squares": 1.3462..e+10,
"variance": 3.814...e+8,
"variance_population": 3.814...e+8,
"variance_sampling": 4.238...e+8,
"std_deviation": 19530.12,
"std_deviation_population": 19530.12,
"std_deviation_sampling": 20588.34,
"std_deviation_bounds": {
"upper": 71560.23,
"lower": -6560.25,
"upper_population": 71560.23,
"lower_population": -6560.25,
"upper_sampling": 73676.67,
"lower_sampling": -8676.69
}
}std_deviation_bounds outlier tespiti için kullanışlıdır — bu aralığın dışında kalan değerler anormaldir.
6. value_count — Değer Sayısı
Bir alandaki değer sayısını sayar (null olmayanlar):
GET sales/_search
{
"size": 0,
"aggs": {
"satis_sayisi": {
"value_count": {
"field": "product"
}
}
}
}⚠️ Not: value_count toplam doküman sayısını değil, o alanda değer olan doküman sayısını sayar. Dizi alanlarda her element ayrı sayılır.
7. cardinality — Benzersiz Değer Sayısı (Approximate)
SQL'deki COUNT(DISTINCT) karşılığı:
GET sales/_search
{
"size": 0,
"aggs": {
"benzersiz_musteri": {
"cardinality": {
"field": "customer_id"
}
},
"benzersiz_sehir": {
"cardinality": {
"field": "city"
}
},
"benzersiz_marka": {
"cardinality": {
"field": "brand"
}
}
}
}Yanıt:
{
"benzersiz_musteri": { "value": 7 },
"benzersiz_sehir": { "value": 4 },
"benzersiz_marka": { "value": 5 }
}HyperLogLog++ Algoritması
Cardinality yaklaşık bir değer döndürür — HyperLogLog++ algoritması kullanır. Çok büyük veri setlerinde bile düşük bellek kullanımıyla çalışır.
Doğruluk hassasiyetini precision_threshold ile kontrol edebilirsiniz:
GET sales/_search
{
"size": 0,
"aggs": {
"benzersiz_musteri": {
"cardinality": {
"field": "customer_id",
"precision_threshold": 40000
}
}
}
}| precision_threshold | Bellek Kullanımı | Doğruluk |
|---|---|---|
| 100 (varsayılan) | ~1.6KB | Çok düşük sayılarda kesin |
| 1000 | ~1.6KB | Daha iyi |
| 10000 | ~16KB | Çoğu senaryo için yeterli |
| 40000 (maks) | ~64KB | En yüksek doğruluk |
8. percentiles — Yüzdelik Dilimler
Değerlerin yüzdelik dağılımını gösterir:
GET sales/_search
{
"size": 0,
"aggs": {
"fiyat_dagilimi": {
"percentiles": {
"field": "price"
}
}
}
}Yanıt:
{
"fiyat_dagilimi": {
"values": {
"1.0": 7499.99,
"5.0": 7499.99,
"25.0": 12999.99,
"50.0": 31499.99,
"75.0": 42999.99,
"95.0": 64999.99,
"99.0": 64999.99
}
}
}Yorumlama:
Fiyatların %50'si 31.500 TL'nin altında (medyan)
Fiyatların %95'i 65.000 TL'nin altında
%25'lik dilim 13.000 TL — ürünlerin çeyreği bu fiyatın altında
Özel Yüzdelik Dilimler
GET sales/_search
{
"size": 0,
"aggs": {
"rating_dagilimi": {
"percentiles": {
"field": "rating",
"percents": [10, 25, 50, 75, 90, 99]
}
}
}
}percentile_ranks — Bir Değerin Yüzdelik Sırası
"29.999 TL'lik bir ürün fiyat sıralamasında yüzde kaçta?" sorusunu yanıtlar:
GET sales/_search
{
"size": 0,
"aggs": {
"fiyat_siralama": {
"percentile_ranks": {
"field": "price",
"values": [10000, 30000, 50000]
}
}
}
}Yanıt:
{
"fiyat_siralama": {
"values": {
"10000.0": 25.0,
"30000.0": 40.0,
"50000.0": 80.0
}
}
}Yorum: 30.000 TL, fiyatların %40'ından yüksek. 50.000 TL, fiyatların %80'inden yüksek.
9. weighted_avg — Ağırlıklı Ortalama
Basit ortalama her değeri eşit ağırlıkta sayar. Ağırlıklı ortalamada her değerin bir ağırlığı vardır:
// Satış miktarına göre ağırlıklı ortalama fiyat
GET sales/_search
{
"size": 0,
"aggs": {
"agirlikli_ort_fiyat": {
"weighted_avg": {
"value": {
"field": "price"
},
"weight": {
"field": "quantity"
}
}
}
}
}Çok satılan ürünler ortalamayı daha çok etkiler. 5 adet satılan 8.999 TL'lik kulaklık, 1 adet satılan 64.999 TL'lik telefondan daha etkili olur.
10. median_absolute_deviation — Medyan Mutlak Sapma
Verinin ne kadar yayıldığını ölçer. Standart sapmaya göre outlier'lara daha dayanıklıdır:
GET sales/_search
{
"size": 0,
"aggs": {
"medyan_fiyat": {
"percentiles": {
"field": "price",
"percents": [50]
}
},
"fiyat_sapmasi": {
"median_absolute_deviation": {
"field": "price"
}
}
}
}Eğer medyan 31.500 ve MAD 18.000 ise, fiyatların çoğu 31.500 ± 18.000 aralığındadır.
11. Script Kullanımı
Aggregation'larda hesaplanmış değerler kullanabilirsiniz:
// Birim başına ciro (revenue / quantity)
GET sales/_search
{
"size": 0,
"aggs": {
"birim_ciro_ort": {
"avg": {
"script": {
"source": "doc['revenue'].value / doc['quantity'].value",
"lang": "painless"
}
}
}
}
}
// İndirimli fiyat ortalaması
GET sales/_search
{
"size": 0,
"aggs": {
"indirimli_ort": {
"avg": {
"script": {
"source": "doc['price'].value * params.discount",
"params": {
"discount": 0.9
}
}
}
}
}
}12. Birden Fazla Aggregation Birleştirme
GET sales/_search
{
"size": 0,
"query": {
"range": {
"sale_date": {
"gte": "2024-01-01",
"lte": "2024-03-31"
}
}
},
"aggs": {
"toplam_ciro": { "sum": { "field": "revenue" } },
"ortalama_fiyat": { "avg": { "field": "price" } },
"toplam_satis_adedi": { "sum": { "field": "quantity" } },
"benzersiz_musteri": { "cardinality": { "field": "customer_id" } },
"fiyat_istatistikleri": { "stats": { "field": "price" } },
"rating_dagilimi": {
"percentiles": {
"field": "rating",
"percents": [25, 50, 75]
}
},
"en_yuksek_satis": { "max": { "field": "revenue" } }
}
}Tek bir sorguda 7 farklı metrik — dashboard verileri için ideal.
13. Java ile Metric Aggregations
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch.core.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class MetricAggregationJava {
public static void basicMetrics(ElasticsearchClient client) throws Exception {
SearchResponse<Void> response = client.search(s -> s
.index("sales")
.size(0)
.aggregations("toplam_ciro", a -> a.sum(su -> su.field("revenue")))
.aggregations("ortalama_fiyat", a -> a.avg(av -> av.field("price")))
.aggregations("en_pahali", a -> a.max(mx -> mx.field("price")))
.aggregations("en_ucuz", a -> a.min(mn -> mn.field("price")))
.aggregations("benzersiz_musteri", a -> a
.cardinality(c -> c.field("customer_id").precisionThreshold(1000))
)
.aggregations("fiyat_stats", a -> a.stats(st -> st.field("price"))),
Void.class
);
var aggs = response.aggregations();
System.out.printf("Toplam Ciro: ₺%.2f%n", aggs.get("toplam_ciro").sum().value());
System.out.printf("Ortalama Fiyat: ₺%.2f%n", aggs.get("ortalama_fiyat").avg().value());
System.out.printf("En Pahalı: ₺%.2f%n", aggs.get("en_pahali").max().value());
System.out.printf("En Ucuz: ₺%.2f%n", aggs.get("en_ucuz").min().value());
System.out.printf("Benzersiz Müşteri: %d%n",
aggs.get("benzersiz_musteri").cardinality().value());
var stats = aggs.get("fiyat_stats").stats();
System.out.printf("Stats — count:%d min:%.2f max:%.2f avg:%.2f sum:%.2f%n",
stats.count(), stats.min(), stats.max(), stats.avg(), stats.sum());
}
public static void percentileMetrics(ElasticsearchClient client) throws Exception {
SearchResponse<Void> response = client.search(s -> s
.index("sales")
.size(0)
.aggregations("fiyat_percentiles", a -> a
.percentiles(p -> p
.field("price")
.percents(25.0, 50.0, 75.0, 90.0, 99.0)
)
)
.aggregations("agirlikli_ort", a -> a
.weightedAvg(w -> w
.value(v -> v.field("price"))
.weight(wt -> wt.field("quantity"))
)
),
Void.class
);
var percentiles = response.aggregations()
.get("fiyat_percentiles").tdigestPercentiles();
System.out.println("Fiyat Yüzdelik Dilimleri:");
percentiles.values().keyed().forEach((key, value) -> {
System.out.printf(" P%s: ₺%.2f%n", key, value);
});
double weightedAvg = response.aggregations()
.get("agirlikli_ort").weightedAvg().value();
System.out.printf("Ağırlıklı Ortalama Fiyat: ₺%.2f%n", weightedAvg);
}
// Filtreli aggregation
public static void filteredMetrics(ElasticsearchClient client) throws Exception {
SearchResponse<Void> response = client.search(s -> s
.index("sales")
.size(0)
.query(q -> q.term(t -> t.field("category").value("Telefon")))
.aggregations("telefon_stats", a -> a.stats(st -> st.field("price")))
.aggregations("telefon_ciro", a -> a.sum(su -> su.field("revenue"))),
Void.class
);
var stats = response.aggregations().get("telefon_stats").stats();
System.out.printf("Telefon Kategori — Sayı: %d | Ort: ₺%.2f | Toplam Ciro: ₺%.2f%n",
stats.count(), stats.avg(),
response.aggregations().get("telefon_ciro").sum().value());
}
}14. Best Practices
✅ Yapın
| Uygulama | Neden |
|---|---|
size: 0 kullanın | Doküman döndürmeden sadece aggregation çalıştırır — hızlı |
stats ile toplu istatistik alın | 5 ayrı sorgu yerine tek sorgu |
cardinality için precision_threshold ayarlayın | Doğruluk/bellek dengesini optimize eder |
| Query + aggregation birleştirin | Filtrelenmiş veri üzerinde metrik hesaplayın |
missing parametresini kullanın | Null değerlerin metriği bozmasını engelleyin |
❌ Yapmayın
| Uygulama | Neden |
|---|---|
| text field'da aggregation yapmayın | Fielddata gerekir, çok bellek yer; keyword kullanın |
| Çok fazla aggregation'ı tek sorguda çalıştırmayın | 20+ aggregation performansı düşürür |
cardinality'yi kesin sayı olarak kabul etmeyin | Yaklaşık değerdir (HLL++) |
| Script aggregation'ı gereksiz yere kullanmayın | Her doküman için script çalışır |
15. Yaygın Hatalar
Hata 1: text Field'da Aggregation
// ❌ text field — hata verir
GET sales/_search
{
"size": 0,
"aggs": {
"products": {
"terms": { "field": "product_name" } // text type!
}
}
}
// ✅ keyword field veya sub-field kullanın
"terms": { "field": "product_name.keyword" }Hata 2: Aggregation Sonuçlarını Sayfa Verisiyle Karıştırmak
// ❌ Aggregation size: 0 yok — gereksiz dokümanlar da döner
GET sales/_search
{
"aggs": { "toplam": { "sum": { "field": "revenue" } } }
}
// ✅ size: 0 ekleyin
GET sales/_search
{
"size": 0,
"aggs": { "toplam": { "sum": { "field": "revenue" } } }
}Hata 3: Cardinality'yi Kesin Sayı Kabul Etmek
10 milyon benzersiz kullanıcı varken cardinality 9.998.500 dönebilir. Tam kesin sayı istiyorsanız (ki genelde gerekmez), field'ı pre-aggregate etmeniz gerekir.
Özet
`avg`, `sum`, `min`, `max` temel aritmetik metriklerdir — SQL karşılıkları gibi çalışır
`stats` ve `extended_stats` tek seferde birden fazla metrik döndürür — dashboard'lar için ideal
`cardinality` benzersiz değer sayısını yaklaşık hesaplar (HyperLogLog++) —
COUNT(DISTINCT)karşılığı`percentiles` verinin yüzdelik dağılımını gösterir — P50 medyan, P99 performans izleme için kritik
`percentile_ranks` belirli bir değerin yüzdelik sıralamasını verir
`weighted_avg` ağırlıklı ortalama hesaplar — satış miktarına göre fiyat ortalaması gibi senaryolarda kullanılır
Aggregation'ları query ile birleştirerek filtrelenmiş veri üzerinde hesaplama yapabilirsiniz
`size: 0` kullanarak sadece aggregation sonuçlarını alın — gereksiz doküman transferini engelleyin
AI Asistan
Sorularını yanıtlamaya hazır