Gülin Koçak Demir
Akbank Teknoloji
Published in
6 min readAug 3, 2023

--

Dayanıklılık Nasıl Sağlanır?

Sistemsel Dayanıklılık Yaklaşımları Üzerine

Ability of system to provide acceptable behaviour even when one or more parts of the system fail”: Sistemin bir veya daha fazla parçası arızalandığında bile sistemin kabul edilebilir davranış sağlama yeteneğidir dayanıklılık.

Dağıtılmış sistemlerle çalışırken her zaman bir numaralı “anything could happen” kuralını hatırlamak gerekir. Ağ sorunları, hizmetin erişilebilir olmaması, uygulama yavaşlığı vb. sorunlar ile karşılaşabiliriz. Bir sistemdeki sorun başka bir sistemin davranışını/performansını da etkileyebilir. Bu tür beklenmedik arızalarla/ağ sorunlarıyla uğraşmak ve çözebilmek zor olabilir. Sistemin bu tür arızalardan kurtulabilmesi ve işlevsel kalabilmesi, sistemi daha “dayanıklı” hale getirir. Ayrıca, birbirine bağlı hizmetlerde art arda gelen arızaları da önler.

Dayanıklı uygulamalar oluşturmak ve çalıştırmak zordur; uygulamanızın dayanıklılığını arttırmak ise devam eden bir yolculuktur. Dikkatli bir planlamayla, uygulamanızın hatalara dayanma yeteneğini geliştirebilirsiniz. Uygun süreçler ve kurum kültürüyle, uygulamanızın dayanıklılığını daha da arttırmak için başarısızlıklardan da ders çıkarabilirsiniz.

Dayanıklılık, mimarinizin tüm düzeylerinde planlama gerektirir. Altyapınızı ve ağınızı nasıl düzenlediğinizi, uygulamanızı ve veri depolamanızı nasıl tasarladığınızı etkiler.

Bu yazımızda dayanıklı sistemler/mimariler kurmamızı sağlayacak başlıca yaklaşımlardan söz edeceğiz.

Resiliency Approaches:

1. Assessing the System Topologies and Architectures Regularly:

“The only constant is change”

Kurumların ihtiyaçları, BT sistemlerinizin yapısı ve destek sağlayıcınızın yetenekleri değiştikçe her zaman sistemin mimarisini basitleştirmeye ve iyileştirmeye çalışmalısınız. Bu şüphesiz sürekli yatırım gerektirse de geçmişten alınan dersler açıktır: Gelişmek, büyümek ve yanıt vermek için BT sistemlerinin yaşaması, nefes alması ve değişmesi gerekir. Ölü, kemikleşen BT sistemleri, organizasyonu hızla durma noktasına getirir, yeni tehditlere ve fırsatlara yanıt veremez. Bu nedenle her zaman uygulamalarınızın mimarisini basitleştirmenin ve iyileştirmenin yollarını arayın. Yazılım sistemleri canlı varlıklardır ve değişen önceliklerinizi yansıtmak için uyum sağlamaları gerekir.

2. Design For Known Failures:

Dayanıklı uygulamalar oluşturmak ve çalıştırmak zordur; uygulamanızın dayanıklılığını arttırmak ise devam eden bir yolculuktur. Dikkatli bir planlamayla, uygulamanızın hatalara dayanma yeteneğini geliştirebilirsiniz. Uygun süreçler ve kurum kültürüyle, uygulamanızın dayanıklılığını daha da arttırmak için başarısızlıklardan da ders çıkarabilirsiniz.

Önceki olaylardan ders almak, özellikle stres altındayken sistemdeki kısıtlamaları vurguladıklarında daha da önemlidir ve basitçe 3 adımda işletilebilir;

a. Öğrendiğiniz dersleri belgelemek için yaşanan önceki sorunları gözden geçirin.

b. Ardından başucu kitabınızı bu derslerle güncelleyin.

c. Son olarak, öğrendiklerinizi sonraki çalışmalarınızda uygulayın.

3. Embrace Failures (Know How It Fails):

