Hatalar Olabilir

Bir hizmet veya sistem bir diğerini aradığı zamanlarda hatalar olabilir. Bu hatalara çeşitli faktörler neden olabilir. Bunların arasında sunucular, ağlar, yük dengeleyiciler, yazılım, işletim sistemleri veya işletim sistemlerinden kaynaklanan hatalar vardır. Sistemlerimizi hata ihtimalini azaltacak şekilde tasarlıyoruz. Fakat, hiç hata vermeyen bir sistem oluşturmak imkansızdır. Amazon’da sistemlerimizi, hata ihtimalini tolere edecek ve azaltacak ve küçük oranlı hataların tam bir kesintiye dönüşmesini önleyecek şekilde tasarlıyoruz. Dayanıklı sistemler oluşturmak için üç esas araç kullanıyoruz: zaman aşımları, yeniden denemeler ve geri çekilme.

Birçok hata türü, isteklerin normalden daha uzun sürmesi ve muhtemelen hiç tamamlanmamasıyla belirgin olur. Bir istemci, bir isteğin tamamlanması için normalden daha uzun süre beklerken aynı zamanda o isteğin tamamlanması için kullanılan kaynakları da daha uzun süre tutar. Birkaç istek kaynakları uzun süre meşgul edince sunucunun kullanabileceği kaynaklar tükenir. Bu kaynaklara bellek, iş parçacıkları, bağlantılar, kısa ömürlü bağlantı noktaları veya sınırlı olan her şey dahil olabilir. Bu durumu önlemek için istemciler zaman aşımı ayarlar. Zaman aşımı bir istemcinin bir isteğin tamamlanması için beklediği en fazla süre miktarıdır.

Çoğu zaman, aynı isteği yeniden gerçekleştirmeyi denemek o isteğin başarılı olmasıyla sonuçlanır. Çünkü oluşturduğumuz sistemler genellikle tek bir birim olarak hata vermezler. Bunun yerine, kısmi veya geçici hatalar verirler. Kısmi hata, isteklerin bir kısmının tamamlanması durumudur. Geçici hata, bir isteğin kısa bir zaman dilimi için hata vermesidir. Yeniden denemeler aynı isteği yeniden göndererek istemcilerin bu rastgele kısmi hatalardan ve kısa ömürlü geçici hatalardan kurtulmasını sağlar.

Yeniden deneme her zaman güvenli değildir. Eğer sistem zaten aşırı yüklenmeye yaklaştığı için hata veriyorsa yeniden deneme söz konusu sistemdeki yükü arttırabilir. Bu sorunu önlemek için istemcilerimizin geri çekilmekullanmalarını sağlıyoruz. Bu, sonraki yeniden denemeler arasındaki süreyi arttırarak arka uçtaki yükü dengede tutar. Yeniden denemeler ile ilgili bir diğer sorun ise uzaktan çağrıların yan etkilerinin olmasıdır. Zaman aşımı veya hata, yan etkilerin yaşanmadığı anlamına gelmeyebilir. Eğer defalarca yan etki yapılması istenmiyorsa API’ları tek seferde etkili olacak şekilde tasarlamak iyi bir uygulamadır. Bu, güvenli bir şekilde yeniden denenebilecekleri anlamına gelir.

Son olarak trafik, Amazon hizmetlerine sabit oranlarda ulaşmaz. Onun yerine, isteklerin ulaşma oranı sıklıkla geçici ve büyük artışlar yaşar. Bu artışlar istemcinin davranışlarından, hata kurtarmadan ve periyodik zamanlanmış görev gibi basit bir şeyden bile kaynaklanabilir. Eğer hatalar yükten kaynaklanıyorsa tüm istemcilerin aynı zamanda yeniden deneme işlemi gerçekleştirmesi durumunda yeniden deneme etkisiz olabilir. Bu sorunu önlemek için sapma kullanıyoruz. Bu, varış oranını yayıp büyük geçici artışlar yaşanmasının engellenmesini sağlamak için istek yapmadan veya bir isteği yeniden denemeden önce geçen rastgele zaman miktarıdır.

