DEV Community

Cover image for Prometheus ve Grafana'yı Derinlemesine Anlamak — TSDB, PromQL ve Custom Exporter
Taha Yağız Güler
Taha Yağız Güler

Posted on

Prometheus ve Grafana'yı Derinlemesine Anlamak — TSDB, PromQL ve Custom Exporter

Neden TSDB?

Normal bir veritabanında bir satır güncellenir — eski değer gider, yeni değer gelir. Monitoring'de bu işe yaramaz. "CPU şu an %45" bilgisi değil, "CPU son 1 saatte nasıl değişti" bilgisi lazım.

TSDB — Time Series Database. Her ölçüm ayrı bir nokta olarak saklanır:

cpu_usage{instance="server-1"}  →  [(10:00, 45), (10:15, 52), (10:30, 61), (10:45, 48)]
Enter fullscreen mode Exit fullscreen mode

Normal veritabanında "son 1 saatteki tüm CPU değerlerini getir" sorgusu tüm tabloyu tarar. TSDB'de bu zaten veri modelinin kendisi — timestamp'e göre sıralı saklıyor, range query neredeyse anlık.

İki ek kazanım:

Sıkıştırma: CPU değerleri birbirine yakın — 45, 47, 44, 46. TSDB delta encoding ile bu benzer değerleri çok verimli sıkıştırıyor. Prometheus ortalama sample başına sadece 1.3 byte kullanıyor.

Retention yönetimi: "Son 15 günü tut, eskisini sil" TSDB'de trivial — zaman bazlı block'lar halinde saklıyor, eski block'ları sil.


Pull-based Mimari

Prometheus hedeflerine giderek metric çekiyor — /metrics endpoint'ini ziyaret ediyor. Push-based sistemlerde uygulama metric'lerini merkezi yere göndermek zorunda.

Neden pull daha iyi?

Uygulama çöktüğünde push-based sistemde son başarılı push'u aldık mı, yoksa çökmeden önce mi gönderdi — belirsiz. Pull-based'de Prometheus scrape etmeye çalışır, başarısız olur — bu durumun kendisi bir sinyal, alert tetikler.


Lab Ortamı

Docker Compose ile Prometheus + Grafana + custom exporter stack'i:

prometheus-lab/
├── docker-compose.yml
├── prometheus/
│   ├── prometheus.yml
│   └── rules.yml
├── grafana/
│   └── provisioning/
│       └── datasources/
│           └── prometheus.yml
└── custom-exporter/
    ├── exporter.py
    └── Dockerfile
Enter fullscreen mode Exit fullscreen mode

prometheus/prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "rules.yml"

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'custom-exporter'
    static_configs:
      - targets: ['custom-exporter:8000']
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus:/etc/prometheus
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
      - '--web.enable-lifecycle'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

  custom-exporter:
    build: ./custom-exporter
    ports:
      - "8000:8000"

volumes:
  prometheus_data:
  grafana_data:
Enter fullscreen mode Exit fullscreen mode

TSDB Dosya Yapısı

Prometheus başladıktan sonra:

docker exec -it prometheus sh
ls /prometheus
Enter fullscreen mode Exit fullscreen mode
chunks_head/
wal/
01XXXXXXXXXXXXXXXXXXXXXX/   ← 2 saatlik block
01YYYYYYYYYYYYYYYYYYYYYY/
Enter fullscreen mode Exit fullscreen mode

WAL (Write Ahead Log): Her scrape önce WAL'a yazılır. Sistem çökerse buradan kurtarılır. Son 2 saatlik veri memory'de, WAL'da yedekleniyor.

Block yapısı: Her block 2 saatlik veri. Zamanla compaction ile birleşiyor — 2 saat → 6 saat → 24 saat → 2 hafta. Eski block'lar retention süresine göre siliniyor.

cat /prometheus/01XXXX.../meta.json
Enter fullscreen mode Exit fullscreen mode
{
  "minTime": 1700000000000,
  "maxTime": 1700007200000,
  "stats": {
    "numSamples": 15420,
    "numSeries": 312
  },
  "compactionLevel": 1
}
Enter fullscreen mode Exit fullscreen mode

minTime ve maxTime bu block'un kapsadığı zaman aralığı. Range query geldiğinde Prometheus sadece ilgili block'lara bakıyor — tüm veriyi taramıyor.


Custom Exporter

Prometheus kendi metric'lerini toplamak için exporter kullanıyor. Node Exporter sistem metriklerini, kube-state-metrics Kubernetes metriklerini topluyor. Kendi uygulamanın metriklerini toplamak için custom exporter yazıyorsun.

Üç metric türü var:

Counter — sadece artar, sıfırlanmaz. Toplam istek sayısı, toplam hata sayısı.

Gauge — artıp azalabilir. Anlık CPU kullanımı, aktif connection sayısı.

Histogram — değerlerin dağılımı. Request latency, response boyutu.

from prometheus_client import start_http_server, Counter, Gauge, Histogram
import time, random

REQUEST_COUNT = Counter(
    'app_requests_total',
    'Toplam istek sayisi',
    ['method', 'endpoint', 'status']
)

ACTIVE_USERS = Gauge(
    'app_active_users',
    'Anlik aktif kullanici sayisi'
)

REQUEST_DURATION = Histogram(
    'app_request_duration_seconds',
    'Istek suresi dagilimi',
    ['endpoint'],
    buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5]
)