Sistemlerimizin nasıl fail ettiğini bilmek ve buna uygun şekilde önlemler almak hataları önlemenin önemli yollarından biridir. Tespitleri yaparken son dönemlerde öne çıkan “Kaos Mühendisliği — Chaos Engineering” metotlarından faydalanılabilir.

Bu metotlar ile sisteme enjekte ettiğiniz hatalar, görevlerin çökmesini, RPC’lerdeki hataları ve zaman aşımlarını veya kaynak kullanılabilirliğindeki azalmaları içerebilir. Hizmet bağımlılıklarında aralıklı arızaları test etmek için rastgele hata enjeksiyonu da yapılabilir. Sistemin bu hatalı durumlara vereceği davranışların üretimde tespit edilmesi ve azaltılması zordur.

Kaos mühendisliği, bu tür deneylerden kaynaklanan sorunların en aza indirilmesini ve kontrol altına alınmasını sağlar. Bu tür testler, gerçek kesintiler için pratik olarak ele alınabilir ve toplanan tüm bilgiler kesinti yönetimini iyileştirmek için kullanılabilir.

4. Fail Fast:

Fail-fast yaklaşımı hataların hızlı yakalandığı ve hızlı fail eden bir sistem tasarlama temeline dayanan yaklaşımdır. Bu yaklaşımla tasarlanan sistemde bir sorun oluştuğunda, hemen ve görülebilir (Failing immediately and visibly) olur. Böylece sistemimizi/uygulamamızı kırılganlıktan kurtarıp daha sağlam olmasını sağlayabiliriz.

Hataların erken evrede bulunması ve düzeltilmesi daha kolaydır. Bu nedenle daha az sorun production’a yansır, kalite artar. Debug ve bug-fix maliyetimiz azalırken verimliliğimiz de artar.

Hızlı başarısız olmanın faydaları:

- Sorunları hızlı bir şekilde ortaya çıkarır.

- Şeffaflık kültürü oluşturur.

- Boşa harcanan çabayı, zamanı ve maliyeti en aza indirir.

- Geliştirme projelerinde verimliliği artırır.

Fail-fast yaklaşımı ile ilgili bazı örnekler:

- Konfigürasyon dosyasındaki connection-count parametresinin isminin hatalı yazılması ve runtime’da parametre bulunamadığında oluşan exception’ın handle edilerek default değer olarak -örneğin 10- atanması. Bu hatayı bir varsayıma göre düzeltme yaklaşımı başta uygulamayı fail etmekten kurtarıcı gibi görünse de aslında hatayı gizleyerek yoğun yük altında ortaya çıkmasına ve geç tespit edilmesine sebep olur. Üstelik çözümü çok daha uzun sürecek bir soruna yol açacaktır.

Doğru yöntem ise hatanın tespit edildiğinde ilgili konfigürasyon dosyası ve parametresi gibi tespiti kolaylaştıracak bilgileri de içerecek şekilde exception’ın atılması, log’lanması ve işlemin devam etmeden kesilmesidir.

- “Catch-all exception” bloklarının hataları yutmasına ve görünür olmasına engel olmak “fail-fast” yaklaşımına terstir. Uygulamamızda global “error-handler”larımız olmalı ve “catch-all” ile yutulan “exception” kalmamasına özen göstermeliyiz.

- Fonksiyonların/servislerin input parametrelerin kontrollerini, fonksiyonun/servisin başında herhangi bir “business logic” işletilmeden önce null/boş gibi değerlerle yapmalıyız.

- Hızlı hata veren bir uygulama/sistem, startup sırasında tüm değiştirilemez ve gerekli olan “initial” konfigürasyonun doğru olduğunu kontrol eder. Örneğin, filepath’in startup’ta yüklenmiş olduğundan daha uygulamanın açılışında emin olmalıyız. Diğer türlü “request” geldiğinde -yani sonradan- hatayı fark edebiliriz.

- Fail-fast bir uygulama/sistem, herhangi bir hesaplama talebi gelmeden önce gelecekteki hesaplamalar için gereken tüm girdi/çıktı kaynaklarının hazır olup olmadığını kontrol eder.