Bu çözümlerin her biri aşağıdaki bölümlerde ele alınmıştır.

Zaman aşımları

Amazon’daki en iyi uygulamalardan birisi herhangi bir uzaktan çağrı için ve genel olarak aynı kutuda olsa bile herhangi bir süreç için zaman aşımı ayarlamaktır. Buna hem bağlantı zaman aşımı hem de istek zaman aşımı dahildir. Birçok standart istemci güçlü yerleşik zaman aşımı kapasitesi sunar.
Genelde, en zor sorun ayarlanacak zaman aşımı değerini seçmektir. Zaman aşımını çok yüksek ayarlamak kullanılabilirliğini düşürür çünkü istemci zaman aşımını beklerken kaynaklar hala tüketilir. Zaman aşımını çok düşük ayarlamanın iki riski vardır:
 
• Çok fazla isteğin yeniden denenmesi sonucunda gecikmenin ve arka uçta trafiğin artması.
• Tüm isteklerin yeniden denenmesi sonucunda küçük arka uç gecikmesinin tam bir kesintiye yol açması.
 
Bir AWS Bölgesi içindeki aramalar için bir zaman aşımı seçmeye aşağı akış hizmetinin gecikme ölçümleri ile başlamak iyi bir uygulamadır. Bu yüzden Amazon’da bir hizmetin başka bir hizmeti aramasını sağladığımızda kabul edilebilir bir yanlış zaman aşımı oranı seçiyoruz (örneğin % 0,1). Sonra aşağı akış hizmetinde karşılık gelen gecikme yüzde değerlerine bakıyoruz (bu örnekte p99,9). Aşağıda belirtildiği üzere, bu yaklaşım çoğu durumda işe yarar fakat birkaç dezavantajı vardır:
 
• Bu yaklaşım, istemcilerin, örneğin internet üzerinden önemli oranda ağ gecikmesinin olduğu durumlarda işe yaramaz. Bu gibi durumlarda istemcilerin dünyada çeşitli yerlere yayılmış olmalarını da göz önünde bulundurarak kabul edilebilir en kötü ihtimal ağ gecikmesini hesaba katarız.
• Bu yaklaşım, p99,9’nin p50’ye yakın olduğu sıkı gecikme sınırları olan hizmetlerde işe yaramaz. Bu gibi durumlarda biraz dolgu yapmak çok sayıda zaman aşımına sebep olan küçük gecikmelerin artmasını önler.
• Zaman aşımı uygularken yaygın bir dezavantajla karşılaştık. Linux’un SO_RCVTIMEO’su güçlüdür. Fakat, uçtan uca soket zaman aşımı olarak kullanılmaya elverişsiz olmasına neden olan bazı dezavantajları vardır. Java gibi bazı diller bu denetimi doğrudan sunar. Go gibi diğer diller ise daha güçlü zaman aşımı mekanizmaları sunar.
• Zaman aşımının, DNS veya TLS anlaşmaları gibi tüm uzaktan çağrıları kapsamadığı uygulamalar da vardır. Genel olarak iyi test edilmiş istemcilerde yerleşik olan zaman aşımlarını kullanmayı tercih ediyoruz. Eğer kendi zaman aşımlarımızı uygulayacaksak zaman aşımı soket seçeneklerinin tam anlamına ve ne iş yapıldığına çok dikkat ederiz.
 
Amazon’da çalıştığım bir sistemde birkaç zaman aşımının dağıtıldıktan hemen sonra bir bağımlılık unsuru ile iletişime geçtiğini gördük. Zaman aşımı yaklaşık 20 milisaniye gibi çok düşük bir değere ayarlanmıştı. Dağıtımların dışında böyle düşük bir zaman aşımı değerinde bile zaman aşımlarının düzenli olarak yaşandığını görmedik. Araştırdıkça zamanlayıcının, sonraki isteklerde yeniden kullanılan yeni bir güvenli bağlantı kurduğunu keşfettim. Bağlantı kurmak 20 milisaniyeden uzun sürdüğü için dağıtımlardan sonra yeni bir sunucu hizmete girdiğinde birkaç isteğin zaman aşımına uğradığını gördük. Bazı durumlarda istekler yeniden denediler ve başarılı oldular. Öncelikli olarak, bir bağlantının kurulması durumuna karşın zaman aşımı değerini arttırarak bu sorunu çözdük. Sonra, bir süreç başladığında trafik almadan önce bu bağlantıları kurarak sistemi geliştirdik. Böylece zaman aşımı sorununu tamamen çözmüş olduk.