def simulate_traffic():
    endpoints = ['/api/users', '/api/orders', '/api/products', '/health']

    while True:
        endpoint = random.choice(endpoints)
        status = random.choice(['200', '200', '200', '404', '500'])
        duration = random.uniform(0.01, 0.3)

        REQUEST_COUNT.labels(
            method='GET',
            endpoint=endpoint,
            status=status
        ).inc()

        REQUEST_DURATION.labels(endpoint=endpoint).observe(duration)
        ACTIVE_USERS.set(random.randint(10, 150))
        time.sleep(0.5)

if __name__ == '__main__':
    start_http_server(8000)
    simulate_traffic()
Enter fullscreen mode Exit fullscreen mode

http://localhost:8000/metrics adresinde şunu görürsün:

# TYPE app_requests_total counter
app_requests_total{endpoint="/api/users",method="GET",status="200"} 142.0

# TYPE app_active_users gauge
app_active_users 87.0

# TYPE app_request_duration_seconds histogram
app_request_duration_seconds_bucket{endpoint="/api/users",le="0.1"} 89.0
app_request_duration_seconds_bucket{endpoint="/api/users",le="0.25"} 134.0
app_request_duration_seconds_sum{endpoint="/api/users"} 18.4
app_request_duration_seconds_count{endpoint="/api/users"} 142.0
Enter fullscreen mode Exit fullscreen mode

PromQL

PromQL'i öğrenirken en önemli şey şunu anlamak: Counter'lar sürekli artar — "toplam 142 request" tek başına işe yaramaz. rate ile "saniyede kaç request" hesaplanır.

Temel sorgular:

# Saniyedeki istek hızı
rate(app_requests_total[5m])

# Endpoint bazında grupla
sum by (endpoint) (rate(app_requests_total[1m]))

# Hata oranı
sum(rate(app_requests_total{status="500"}[2m]))
/
sum(rate(app_requests_total[2m]))
* 100
Enter fullscreen mode Exit fullscreen mode

p95 latency — production'da en çok kullandığım sorgu:

histogram_quantile(0.95,
  sum by (endpoint, le) (
    rate(app_request_duration_seconds_bucket[2m])
  )
)
Enter fullscreen mode Exit fullscreen mode

le label'ı histogram bucket'larının üst sınırı — histogram_quantile için zorunlu. 0.95 → isteklerin %95'i bu sürede tamamlanıyor.

Disk dolma tahmini:

predict_linear(prometheus_tsdb_storage_blocks_bytes[1h], 3600)
Enter fullscreen mode Exit fullscreen mode

Mevcut trende göre 1 saat sonraki değeri tahmin ediyor. Disk dolma alertlerinde kullanılıyor.

Metric kaybolursa:

absent(up{job="custom-exporter"})
Enter fullscreen mode Exit fullscreen mode

Uygulama çöktüğünde metric gelmez — absent bunu yakalar.


Alert Kuralları

prometheus/rules.yml:

groups:
  - name: app.rules
    rules:

      - alert: HighErrorRate
        expr: |
          sum(rate(app_requests_total{status="500"}[2m]))
          /
          sum(rate(app_requests_total[2m]))
          > 0.10
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Hata orani yuksek"
          description: "Hata orani %{{ $value | humanizePercentage }} ulastu"

      - alert: HighLatency
        expr: |
          histogram_quantile(0.95,
            sum by (endpoint, le) (
              rate(app_request_duration_seconds_bucket[2m])
            )
          ) > 0.5
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Yuksek latency: {{ $labels.endpoint }}"

      - alert: ExporterDown
        expr: up{job="custom-exporter"} == 0
        for: 30s
        labels:
          severity: critical
        annotations:
          summary: "Custom exporter down"
Enter fullscreen mode Exit fullscreen mode

for: 1m kritik — koşul anlık doğru olduğunda alert hemen firing olmaz. Önce pending, 1 dakika boyunca koşul doğru kalırsa firing. Anlık spike'larda yanlış alert üretmemek için.

Koşul doğru → pending → (1 dakika geçerse) → firing
Koşul yanlış → resolved
Enter fullscreen mode Exit fullscreen mode

Prometheus UI → Alerts sekmesinde üç durumu canlı izleyebilirsin.


Grafana Dashboard

Prometheus'u datasource olarak ekledikten sonra PromQL sorguları ile panel oluşturuyorsun.

Kurduğum dashboard'daki paneller:

Request Ratesum by (endpoint) (rate(app_requests_total[1m])) — Time series

Error Rate — hata oranı yüzdesi — Stat panel, threshold: 5% sarı, 10% kırmızı

p95 Latencyhistogram_quantile(0.95, ...) — Time series, unit: seconds

Active Usersapp_active_users — Gauge, threshold: 120 kırmızı

Request Dağılımı — status code bazında — Pie chart

Dashboard'u JSON olarak export edip repository'ye ekleyebilirsin — takım arkadaşları import eder, sıfırdan kurmak zorunda kalmaz.


Öğrendiklerim

Bu lab'dan önce Prometheus'u "bir şeyleri izleyen araç" olarak görüyordum. Şimdi şunu söyleyebiliyorum:

TSDB seçimi rastgele değil. Monitoring verisinin yazma pattern'i — düzenli aralıklarla aynı metric'lerin yazılması — TSDB için biçilmiş kaftan. Range query'ler hızlı, sıkıştırma verimli, retention yönetimi kolay.

PromQL'de rate ve histogram_quantile olmadan anlamlı sorgu yazmak neredeyse imkânsız. Counter'ların sürekli artan yapısını anlamadan "neden bu grafik düz bir çizgi?" sorusuna cevap veremezsin.

Custom exporter yazmak en değerli adımdı — metric türlerini anlamak için en iyi yol kendi metric'lerini oluşturmak.


Bu yazı, bir mülakattan aldığım geri bildirimi uygulamalı olarak çalışma serim.
Serinin diğer yazıları:

Top comments (0)