5. Degrade Functionality If Possible When Overloaded:

Sistemimiz yoğun yük altında sorun yaşıyorken yük devam ettiği takdirde tamamen cevap veremez hale gelip tam bir fonksiyonel kesinti durumu oluşabilir. Bu durumu önlemek amacıyla sistemin normal zamanlardaki fonksiyonalitesini sağlamak yerine kontrollü olarak düşürmeyi tercih edebiliriz.

Bunu gerçekleştirmek için mimarimizi aşırı yüklenmeyi tolere edecek şekilde tasarlamalıyız. Yani sistemlerimizin aşırı yükü algılamasını ve kullanıcıya daha düşük kaliteli de olsa hizmet sunmaya devam edebilmesini sağlayan birçok yönteme başvurabiliriz. Bu yöntemlerden en yaygın kullanılanları aşağıda yer almaktadır:

· Cache’ten cevap verme

· Static web sayfalarına yönlendirme

· “Read-only” (Salt okunur) işlemlere erişime izin verme ve veri güncelleme yapan işlemleri geçici olarak devre dışı bırakma

· “Circuit breaker” ile trafiği kontrollü olarak kesme

· Fallback metodu kullanma.

Detaylar “Resiliency Patterns” isimli bir sonraki yazımızda incelenecektir.

6. Predict Peak Traffic Events and Plan For Them:

Üretime yeni aldığımız ürün/sistem/özelliklerin kullanıma açılması, kampanya aktivasyonları, yılbaşı, bayram arifesi vb. önemli gün ve tatil olayları sistemlerimizin daha yoğun kullanılmasına ve dolayısıyla uygulamalarımızın kullanım trafiğinde standart temel çizgisinin ötesinde keskin bir artışa neden olacaktır. Bu tarz trafik arttırıcı zirve olayların sistemlerimizi kesintiye uğratmaması ise planlı ölçeklendirme yapılmasını gerektirir.

Bu ölçeklemeyi sağlıklı yapmak için uygulanabilecek başlıca pratikler sıralanmıştır:

Pratik 1: Öncesinde aşağıdaki konuları da gözeterek iyi bir plan yapmak ve bu planı dokümante etmek önerilmektedir:

- Varsayımları, riskleri ve bilinmeyen faktörleri tanımlayın.

- Yaklaşan lansman veya yoğunluk yaratacak etkinlikle ilgili bilgileri belirlemek için geçmiş etkinlikleri inceleyin. Hangi verilerin mevcut olduğunu ve bu verilerin geçmişte sağladığı değeri belirleyin.

- Detaylı rollback planı hazırlayın.

- Bir mimari incelemesi gerçekleştirin:

- Önemli kaynakları ve mimari bileşenleri belgeleyin.

- Riskleri ve ölçeği belirlemek için ortamı tüm mimari gereksinim ve kriterler açısından gözden geçirin.

- Uygunsa, bir arıza olması durumunda otomatik yeniden başlatma için uyarı eylemlerini kullanmak üzere hizmeti yapılandırın.

- İzlenecek metrikleri ve uyarıları belirleyin, izleme ve uyarı sisteminizi öğrenilen dersleri de göz önüne alarak event’e uygun şekilde hazırlayın.

- Gerekli kota yönetimini planlayın.

- İlgili paydaşları içeren bir iletişim planı, kritik görevleri ve bunların sahibi olan ekipleri içeren net bir zaman çizelgesi oluşturun.

Pratik 2: Sonrasında da yaşanan sorunların review edilmesi ve öğrenilen derslerin sonraki event’lerde de kullanılmak üzere belgelenmesi önemlidir. Önceki olaylardan ders almak, özellikle stres altındayken sistemdeki kısıtlamaları vurguladıklarından çok daha önemlidir.

7. Test and Validate Resiliency and Scalability Behaviors of Systems