Yeniden denemeler ve geri çekilme

Yeniden denemeler “bencildir.” Bir başka deyişle bir istemci yeniden denediği zaman daha yüksek bir başarı şansı yakalamak için sunucunun zamanını daha çok harcar. Hataların nadir ve geçici olduğu durumlarda bu sorun olmaz. Çünkü genel olarak yeniden denenen isteklerin sayısı azdır ve artan belirgin erişilebilirlik tavizi kullanışlıdır. Hatalar aşırı yükten kaynaklandığı zamanlarda yükü arttıran yeniden denemeler durumu büyük ölçüde kötü hale getirebilir. Asıl sorun çözüldükten çok sonra bile yükü yüksek tutarak durum kurtarma işlemini bile erteleyebilirler. Yeniden denemeler güçlü bir ilaca benzer. Doğru dozda kullanıldığında etkili olurken çok kullanıldığında büyük ölçüde zarar verebilirler. Ancak, dağıtılmış sistemlerde doğru sayıda yeniden deneme elde etmek için tüm istemciler arasında koordinasyon sağlamanın neredeyse bir yolu yoktur.

Amazon’da kullanmayı tercih ettiğimiz çözüm geri çekilmedir. İstemci, hemen ve saldırgan bir şekilde yeniden denemek yerine yeniden denemeler arasında bir süre bekler. En yaygın görülen model ise her girişim sonrasında bekleme süresinin katlanarak arttığı üstel geri çekilmedir. Üstel geri çekilmeler hızlı büyüdükleri için çok uzun geri çekilme sürelerine neden olabilir. Çok uzun süre yeniden denemeyi önlemek için uygulamalar genelde geri çekilmelerini maksimum bir değer ile sınırlar. Buna, tahmin edildiği gibi sınırlı üstel geri çekilme denir. Ancak, bu başka bir soruna neden olur. Şimdi tüm istemciler sürekli sınırlı oranda yeniden deniyor. Neredeyse tüm durumlarda çözümümüz istemcinin yeniden deneme sayısını sınırlamak ve daha önceden hizmet odaklı mimari de meydana gelen hatayı ele almaktır. Çoğu durumda istemci çağrıdan vazgeçer çünkü onun da kendi zaman aşımı vardır.

Aşağıda belirtildiği üzere, yeniden denemelerle ilgili başka sorunlarda vardır:

