Hata Yönetimi Nedir? Dayı-Yeğen ile Hata Mühendisliği
Yazılım sistemlerinde hata yönetimi (failure handling) süreçlerini, dayı-yeğen sohbeti eşliğinde, gerçek dünya senaryoları ve pratik kod örnekleriyle keşfedin.
Sıcak bir pazar ikindisiydi. Can, bilgisayarının başında saçını başını yolarken dayısı odaya girdi. Elindeki çay bardağını masaya bırakıp yeğeninin omuzuna dokundu. “Yine neyi çökerttin Can?” diye sordu gülümseyerek. Can, bıkkın bir şekilde iç çekti: “Dayı, yazdığım yeni mikroservis (küçük bağımsız servis) yerel ödeme sistemine bağlanırken hata veriyor ve tüm uygulama donuyor! Kullanıcılar ödeme yapamadığı gibi ana sayfaya bile erişemiyor. Nerede hata yapıyorum?” Dayısı sandalyesini çekti, çayından bir yudum aldı ve “Bak yeğenim,” dedi, “sen mutlu yolu (happy path) çok iyi çizmişsin ama hayat her zaman mutlu yollardan ibaret değildir. Bugün seninle Hata Mühendisliği (Failure Engineering) serimizin dördüncü bölümünü, yani Hata Yönetimi (Failure Handling) konusunu konuşacağız.”
Yazılımda Hata Yönetimi Nedir ve Neden Hayatidir?
Can, dayısının her zaman yaptığı gibi konuyu basitleştirmesini bekliyordu. Dayısı tam da beklediği gibi söze girdi: “Düşün ki Kadıköy’de popüler bir fırın işletiyorsun. Fırının en meşhur ürünü de sıcak simit. Bir sabah un sevkiyatı gecikti diye fırının kapısına kilit vurup içerideki tüm müşterileri dışarı mı atarsın? Yoksa müşterilere ‘Simit biraz gecikecek ama isterseniz taze poğaçamızdan verelim’ mi dersin?”
Can hemen atıldı: “Tabii ki poğaça teklif ederim dayı, yoksa müşteriler bir daha gelmez.”
“İşte yazılımda hata yönetimi tam olarak budur yeğenim,” dedi dayısı. “Yazılım dünyasında sistemlerin hata yapması kaçınılmazdır. Ağ bağlantısı kopabilir, veri tabanı aşırı yüklenebilir veya entegre olduğun üçüncü parti bir API (uygulama programlama arayüzü) yanıt vermeyebilir. Önemli olan, bu hatalar gerçekleştiğinde sistemin tamamen çökmesini engellemek ve kullanıcıya hissettirmeden alternatif çözümler sunmaktır. Biz buna yazılımda dayanıklılık (resiliency) diyoruz. Eğer hataları doğru yönetemezsen, tek bir servisin gecikmesi veya hata vermesi, domino taşı etkisiyle tüm sistemini yerle bir eder. Bu durum hem müşteri kaybına hem de şirket için ciddi prestij ve maddi zarara yol açar.”
Dayısı, hata yönetiminin temel amacının sistemin tamamen durmasını engellemek ve kademeli bozulma (graceful degradation) prensibini uygulamak olduğunu belirtti. Yani sistem en kötü senaryoda bile tamamen kapanmak yerine, kısıtlı da olsa çalışmaya devam etmelidir.
Sistemler Neden Çöker? En Sık Karşılaşılan Hata Türleri
Can merakla sordu: “Peki dayı, ben kodumu hatasız yazsam bile sistem neden çöker? Sorun sadece benim yazdığım kodda mı?”
Dayısı güldü. “Ah Can, keşke dünya sadece bizim yazdığımız kodlardan ibaret olsaydı. Dağıtık sistemlerde (distributed systems) hataların büyük çoğunluğu bizim dışımızdaki etkenlerden kaynaklanır. Bunları kategorize etmek, doğru hata yönetimi stratejisini seçmemiz için çok önemlidir.”
Dayısı cebinden kalemini çıkarıp Can’ın önündeki kağıda bir tablo çizmeye başladı:
| Hata Kategorisi | Açıklama (LSI) | Gerçek Dünya Örneği | Çözüm Yaklaşımı |
|---|---|---|---|
| Geçici Hatalar (Transient Failures) | Anlık ağ dalgalanmaları veya kısa süreli aşırı yüklenmeler. | Ödeme geçidinin 1 saniyeliğine yanıt vermemesi. | Yeniden Deneme (Retry) mekanizması. |
| Kalıcı Hatalar (Persistent Failures) | Yanlış yapılandırma, kod hatası veya fiziksel donanım arızası. | Yanlış veri tabanı şifresi girilmesi. | Hızlı Başarısız Olma (Fail-Fast) ve Alarm üretme. |
| Sistem Aşırı Yüklenmesi (Overload) | Sistemin kaldırabileceğinden çok daha fazla istek alması. | Efsane Cuma indirimlerinde sitenin kilitlenmesi. | Hız Sınırlama (Rate Limiting) ve Kuyruk yönetimi. |
“Gördüğün gibi yeğenim,” diye devam etti dayısı, “her hatanın karakteri farklıdır. Geçici bir ağ hatası için sistemi kapatmak ne kadar yanlışsa, kalıcı bir veri tabanı bağlantı hatasını durmadan yeniden denemek de o kadar aptalcadır. Çünkü kalıcı hataları sürekli denemek, sistemi daha da yorar ve kaynakları tüketir.”
Yeniden Deneme (Retry) Mekanizması Nasıl Tasarlanır?
Can heyecanla araya girdi: “Dayı, o zaman geçici hatalar için hemen bir ‘döngü’ yazıp hata aldıkça tekrar istek atayım, değil mi?”
“Dur orada aceleci yeğenim!” dedi dayısı. “Eğer bir servis yoğunluktan dolayı yavaşlamışsa ve sen ona durmadan, saniyede yüzlerce kez ‘tekrar dene’ isteği gönderirsen ne olur? O servisi tamamen çökertirsin. Kendi ellerinle kendi sistemine siber saldırı (DDoS) yapmış olursun. Bu yüzden yeniden deneme (retry) mekanizmalarını tasarlarken çok dikkatli olmalıyız.”
Dayısı, güvenli bir yeniden deneme mekanizması için üç altın kuralı sıraladı:
- Üstel Geri Çekilme (Exponential Backoff): Her başarısız denemeden sonra bekleme süresini katlayarak artırmalısın. Örneğin; ilk deneme başarısız olunca 1 saniye, ikincide 2 saniye, üçüncüde 4 saniye beklemelisin.
- Rastgele Sapma (Jitter): Bekleme sürelerine ufak rastgele saniyeler eklemelisin. Eğer tüm istemciler aynı anda üstel geri çekilme yaparsa, hepsi aynı saniyede tekrar istek atar ve hedef servisi yine kilitler. Jitter bu yoğunluğu dağıtır.
- Maksimum Deneme Sınırı (Max Retries): Sonsuza kadar deneme yapamazsın. Genellikle 3 veya 5 denemeden sonra pes edip alternatif plana geçmelisin.
“Şimdi gel bunu modern bir JavaScript koduyla somutlaştıralım,” diyerek Can’ın klavyesine uzandı dayısı:
async function guvenliIstekAt(url, denemeSayisi = 3, gecikme = 1000) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Sunucu hatası!");
return await response.json();
} catch (error) {
if (denemeSayisi <= 1) {
console.error("Maksimum deneme sınırına ulaşıldı. Hata:", error.message);
throw error;
}
// Rastgele sapma (Jitter) eklenmiş üstel geri çekilme hesaplaması
const jitter = Math.random() * 200;
const sonrakiGecikme = gecikme * 2 + jitter;
console.warn(İstek başarısız oldu. ${gecikme}ms sonra tekrar deneniyor...);
await new Promise(resolve => setTimeout(resolve, gecikme));
return guvenliIstekAt(url, denemeSayisi - 1, sonrakiGecikme);
}
}
“Bak yeğenim,” dedi dayısı kodu göstererek. “Bu kodda hem üstel geri çekilme uyguladık hem de araya jitter ekleyerek sunucunun nefes almasını sağladık. İşte profesyonel hata yönetimi budur.”
Devre Kesici (Circuit Breaker) Deseni Nasıl Çalışır?
Can kodu inceledi ama aklına başka bir soru takıldı: “Peki dayı, karşıdaki servis tamamen çökmüşse ve bizim 5 saniye bekleyecek vaktimiz yoksa ne yapacağız? Kullanıcıyı her seferinde saniyelerce bekletmek haksızlık değil mi?”
Dayısı keyifle gülümsedi. “Harika bir noktaya parmak bastın Can. İşte tam bu senaryo için elektrik mühendisliğinden ödünç aldığımız bir desenimiz var: Devre Kesici (Circuit Breaker) deseni.”
“Evlerimizdeki sigorta kutusunu düşün,” diye devam etti dayısı. “Eğer bir odada kısa devre varsa, sigorta atar ve elektrik akımını keser. Böylece tüm evin yanmasını engeller. Yazılımda da devre kesici aynı mantıkla çalışır. Üç temel durumu (state) vardır:”
- Kapalı (Closed): Her şey yolundadır. İstekler hedef servise doğrudan iletilir. Hatalar izlenir.
- Açık (Open): Hedef servis belirlenen hata eşiğini (örneğin son 10 istekten 5’inin başarısız olması) aşarsa devre açılır. Artık istekler hedef servise hiç gönderilmez, doğrudan hata veya yedek plan (fallback) döndürülür. Böylece sistem zaman kazanır ve karşı servis iyileşme fırsatı bulur.
- Yarı Açık (Half-Open): Belirli bir süre sonra devre kesici, karşı servisin düzelip düzelmediğini anlamak için birkaç deneme isteği gönderir. Eğer bu istekler başarılı olursa devre tekrar ‘Kapalı’ konumuna döner. Başarısız olursa ‘Açık’ konumuna geri döner.
Bu desen sayesinde, çökmüş bir servise ısrarla istek göndererek hem kendi kaynaklarımızı (thread, bellek) tüketmeyiz hem de karşı servisin üzerindeki yükü kaldırarak onun daha hızlı ayağa kalkmasına yardımcı oluruz.
Yedek Plan (Fallback) ve Kademeli Bozulma Nedir?
Can kafasını salladı. “Devre kesici harika bir şeymiş dayı! Ama devre açıldığında kullanıcıya sadece hata ekranı mı göstereceğiz? Bu da kötü bir deneyim değil mi?”
“Kesinlikle kötü bir deneyim yeğenim,” dedi dayısı. “İşte bu yüzden devre açıldığında veya tüm denemeler başarısız olduğunda devreye Yedek Plan (Fallback) mekanizması girer. Amacımız kademeli bozulma (graceful degradation) sağlamaktır.”
Dayısı yerel bir e-ticaret sitesinden örnek verdi: “Düşün ki Türkiye’nin büyük bir e-ticaret sitesindesin. Kullanıcıya özel ürün önerileri sunan yapay zeka servisin çöktü. Kullanıcının karşısına kocaman bir ‘500 Sunucu Hatası’ mı çıkarmalısın? Yoksa o alan boş kalmasın diye veritabanından önceden önbelleğe (cache) alınmış en çok satan popüler ürünleri mi göstermelisin?”
Can, “Tabii ki en çok satanları gösteririm dayı, kullanıcı sistemin çöktüğünü anlamaz bile!” dedi.
“Aynen öyle Can,” dedi dayısı. “Yedek planlar hayat kurtarır. Bir ödeme servisi çalışmadığında kullanıcıya ‘Şu an banka entegrasyonumuzda çalışma var, siparişinizi kaydettik, en kısa sürede işleme alacağız’ demek bir yedek plandır. Veya ana veritabanı çöktüğünde verileri salt okunur (read-only) bir yedek veritabanından sunmak kademeli bozulmadır. Sistem tam kapasite çalışmaz ama tamamen de durmaz.”
Gerçek Dünya Senaryosu: Bir E-Ticaret Ödeme Akışı
Dayısı, konunun iyice pekişmesi için Can’a gerçek bir vaka analizi sundu. “Gel şimdi seninle ‘Efsane Cuma’ gecesinde çöken bir ödeme sistemini nasıl kurtaracağımızı adım adım tasarlayalım.”
Senaryomuzda bir kullanıcının sepetindeki ürünleri satın almak için “Öde” butonuna bastığını varsayalım. Arkada çalışan ödeme mikroservisi, yerel bir bankanın API’sine bağlanmak zorunda. Ancak banka sunucuları yoğunluktan dolayı çok yavaş yanıt veriyor.
Dayısı bu süreci nasıl yönetmeleri gerektiğini adım adım anlattı:
- Adım 1: Zaman Aşımı (Timeout) Belirleme: Banka API’sine yapılan istek için maksimum 3 saniyelik bir zaman aşımı süresi koymalıyız. Eğer banka 3 saniye içinde cevap vermezse, isteği hemen sonlandırmalıyız. Sonsuza kadar bekleyerek sistemdeki iş parçacıklarını (threads) kilitleyemeyiz.
- Adım 2: Devre Kesiciyi Devreye Sokma: Eğer son 20 istekten 10’u bu şekilde zaman aşımına uğrarsa, devre kesiciyi ‘Açık’ konumuna getirmeliyiz. Artık yeni gelen ödeme isteklerini bankaya göndermemeliyiz.
- Adım 3: Yedek Planı (Fallback) Çalıştırma: Devre açıkken gelen ödemeler için kullanıcıya ‘Birincil ödeme kanalımızda yoğunluk yaşanıyor, sizi alternatif ödeme kanalımıza yönlendiriyoruz’ mesajı gösterilmeli ve istek otomatik olarak ikinci bir ödeme geçidine (örneğin başka bir banka veya iyzico gibi bir aracı kurum) yönlendirilmelidir.
- Adım 4: Asenkron Süreç Yönetimi: Eğer alternatif kanal da yanıt vermiyorsa, siparişi ‘Beklemede’ durumuna alıp kullanıcıya ‘Ödemeniz sıraya alındı, onaylandığında size SMS ile bilgi vereceğiz’ diyerek işlemi arka planda çalışan güvenli bir mesaj kuyruğuna (message queue) aktarmalıyız.
Can gözleri parlayarak dinliyordu. “Dayı, bu senaryoda kullanıcı siteden eli boş dönmüyor, alışverişini tamamlıyor ve biz de satışı kaçırmıyoruz!”
Hata Yönetiminde Sıkça Yapılan Hatalar Nelerdir?
Dayısı ayağa kalktı, çay bardağını tazelemek için yönelirken son olarak Can’ı sık yapılan hatalar konusunda uyardı. “Hata yönetimi yapıyorum derken kendi topuğuna sıkma yeğenim. Şu üç hatadan kesinlikle uzak durmalısın:”
İlk olarak, hataları yutmak (silent exceptions) en büyük günahlardan biridir. Bir catch bloğu yazıp içini boş bırakmak veya sadece hata mesajını konsola basıp geçmek, sistemdeki sorunları halının altına süpürmektir. Hatalar mutlaka loglanmalı (kayıt altına alınmalı) ve izleme (monitoring) araçlarına bildirilmelidir.
İkinci olarak, yanlış hata türlerini yeniden denemek gelir. Örneğin, kullanıcının şifresini yanlış girmesi sonucu oluşan bir “Yetkisiz Erişim (401 Unauthorized)” hatasını 5 kez yeniden denemek tamamen anlamsızdır. Yeniden deneme mekanizmaları sadece geçici ağ ve sunucu hataları (500, 503, 504 gibi) için kullanılmalıdır.
Son olarak, zaman aşımlarını (timeouts) yapılandırmamak sistemlerin yavaş yavaş ölmesine neden olur. Kullandığın her dış kütüphane ve HTTP istemcisi için varsayılan zaman aşımlarını mutlaka manuel olarak set etmelisin. Varsayılan değerler genellikle çok uzundur veya hiç yoktur.
“Şimdi Can,” dedi dayısı kapıdan çıkarken, “git ve o yazdığın mikroservise üstel geri çekilmeli bir yeniden deneme mekanizması ve güzel bir devre kesici ekle. Akşama kontrol edeceğim!”
Hata Yönetimi Hakkında Sıkça Sorulan Sorular
Hata yönetimi ile hata ayıklama (debugging) arasındaki fark nedir?
Hata ayıklama (debugging), yazılım geliştirme aşamasında koddaki hataları bulup düzeltme sürecidir. Hata yönetimi (failure handling) ise, sistem canlı ortamda çalışırken ortaya çıkan kaçınılmaz operasyonel hataları (ağ kesintisi, servis çökmesi vb.) sistemin çökmesini engelleyecek şekilde yönetme stratejisidir.
Her API isteği için devre kesici (circuit breaker) kullanmalı mıyım?
Hayır, her istek için gerek yoktur. Özellikle kritik öneme sahip, dış dünyaya bağımlı olan ve sistemin darboğaz (bottleneck) oluşturabileceği ana servis geçişlerinde (ödeme sistemleri, dış veri sağlayıcıları vb.) devre kesici kullanılması önerilir. Kendi kontrolünüz altındaki kararlı iç servisler için basit zaman aşımları yeterli olabilir.
Yedek plan (fallback) verileri güncel değilse ne yapmalıyım?
Yedek planın amacı mükemmel veriyi sunmak değil, sistemi çalışır halde tutmaktır. Kullanıcıya eski (stale) veri göstermek, hiç veri göstermemekten veya hata ekranı vermekten genellikle daha iyidir. Ancak kritik finansal işlemlerde eski veri göstermek yerine, işlemi güvenli bir şekilde sıraya alıp kullanıcıyı bilgilendirmek daha doğru bir yaklaşımdır.
Hata yönetimi sistem performansını olumsuz etkiler mi?
Aksine, doğru yapılandırılmış bir hata yönetimi sistem performansını korur. Devre kesiciler sayesinde yanıt vermeyen servislere boşuna istek atılmasının önüne geçilir ve sistem kaynakları (CPU, bellek, thread) gereksiz yere tüketilmez. Sadece yeniden deneme (retry) sayıları çok yüksek tutulursa geçici bir ek yük oluşabilir.
#Teknoloji #HataYonetimi #YazilimGelisitirme #SistemMimarisi