Sertaç Yıldırımsaha notları

Ana sayfa → Teknik

Pod Scale Etme Mantığı

Kampanya başladı, trafik üç katına çıktı. Otomatik ölçekleyici devreye girdi, pod sayısı 4’ten 24’e çıktı. Ve yanıt süresi daha da kötüleşti. Sonra veritabanı yeni bağlantı kabul etmeyi bıraktı.

Özet
  • Ölçekleme darboğazı taşır, kaldırmaz. Önce darboğazın nerede olduğunu bul.
  • CPU çoğu web uygulaması için yanlış ölçü. Bekleyen uygulama CPU harcamaz ama yavaştır.
  • Pod sayısı arttıkça veritabanı bağlantısı çarpılarak artar. Aşağıyı korumadan yukarıyı büyütme.
  • Yanlış request = yanlış ölçekleme. Yüzde hesabı limite göre değil request’e göre yapılıyor.

Otomatik ölçekleyici aslında ne yapıyor?

Karmaşık görünüyor ama tek bir orantı hesabı:

Formül
istenen_pod = mevcut_pod × (mevcut_deger / hedef_deger)

# ornek: 4 pod, ortalama CPU %90, hedef %60
# istenen = 4 × (90 / 60) = 6 pod

Bu kadar. Bütün mesele “mevcut_deger” olarak neyi seçtiğin. Ve çoğu ekip burada CPU seçip konuyu kapatıyor.

CPU neden çoğu zaman yanlış ölçü?

Tipik bir web isteğinin ömrüne bak: 3 ms JSON ayrıştırma, 80 ms veritabanı bekleme, 2 ms serileştirme. Uygulama zamanının %95’ini beklemekle geçiriyor ve beklemek CPU harcamıyor.

Yani sistem dizlerinin üstünde sürünürken CPU %25 görünebiliyor. Ölçekleyici de “her şey yolunda” deyip hiçbir şey yapmıyor.

Uygulama tipiDoğru ölçüNeden
Görsel işleme, şifreleme, rapor üretimiCPUGerçekten işlemci sınırlı
Tipik REST APIEşzamanlı istek / saniyedeki istekZamanın çoğu beklemede geçiyor
Kuyruk işçisiKuyruk uzunluğuBiriken iş, gecikmenin öncü göstergesi
Uzun bağlantılar (WebSocket, SSE)Açık bağlantı sayısıBellek ve soket sınırı belirleyici
Kuyruk uzunluğuna göre ölçekleme
# KEDA ile: her 100 bekleyen mesaj icin 1 worker
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
spec:
  scaleTargetRef:
    name: bildirim-worker
  minReplicaCount: 2
  maxReplicaCount: 40
  triggers:
    - type: rabbitmq
      metadata:
        queueName: bildirimler
        queueLength: "100"

Bu, işçiler için CPU’dan kat kat iyi: kuyruk büyümeye başladığı anda tepki veriyor, işçiler bekleme yüzünden yavaşlasa bile doğru çalışıyor.

request ve limit: en çok yanlış kurulan iki alan

Bu ikisi karıştırılınca ölçekleme baştan yanlış hesaplanıyor.

request

Garanti edilen pay. Zamanlayıcı pod’u yerleştirirken buna bakar. Ve kritik olan şu: ölçekleyicinin yüzde hesabı buna göre yapılır.

limit

Aşılamaz tavan. CPU’da aşarsan yavaşlatılırsın, bellekte aşarsan öldürülürsün. İkisi çok farklı hisler.

Klasik hata
resources:
  requests:
    cpu: 100m        # ← gercekte 400m kullaniyor
  limits:
    cpu: 1000m

# HPA hedefi %70 olsun. Hesap request'e gore:
# 400m / 100m = %400 kullanim
# istenen = 4 × (400 / 70) ≈ 23 pod
#
# Uygulama aslinda rahat calisiyor. Olcekleyici panige kapildi.

Tersi de oluyor: request’i çok yüksek verirsen (örneğin 2000m) kullanım hep düşük görünür, ölçekleyici hiç açılmaz ve sistem yük altında ezilir.

Pratik kural: request, normal yükteki gerçek kullanımın biraz üstü olmalı. Tahminle değil, ölçerek. Bir hafta boyunca gerçek kullanımın %90’lık dilimine bak ve onu yaz.

CPU limiti meselesi

CPU limitine takılan bir süreç öldürülmüyor, kısıtlanıyor (throttling). Bu, teşhisi en zor gecikme kaynaklarından biri: hata yok, log temiz, CPU grafiği bile limitin altında görünüyor — ama istekler takılıyor.

Sebep şu: kısıtlama çok kısa aralıklarla uygulanıyor. Ortalamada limitin altında kalsan bile, kısa ani yükselmelerde milisaniyeler boyunca durduruluyorsun. Bunu görmenin tek yolu doğrudan kısıtlama metriğine bakmak:

Kontrol et
container_cpu_cfs_throttled_seconds_total

# Bu deger surekli artiyorsa CPU limitin dusuk demektir.
# Cok gorulen cozum: gecikmeye duyarli servislerde
# CPU limitini hic koymamak (request koymak yeterli).

Asıl tuzak: aşağıdaki darboğaz

Baştaki hikâyeye dönelim. Pod 4’ten 24’e çıktı. Her pod veritabanına 20 bağlantılık bir havuz açıyordu.

Çarpan etkisi
4 pod  × 20 baglanti =  80  ✓ (limit 200)
24 pod × 20 baglanti = 480  ✗ (limit 200)

Sonuc: veritabani yeni baglantiyi reddediyor.
Uygulama "connection timeout" firlatiyor.
Saglik kontrolu basarisiz oluyor, pod yeniden baslatiliyor.
Yeniden baslayan pod tekrar 20 baglanti istiyor...