• Dağıtılmış sistemlerin çoğunlukla birden fazla katmanı vardır. Müşterinin çağrısının, hizmet çağrısı yığını oluşturduğu bir sistem düşünün. Bu, bir veri tabanı sorgusu ve her katmanda üç yeniden deneme ile sonlanır. Veri tabanı yük altındayken sorgularda başarısız olmaya başlarsa ne olur? Eğer her bir katman bağımsız bir şekilde yeniden denerse veri tabanı üzerindeki yük 243 kat artacak ve durumun kurtarılması olanaksız hale gelecektir. Bunun sebebi her bir katmanda yeniden denemelerin çoğalmasıdır. Önce üç yeniden deneme, sonra dokuz yeniden deneme ve böyle çoğalarak devam eder. Bunun aksine yığının en üst katmanındaki yeniden deneme önceki çağrılardaki işleri heba edebilir ve bu da verimi düşürür. Genel olarak, düşük maliyetli denetim düzlemi ve veri düzlemi işlemleri için en iyi uygulamamız yığının tek bir noktasında yeniden denemektir.
• Yük. Sadece tek bir yeniden deneme katmanı olsa bile hatalar başlayınca trafik önemli ölçüde artar. Bir hata eşiğinin aşılmasıyla birlikte bir aşağı akış hizmetinin tamamen durduğu zamanlarda sorunun çözülmesi için devre kesicilerbüyük ölçüde desteklenir. Ancak, devre kesiciler sisteme test edilmesi zor tipik davranışlar tanıtırlar ve durum kurtarma için gereken zamanı önemli ölçüde arttırabilirler. Bu riski, bir belirteç klasörü ile yeniden denemeleri yerel bir şekilde sınırlayarak azaltabileceğimizi bulduk. Bu, belirteç olduğu sürece tüm çağrıların yeniden denemelerine ve belirteçler bittiğinde ise sabit bir oranda yeniden denemelerine olanak sağlar. AWS, 2016’da bu davranışı AWS SDK’ya ekledi. Yani, SDK’yı kullanan müşteriler bu kısıtlayıcı davranışa yerleşik olarak sahiptir.
• Yeniden deneme zamanını belirleme. Genel olarak teklik sağlamadıkları sürece yan etkileri olan API’ların yeniden denenmesinin güvenli olmadığı görüşündeyiz. Bu, ne sıklıkla yeniden denediğiniz fark etmeksizin yan etkilerin sadece bir kez yaşanacağını garanti eder. Kaynak oluşturma API’leri tek seferlik değilken salt okunur API’ler genelde tek seferliktir. Amazon Elastic Compute Cloud (Amazon EC2) RunInstances API gibi bazı API’ler teklik sağlamak ve yeniden denenmeyi güvenli hale getirmek için açık belirteç tabanlı mekanizmalar sunarlar. Yinelenen yan etkileri önlemek için istemcileri uygularken iyi bir API tasarımı ve hizmeti gereklidir.
• Hangi hataların yeniden denemeye değdiğini bilmek. HTTP istemci ve sunucu hataları arasında belirgin bir fark sunar. İstemci hatalarının aynı istekle yeniden denetlenmemesi gerektiğini belirtir. Çünkü, sunucu hataları sonraki denemelerde başarılı olabilecekken istemci hataları sonrasında başarılı olmayacaktır. Ancak, sistemlerdeki nihai tutarlılık bu çizgiyi büyük ölçüde bulanıklaştırır. Durum değiştikçe, bir istemci hatası sonradan başarıya dönüşebilir.

Bu riskler ve zorluklara rağmen yeniden denemeler geçici ve rastgele hatalar karşısında yüksek erişilebilirlik sundukları için güçlü mekanizmalardır. Her bir hizmet için doğru tavizi bulmak karar gerektirir. Bizim tecrübemize göre yeniden denemelerin bencil olduğunu hatırlamak iyi bir başlangıçtır. Yeniden denemeler, istemcilerin isteklerinin önemini belirtme ve hizmetten bu isteği ele alması için daha fazla kaynak kullanmasını talep etme şeklidir. Eğer bir istemci çok bencil olması çeşitli sorunlar yaratabilir.

Sapma

Hatalar, iş yükünden veya çekişmeden kaynaklandığı zaman geri çekilme genellikle gerektiği kadar yardımcı olmaz. Bunun nedeni bağıntıdır. Eğer tüm başarısız çağrılar aynı anda geri çekilirse yeniden denendikleri zaman yeniden çekişmeye veya aşırı yüklenmeye neden olurlar. Çözümümüz sapmadır. Sapma, yeniden denemeleri zamana yaymak için geri çekilmeye bir miktar rastlantısallık ekler. Ne kadar sapma eklenmesi gerektiği ve en iyi ekleme yolları hakkında daha fazla bilgi edinmek için Üstel Geri Çekilme ve Sapmasayfasına bakınız.

