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)]
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
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']
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:
TSDB Dosya Yapısı
Prometheus başladıktan sonra:
docker exec -it prometheus sh
ls /prometheus
chunks_head/
wal/
01XXXXXXXXXXXXXXXXXXXXXX/ ← 2 saatlik block
01YYYYYYYYYYYYYYYYYYYYYY/
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
{
"minTime": 1700000000000,
"maxTime": 1700007200000,
"stats": {
"numSamples": 15420,
"numSeries": 312
},
"compactionLevel": 1
}
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()
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
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
p95 latency — production'da en çok kullandığım sorgu:
histogram_quantile(0.95,
sum by (endpoint, le) (
rate(app_request_duration_seconds_bucket[2m])
)
)
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)
Mevcut trende göre 1 saat sonraki değeri tahmin ediyor. Disk dolma alertlerinde kullanılıyor.
Metric kaybolursa:
absent(up{job="custom-exporter"})
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"
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
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 Rate — sum 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 Latency — histogram_quantile(0.95, ...) — Time series, unit: seconds
Active Users — app_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)