← Kursa Dön
📄 Text · 25 min

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ür

  • aggs (veya aggregations) → Aggregation bloğu

  • aggregation_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_thresholdBellek KullanımıDoğruluk
100 (varsayılan)~1.6KBÇok düşük sayılarda kesin
1000~1.6KBDaha iyi
10000~16KBÇoğu senaryo için yeterli
40000 (maks)~64KBEn 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

UygulamaNeden
size: 0 kullanınDoküman döndürmeden sadece aggregation çalıştırır — hızlı
stats ile toplu istatistik alın5 ayrı sorgu yerine tek sorgu
cardinality için precision_threshold ayarlayınDoğruluk/bellek dengesini optimize eder
Query + aggregation birleştirinFiltrelenmiş veri üzerinde metrik hesaplayın
missing parametresini kullanınNull değerlerin metriği bozmasını engelleyin

❌ Yapmayın

UygulamaNeden
text field'da aggregation yapmayınFielddata gerekir, çok bellek yer; keyword kullanın
Çok fazla aggregation'ı tek sorguda çalıştırmayın20+ aggregation performansı düşürür
cardinality'yi kesin sayı olarak kabul etmeyinYaklaşık değerdir (HLL++)
Script aggregation'ı gereksiz yere kullanmayınHer 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