Sapma sadece yeniden denemeler için değildir. Operasyonel tecrübe bize, denetim düzlemleri ve veri düzlemleri dahil olmak üzere hizmetlerimizdeki trafiğin çok fazla aniden artma eğiliminde olduğunu öğretti. Bu ani trafik artışları çok kısa olabilir ve genellikle toplanan ölçümler tarafından gizlenir. Sistemleri oluştururken tüm zamanlayıcılara, periyodik işlere ve diğer ertelenen işlere sapma eklemeyi göz önünde bulundururuz. Bu, ani iş artışlarının yayılmasına yardımcı olur ve aşağı akış hizmetlerinin bir iş yükü için ölçeklendirilmesini kolaylaştırır.

Planlanmış işe sapma eklerken rastgele her bir konakta sapma seçmeyiz. Bunun yerine aynı konakta her seferinde aynı numarayı üreten tutarlı bir yöntem kullanırız. Böylece, aşırı yüklenen bir hizmet veya yarışma durumu varsa sapma, bir model halinde aynı şekilde gerçekleşir. Biz insanlar modelleri belirleme konusunda iyiyizdir ve temel nedeni belirleme olasılığımız daha fazladır. Rastgele bir yöntem kullanmak, bir kaynak doldurulurken bunun sadece rastgele olmasını sağlar. Bu sorun gidermeyi çok daha zor hale getirir.

Amazon Elastic Block Store (Amazon EBS) ve AWS Lambda gibi üzerinde çalıştığım sistemlerde istemcilerin çoğu zaman dakikada bir kez gibi düzenli aralıklarla istek yolladığını gördüm. Ancak bir istemcinin birden fazla sunucusu aynı şekilde davranıyorsa sıraya girerek isteklerini aynı anda tetikleyebilirler. Bu, günlük işler için bir dakikanın ilk birkaç saniyesi veya gece yarısından sonra ilk birkaç saniye olabilir. Saniye başı yüke dikkat ederek ve periyodik iş yüklerini saptırmak için istemciler ile çalışarak daha az sunucu kapasitesi kullanarak aynı miktarda işi yaptık.

Müşteri trafiğindeki ani artışlar üzerinde daha az kontrolümüz var. Ancak, müşteri tarafından tetiklenen görevler için bile müşteri deneyimini etkilemediği yerde sapma eklemek iyi bir fikirdir.

Sonuç

Dağıtılmış sistemlerde, uzaktan etkileşimlerde geçici hatalar veya gecikmeler kaçınılmazdır. Zaman aşımları sistemlerin aşırı uzun süre asılı kalmasını önler, yeniden denemeler bu hataları gizleyebilir ve geri çekilme ve sapma, kullanımı geliştirebilir ve sistemlerdeki sıkışıklığı azaltabilir.

Amazon’da, yeniden denemeler konusunda dikkatli olmamız gerektiğini öğrendik. Yeniden denemeler bağımlı sistemdeki yükü arttırabilir. Eğer bir sisteme giden çağrılar zaman aşımına uğruyorsa ve o sistem aşırı yükleniyorsa, yeniden denemeler aşırı yükü iyileştireceğine daha kötü hale getirebilir. Bu artışı, sadece bağımlılığın sağlıklı olduğunu gözlemlediğimiz zaman yeniden deneyerek önlüyoruz. Yeniden denemelerin erişilebilirliğin iyileştirilmesine yardımcı olmadıkları durumlarda yeniden denemeyi durduruyoruz.


Yazar hakkında

Marc Brooker, Amazon Web Services'ta Kıdemli Baş Mühendistir. 2008'den bu yana AWS'de EC2, EBS ve IoT gibi çeşitli hizmetlerde çalışmıştır. Bugün ölçeklendirme ve sanallaştırma dahil olmak üzere AWS Lambda üzerine odaklanmaktadır. Marc, hata düzeltmelerini ve son durum incelemelerini okumaktan gerçekten keyif almaktadır. Elektrik mühendisliği alanında doktora yapmıştır.

Dağıtılmış sistemlerde karşılaşılan zorluklar Aşırı yüklenmeyi engellemek için yük atma yöntemini kullanma Dağıtılmış sistemlerde geri dönüşten kaçınma