Bu bir kısır döngü ve ölçekleme onu hızlandırıyor. Sistem, hiç ölçeklenmemiş olsaydı yavaş ama ayakta kalacaktı.

Yukarıyı büyütmeden önce aşağıyı koru. Ölçeklenebilir olmayan bir bileşenin önüne ölçeklenebilir bir katman koymak, o bileşeni daha hızlı öldürür.

Üç savunma:

  1. Havuz boyutunu pod sayısına göre hesapla. pod_başına_havuz × max_pod ≤ veritabanı_limiti × 0,8. 24 pod hedefliyorsan pod başına havuz 6 olmalı, 20 değil.
  2. Bağlantı havuzlayıcı kullan. PgBouncer gibi bir katman, yüzlerce uygulama bağlantısını onlarca gerçek bağlantıya indirir. Bu, ölçeklemeyi mümkün kılan tek yapısal çözüm.
  3. maxReplicas’i keyfi koyma. O sayı, aşağıdaki en zayıf halkanın kaldırabileceğinden türetilmeli. Ölçekleyicinin üst sınırı bir güvenlik kemeri; “100 yazalım rahat olsun” demek kemeri çıkarmak.

Yavaş açılan uygulama problemi

Bir Java servisinde şunu yaşadık: yeni pod ayağa kalkıyor, hazır olduğunu bildiriyor, trafik alıyor — ama ilk 30 saniye çok yavaş cevap veriyor (JIT ısınması, önbellek doldurma, bağlantı kurma).

Sonuç: yeni pod ortalama gecikmeyi yükseltiyor, ölçekleyici bunu “yetersiz kapasite” sanıp bir pod daha açıyor, o da yavaş başlıyor... Buna salınım (flapping) deniyor ve grafikte testere dişi gibi görünüyor.

Üç düzeltme
# 1) Isinana kadar trafik alma
readinessProbe:
  httpGet: { path: /hazir, port: 8080 }
  initialDelaySeconds: 20
  periodSeconds: 5

# 2) Yavas acilisi olum sanma (yeniden baslatma dongusunu keser)
startupProbe:
  httpGet: { path: /saglik, port: 8080 }
  failureThreshold: 30
  periodSeconds: 5

# 3) Olcekleyiciye sogumasi icin zaman ver
behavior:
  scaleUp:
    stabilizationWindowSeconds: 60
  scaleDown:
    stabilizationWindowSeconds: 300

Üçüncüsü özellikle önemli: küçülme yükselmeden yavaş olmalı. Trafik dalgalanıyorsa hızlı küçülen bir sistem, iki dakika sonra aynı pod’ları yeniden açar ve soğuk başlangıç maliyetini tekrar tekrar öder.

Küçülürken iş kaybetmek

Ölçekleme yazılarında en az konuşulan kısım bu. Pod kapatılırken üzerinde yarım kalmış işler olabilir.

Doğru kapanış
  • Kapanış sinyali gelince yeni iş almayı bırak, elindekini bitir.
  • terminationGracePeriodSeconds, en uzun işinden büyük olsun. Varsayılan 30 saniye; 5 dakika süren bir işin varsa yetmez.
  • Uzun süren işler için: pod kapanırken işi kuyruğa geri koy ki başka bir worker devralsın.
  • Web servisinde: hazırlık kontrolünü önce başarısız yap, birkaç saniye bekle, sonra kapan — yük dengeleyicinin listeden çıkarması zaman alıyor.

Ne zaman dikey büyümek daha doğru?

Her şeyin cevabı yatay değil. Pod eklemek şu durumlarda işe yaramaz:

  • Tek bir isteğin kendisi yavaşsa. 8 saniyelik bir sorgu, 40 pod ile de 8 saniye sürer.
  • Uygulama bellekte büyük bir önbellek tutuyorsa. Her pod aynı veriyi ayrı ayrı tutar; bellek israfı katlanır.
  • Lisans veya bağlantı başına maliyet varsa. Kopya sayısı doğrudan faturaya yazılır.

Bu durumlarda önce sorguyu, önbelleği veya algoritmayı düzeltmek gerekiyor. Ölçekleme bir performans çözümü değil, bir kapasite çözümü.

Kontrol listesi

Ölçeklemeyi açmadan önce
  • Darboğazın nerede olduğunu ölçtüm mü? (Uygulama mı, veritabanı mı, dış servis mi?)
  • Ölçüm metriği doğru mu? (CPU mu olmalı, kuyruk mu, eşzamanlı istek mi?)
  • request değerleri gerçek kullanıma göre mi ayarlandı?
  • CPU kısıtlama (throttling) metriğine baktım mı?
  • maxReplicas × pod_başına_bağlantı veritabanı limitinin altında mı?
  • Hazırlık kontrolü uygulama gerçekten hazır olduğunda mı geçiyor?
  • Küçülme penceresi büyümeden uzun mu?
  • Pod kapanırken yarım kalan iş ne oluyor?
  • Ölçekleme testi yaptım mı, yoksa canlıda mı öğreneceğim?

Sonuç

Otomatik ölçekleme, doğru kurulduğunda gece uykusu demek; yanlış kurulduğunda çöküşü hızlandıran bir mekanizma. Aradaki farkı belirleyen şey pod sayısı değil, hangi sayıya baktığın ve aşağıdakini koruyup korumadığın.

Baştaki olayda çözüm pod azaltmak oldu — 24’ten 8’e indirdik, araya bağlantı havuzlayıcı koyduk, ölçüyü CPU’dan eşzamanlı isteğe çevirdik. Sistem hem daha az kaynakla hem de daha hızlı çalıştı.