Uygulamalarımızın beklenen kalite ve güvenliğe uygunluğunu doğrulamak için pek çok aşamada ve çeşitte testler yapmamız gerekir. Birim testleri, entegrasyon testleri, güvenlik testleri gibi testler en bilinen yöntemlerdir. Bunların dışında kaos (Chaos)/dayanıklılık ve ölçeklenebilirlik testleri, disaster recovery testleri olarak bilinen sistemlerimizi daha iyi tanımamıza ve dayanıklılık arttırıcı çözümler geliştirmemize yarayan testler de vardır.

Uygulamanız geliştikçe kullanıcı davranışı, trafik profilleri ve hatta iş öncelikleri değişebilir. Benzer şekilde, uygulamanızın bağlı olduğu diğer hizmetler veya altyapı da gelişebilir. Bu nedenle, uygulamanızın dayanıklılığını ve ölçeklenebilirliğini de düzenli olarak test etmek ve doğrulamak da oldukça önemlidir.

Uygulamanızın hatalara beklediğiniz şekilde yanıt verip vermediğini test etmek çok önemlidir. Buradaki ana fikir, başarısızlıktan kaçınmanın en iyi yolunun başarısızlığı tanımak ve ondan öğrenmek olduğudur.

Arızaları simüle etmek ve tanımak karmaşıktır. Uygulamanızın veya hizmetinizin davranışını doğrulamanın yanı sıra, beklenen uyarıların ve uygun ölçümlerin oluşturulduğundan da emin olmalısınız. Burada yöntem olarak öncelikle basit başarısızlıkları tanıttığınız ve ardından ilerlettiğiniz yapılandırılmış bir yaklaşım önerilir. Örneğin, her aşamada davranışı doğrulayarak ve belgeleyerek aşağıdaki gibi ilerleyebilirsiniz:

1. Basit arızaları tanıtın.

2. Hizmetin bağımlılıklarına erişimi engelleyin.

3. Tüm ağ iletişimini engelleyin.

4. Ana bilgisayarları sonlandırın.

Uygulama hizmetlerinizi yönetmek için Istio gibi bir hizmet ağı kullanıyorsanız, bölmeleri(pod) veya makineleri öldürmek yerine uygulama katmanına hatalar enjekte edebilir veya TCP katmanına bozucu paketler enjekte edebilirsiniz. Ağ gecikmesini veya aşırı yüklenmiş bir hizmet alınan sistemi simüle etmek için gecikmeler sağlayabilirsiniz.

Performans ve yük testleri için yaygın bir yaklaşım, cevap verme ya da işlemi tamamlama süresi gibi temel metriklerin değişen yükler için beklenen seviyelerde kalmasını sağlamaktır. Örneğin, web katmanınızın ölçeklenebilirliğini test ediyorsanız, ani hacimlerdeki kullanıcı istekleri için ortalama istek gecikmelerini ölçebilirsiniz.

Ayrıca testlerinizin, test yükünü işlemek için oluşturulan kaynak sayısının beklenen aralık içinde olduğunu ölçmesi de önemlidir. Örneğin, testleriniz bazı arka uç görevlerini yerine getirmek için oluşturulan Pod/VM sayısının belirli bir değeri aşmadığını doğrulayabilir.

Edge (Sınır) senaryolarını test etmek de önemlidir. Maksimum ölçeklendirme sınırlarına ulaşıldığında uygulamanızın veya hizmetinizin davranışı nasıl olur? Hizmetiniz küçülüyorsa ve ardından yük aniden tekrar artıyorsa davranış nedir?

8. Apply Resiliency Patterns to Avoid Crashes and Cascading Failures:

Timeout”, “Circuit Breaker”, “Bulkhead”, “Retries”, “Rate Limit”, “Load Shedding” gibi dayanıklılık arttırmaya yönelik pattern’leri uygulayarak bağımlı hizmetler erişilebilir olmadığında bile temel hizmetlerin beklendiği gibi çalışmasını ve yanıt vermesini sağlayabiliriz.

Dayanıklılık pattern’lerinden bahsedeceğimiz bir sonraki yazımızda görüşmek üzere…

--

--