Bulut Uygulamaları İçin Dayanıklılık (Resiliency) Tasarım Desenleri

Dayanıklılık, zor durumlar ile başa çıkabilme, ayağa kalkabilme ve tekrar devam edebilmektir. Yazılım dünyasında dayanıklılık ise bir uygulamanın hata durumlarına düştüğünde durumu yönetebilmesi, tekrar çalışır hale gelebilmesi ve sorunlu durumdan tamamen kullanılmaz hale gelmeden kurtulabilmesi diyebiliriz. Uygulamanın devamlılığını sağlayabilmesi, geliştiricileri olarak bizlerin sorumluluğudur. Ayrıca bu tarz durumlarda yine sorunu çözebilmek için gecesi gündüzüne karışanlar gene biz yazılım geliştiriciler oluyoruz. Dolayısıyla uygulama geliştirirken dayanıklılık konusu da tasarımımızın bir parçası olmalı, gerekli yerlerde mimarimize bu tarz sorunlu durumlarda hayatımızı kolaylaştıracak müdahaleler yapmalıyız. MSDN'in bu linkteki makaleler zincirini okurken kendi adıma aldığım kısa notlar olarak yazdığım bu yazıdaki çoğu görsel kaynak da yine aynı makalelerden alınmıştır. Bulkhead Tasarım Deseni Bulkhead, gemilerde kullanılan, geminin gövdesini odalara ayırarak tehlikeli durumlara karşı gemiyi koruma yöntemidir. Bu sayede ayrık odalardan birinde meydana gelecek kaza ve su sızıntısı, geminin tamamının su almasını engeller. Uygulama içerisinde özellikle yoğun kullanılan servisleri ayrı uygulama havuzları (pool) ile çalıştırarak kaynakların paylaşılması sağlanır. Bir serviste oluşacak sistem yoğunluğu ya da hatalar silsilesi, sadece bir havuzu etkileyeceğinden, uygulama altındaki diğer servisler etkilenmeden çalışmaya devam edebilirler. Bazı senaryolarda uygulamanın mimarisinin anlaşılmasının zorlaştırabilir, bu gibi durumlarda faydası ile karşılaştırarak tercih edilebilir.       Devre Kesici (Circuit Breaker) Tasarım Deseni Uygulamalarımız bazen hata durumuna düşer ve bu hata durumlarından çıkması, müdahale süresi vakit alabilir. Bu tarz bir durumda, uygulama gelen istekleri karşılayamaz ve sürekli hatalar verir. Belki arka planda yarım işlemler bırakır. Ya da uygulama zaman aşımına düşme durumlarında istemci uygulamaya hata mesajının dönmesi vakit alabilir. Biriken yeni talepler gitgide daha fazla soruna sebep olabilir. İşte bu tarz sorunlu anlarda uygulamanın bu durumu, tamamıyla hata durumuna düşmeden, kolay bir şekilde atlatabilmesi için tasarlanmış devre kesici - circuit breaker tasarım desenini kullanabilir. Bu tasarım desenine göre uygulamamız üç ayrı durumda bulunabilir. Kapalı Durum (Closed) : Kapalı elektrik devresi gibi düşünebiliriz. Uygulamamız gelen isteklere cevap verebilir durumdadır ve gelen istekleri işler, cevap döner. Hata alınması durumunda bir sayaç değeri arttırılarak tutulur. Yarı Açık Durum (Half-Open) : Uygulama gelen isteklerin bazılarını işler, bazılarına ise hızlıca hata mesajı döner. İşlenen istekler hata alırsa sayaç arttırılmaya devam eder.  Açık durum (Open) : Uygulama stabil durumda değildir. Gelen isteklere belli bir zaman aşımı süresine kadar hata döner ve istekleri işlemeye çalışmaz. Bu tasarım deseni dış kaynaklara erişilen durumlarda tercih edilebilir. Uygulama içi kaynakların kullanıldığı durumlarda ek bir mekanizma olacağından yük getirebilir.     Telafi İşlem Tasarım Deseni Başlattığımız bir işlemin hata alması durumunda işlemin rollback edilmesi geliştirme sırasında sıklıkla çalıştığımız bir senaryodur. Eğer çalıştığımız sistem, birbirine bağlı microservice veya entegre uygulamalardan oluşuyor ise rollback mekanizması sıkı kurallara sahip ve tutarlı olamayabilir. Her yapılan işlemin tersini veya hata durumunda yapılacak işlemleri içeren bir metodunun olması gerekir. Bazen parçalı işlemlerden bir kısmı gerçekleştirilip, bir aşamasında hata alındığında, uygulamaya bağlı olarak, her zaman işlemler tersine çevrilmeyebilir. Yerine alternatif işlemler yapıldığı durumlar da olabilir. Mesela birden fazla noktaya uçuş planladığımız bir uçuş planının bir kısmı gerçekleştirilirken, bir aşamasında hata alındığını düşünelim. Bu hata alınan aşamada alınmış olan biletleri iptal etmek yerine belki kullanıcı onayı ile farklı bir tarihteki uçuş ile veya farklı bir sağlayıcıdan girilecek bir tarife ile alternatif bir çözüm bulunabilir. Bu mekanizma geliştirilen uygulama özelinde bir senaryo tasarlanarak incelenmelidir. Bu tasarımlar sırasında rollback veya telafi işlemin gerçekleştirilmesi sırasında alınabilecek hatalar da göz önünde bulundurulmalı.  Aslında benim fikrim bu konu dayanıklılık konusu dışında zaten tasarım aşamasında düşünülmesi gereken senaryolardan biri. Sistem hataları olması durumunda uygulamanın dayanıklılığından ziyade müşteri deneyimini iyileştirmeye yönelik bir çalışma. Ayrıca hatalı ya da yarım kalıp tamamlanamayacak işlemler konusunda nasıl aksiyon alınacağının yönetimi yine bu tarz çalışmalarda düşünülmesi gereken başlıklardan.   Uygulama Sağlığı İzleme Noktaları (Health Monitoring Endpoints) Uygulamalarımız arka planda bir çok kaynağı kullanıyor ve bu kaynaklara erişimde sunucu bazlı sorunlarla karşılaşıyor olabiliriz. Uygulamamız bulutta veya sunucuda çalıştığı durumlarda her an elimizin altında olmadığından sunucularda olabilecek sorunları sorun iletilmeden fark edebilmek çok önemli. Bu tarz sistem kaynaklarına erişim ve network seviyesindeki gecikmeleri izleyebilmek için uygulamalara bu kontrolleri yapan birer erişim noktası tanımlanıp, belli aralıklarla uygulamanın sağlığı ve gelen isteklere cevap süreleri kontrol edilir. Hatta uygulamanın başka servislere entegrasyon noktaları ve cevap süreleri de kontroller içerisine alınıp, tüm erişim ve performans takibi yapılabilir. Kısaca şu kaynakların erişimi ve cevap sürelerini izlemek faydalı olacaktır :  Var ise disk erişimi Var ise ağ dizini erişimi Veritabanı erişimi CDN'de tutulan script veya css gibi kaynaklar var ise bunlara erişim Dış entegrasyon servislerine erişim Sertifika validasyonları     Lider Seçim Deseni Uygulamalarımızda toplu olarak ve birbiri ile koordineli olarak çalışması gereken işlemler yer aldığında bir thread veya instance tarafından işlenen işin bir diğer thread veya instance tarafından ezilmesini engellemek ve eldeki tüm kaynakları koordine edecek bir lidere ihtiyaç duyarız. Bu liderin kaynaklar içerisinden seçilip, liderin bir sorun yaşaması halinde ya da lidere erişilemez hale geldiği bir durumda yeni bir liderin seçilebilmesini sağlayan bir tasarıma ihtiyaç olur. Liderin diğer kaynaklar gibi işi yapması gerekmeyebilir. Liderin tek işi koordinasyon ve diğer kaynakların birbiri ile çakışmaması olabilir. Liderin sistem için bir darboğaz oluşturmaması gerekir. Eğer tüm kaynaklar aynı kodu çalıştırıyor ise tüm kaynaklar lider olabilme potansiyeline sahip demektir, bu durumda birden fazla lider seçimi yapılmaması dikkat edilecek önemli noktalardan bir tanesidir. Aşağıdaki örneğe bakarsak, işlemleri yapan üç ayrı instance görüyoruz. Üçü de aynı liderlik metodunu çağırıyor, 1 nolu instance metodu ilk çağırdığı için liderlik rolünü alıyor ve metodu locklıyor. 2 ve 3 nolu instance'lar liderlik rolünü alamıyor, ama belli aralıklarla liderlik rolünü metodunu çağırmaya devam ediyorlar. Liderliği elde eden 1 nolu instance elindeki işleri diğer 2 ve 3 nolu instance'lara paylaştırıyor. Bu arada liderin sağlıklı olduğunu ve işini yapabildiğini kontrol edebilmek için lider belli zaman aralığında liderlik vasfını yeniliyor. Eğer liderde bir sorun var ise lock kalkıyor, yapılan işlemler geri alınıyor. Diğer instancelar'dan biri lider olup koordinasyonu sağlıyor. Mesaj Kuyruğu Tabanlı Yük Dengeleme Enterprise uygulamalarda yük yönetimi ve servisler arası bağımlılığı azaltma amaçlı olarak sıklıkla kuyruk tabanlı çözümler kullanırız. Uygulamamıza gelen mesaj sayısı zamana göre değişebiliyor ve zaman zaman uygulamanın karşılayabileceği seviyelerin üzerine çıkıyor ise kuyruk çözümü derdimize çare olacaktır. İşlenmek üzere uygulamamıza gelen mesajlar mesaj kuyruğunda, uygulamamız müsait oldukça uygulamamıza beslenerek yük kontrolü sağlanabilir. Yada mesajlar, kuyruk tarafından tüketici uygulamalarımıza yük durumlarına göre paylaştırılabilir. Özellikle veritabanı gibi tek bir kaynağı işleyen fonksiyonlarda aynı anda birden fazla işlem kaynağa erişimde düğüm oluşturup uygulamamızı cevap veremez hale getirebilir. Bu tarz durumlar için çözüm işlenecek mesajları kuyrukta biriktirip, peyderpey alıp işlemektir.       Tekrar Deneme Uygulamamızın çalışması sırasında sıklıkla hatalar ile karşılaşırız. Kimi hatalar ağ kesintileri veya servis yoğunluklarından kaynaklı olup, geçici olabildiği gibi kimileri ise uygulamada bulunan mantık hatalarından kaynaklı kalıcı hatalar olabilir. Hatalara, tipine göre tekrar deneme mekanizmasının kurulması uygulamanın dayanıklılığını arttırıcı önlemlerden biridir. Hata durumlarında alınabilecek aksiyonları temelde aşağıdaki gibi düşünebiliriz. İptal : Oluşan hata kalıcı bir hatadır, aralıklarla tekrar tekrar işlemin denenmesi işlemi başarılı yapmaz. Bu tarz durumlarda işlem iptal edilip, loglanarak raporlanmalıdır. Tekrar Deneme : Oluşan hata, nadir oluşan geçici bir sistem aksaklığından dolayı oluşmuştur. Bu tarz hataları tekrar denemek işlemi başarılı sonlandırabilir. Ertelemeli Tekrar Deneme : Oluşan hata, yine nadir oluşan geçici bir sistem aksaklığından dolayı oluşmuştur. Fakat tekrar deneme işlemi art arda denemelerde tekrar tekrar hata alınıyor ise bekleme süresi arttırılarak tekrar deneme yapılır, hatta belli sayıda denemeden sonra limit aşımından işlemler iptal edilir. Böylece sistemde hata alan işlemlerden dolayı bir yük yaratılmaz. Tekrar deneme tasarımları yapılırken, agresif ertelemeler vermek sistemi negatif yönde etkileyebilir. O yüzden uygun bekleme sürelerinin belirlenmesi çok kritiktir. Ayrıca yukarıda bahsettiğimiz Devre Kesici Tasarım Deseni mekanizma içerisine dahil edilerek daha kontrollü bir tekrar deneme mekanizması tasarlanabilir. Yeniden denenecek işlemlerin her defasında yarım kalmış kayıtlar yaratma olasılığı olduğundan dolayı denemeler atomik ve hata durumunda kendi kendini geri alabiliyor olmalı (rollback). İşlem her yeni denemede sanki sıfırdan sisteme giriyor gibi çalışabilmeli ve hata durumunda sistemde kalıntı bırakmamalı. Tekrar deneme mekanizmasını kurgularken, tekrar denenecek hataların iyi analiz edilmesi gerekir. Her hatanın tekrar denemeye sokulması sistemde beklenmedik sonuçlar doğurabilir. Hata logları iyi analiz edildikten sonra tekrar deneme mekanizması kurulup, mekanizma için güvenli, yani tekrar denemeye müsait hatalar tespit edildikten sonra kullanıma alınması daha uygun olacaktır.   Örneğin yukarıdaki servis çağrısı, ilk denemede HTTP 500 hatası alıyor. Belli bir süre beklenip mekanizma tarafından tekrar deneneniyor, tekrar HTTP 500 hatası alıyor. Önceki beklemeden daha uzun bir süre beklenip, tekrar deneniyor ve bu sefer HTTP 200 cevabı alıp işlemi başarıyla tamamlıyor.   Scheduler, Agent, Supervisor Tasarım Deseni Yukarıda öğrendiğimiz tasarım desenlerinin topluca birlikte kullanıldığı bir desen olan Scheduler Agent Supervisor tasarım deseni, bir işlemi gerçekleştirmek için gerekli olan birbirine bağlı işlerden oluşan iş akışının yönetimine odaklanıyor. Bu tasarım deseni isminden de anlaşılacağı üzere desen üç parçadan oluşuyor. Scheduler : İş akışı içerisindeki işleri organize edip çalıştıran, aralarındaki orkestrasyonu yapan yapı. Agent : Her bir işi çalıştıran yapı, scheduler tarafından yönlendirilen yapı aynı zamanda. İş eğer hata alırsa, yarım işlemleri geri alan yapıyı da içeriyor olmalı. Supervisor : Scheduler tarafından çalıştırılan işlerin durumlarını takip eden, gerekli durumlarda tekrar çalıştırılması için scheduler'ı tetikleyen yapı.         Agent'ların hata alması veya timeout olması durumunda supervisor'lar devreye girip işi geri alıyor. Fakat supervisor'ın hata alması veya cevap vermemesi durumunda scheduler, bu işi komple hatalı duruma çekip iptal etmelidir. İptale çekilen iş için yeni bir supervisor ile iş tekrar denenmesi halinde, önceki supervisor agent'a veya scheduler'a tekrar müdahale edememelidir. Lider seçimi tasarım deseni burada supervisor seçimi ve çalışması için uygun yöntem. Karmaşık bir yapıya sahip bir sistem olduğu için yapının tüm hata senaryolarını içeren bir testi yapılmalı.