Hayatı taklit eden algoritmalar
Üniversitedeki ilk bilgisayar bilimi dersimden beri, algoritmaların gerçek dünyada nasıl bir rol oynadıkları ilgimi çekiyordu. Gerçek dünyada gerçekleşen bazı olayları düşündüğümüzde, bunları taklit eden algoritmalar oluşturabiliriz. Özellikle markette, trafikte veya havaalanında kuyrukta beklerken bunu düşünüyorum. Kuyrukta beklerken sıkılmanın, kuyruğa alma teorisine kafa yormak için harika bir fırsat olduğunu belirledim.
On yılı aşkın bir süre önce, bir günümü bir Amazon deposunda çalışarak geçirdim. Raflardan ürünlerin alınması, bir kutudan diğerine konulması ve kolilerin taşınması konularında bir algoritma bana kılavuzluk etti. Çok sayıda kişiyle birlikte çalışırken, aslında harika bir şekilde düzenlenmiş fiziksel birleştirme sıralamasının bir parçası olmaya bayıldım.
Kuyruğa alma teorisinde, kısa kuyrukların davranışları nispeten ilgi çekici değildir. Sonuçta, kuyruk kısaysa herkes mutludur. Kuyrukta biriktirme listesi oluştuğunda, bir etkinliğin kuyruğu kapıdan çıkıp köşeyi dönecek kadar uzadığında, insanlar aktarım hızını ve önceliklendirmeyi düşünmeye başlar.
Bu makalede, kuyruk biriktirme listesi senaryolarıyla başa çıkmak için Amazon'da kullandığımız stratejilerden, kuyrukları hızlıca boşaltmak ve iş yüklerini önceliklendirmek için kullandığımız tasarım yaklaşımlarından bahsedeceğim. En önemlisi de kuyruk biriktirme listelerinin en baştan nasıl önleneceğini açıklayacağım. İlk kısımda, biriktirme listelerine yol açan senaryoları açıklarken, ikinci kısımda biriktirme listelerinden kaçınmak veya bunlarla düzgün biçimde başa çıkmak için Amazon'da kullandığımız yaklaşımlardan bahsedeceğim.
Kuyrukların sinsi doğası
Kuyruğa dayalı sistemler
Bu makaledeki kuyruğa dayalı sistemleri anlatabilmek için iki AWS hizmetinin nasıl çalıştığından bahsedeceğim: kodunuzu, üzerinde çalıştığı altyapı hakkında endişelenmenize gerek kalmadan olaylara karşı çalıştıran bir hizmet olan AWS Lambda ve bağlı cihazların hem kolay hem de güvenli bir şekilde bulut uygulamalarıyla ve diğer cihazlarla etkileşime geçmesini sağlayan yönetilen bir hizmet olan AWS IoT Core.
AWS Lambda ile işlev kodunuzu yüklersiniz ve ardından işlevlerinizi iki yoldan biriyle çağırırsınız:
• Zaman uyumlu olarak: İşlevinizin çıktısı size HTTP yanıtıyla döner.
• Zaman uyumsuz olarak: HTTP yanıtı hemen döner ve işleviniz yürütülüp arka planda yeniden denenir.
Lambda, sunucu hataları oluştuğunda bile işlevinizi çalıştırdığı için isteğinizin depolanacağı dayanıklı bir kuyruğa ihtiyaç duyar. İşleviniz ilk seferinde başarısız olursa, dayanıklı bir kuyruk sayesinde isteğiniz yeniden işleme alınabilir.
AWS IoT Core ile hem cihazlarınız hem de uygulamalarınız bağlantı kurar ve PubSub mesaj konularına abone olabilir. Bir cihaz veya uygulama bir mesaj yayımladığında, eşleşen aboneliklere sahip uygulamalar kendi mesaj kopyalarını alır. Bu PubSub mesajlaşmasının çoğu, zaman uyumsuz olarak gerçekleşir çünkü kısıtlanmış bir IoT cihazı, sınırlı kaynaklarını tüm abone cihazların, uygulamaların ve sistemlerin bir kopya aldığından emin olmak için beklemeye harcamak istemez. Abone bir cihaz; başka bir cihaz tarafından ilgisini çekecek bir mesaj yayımlandığında çevrimdışı olabileceğinden, bu özellik oldukça önemlidir. Çevrimdışı cihaz yeniden bağlandığında, öncelikle hızına tekrar kavuşmayı ve ardından mesajların kendisine teslim edilmesini bekler (yeniden bağlantı sonrasında mesaj teslimini yönetmek üzere sisteminizi kodlama hakkında bilgi edinmek için AWS IoT Geliştirici Kılavuzu'ndaki MQTT Kalıcı Oturumları bölümüne göz atın). Bunu gerçekleştirmek için arka planda çalışan çeşitli kalıcı ve zaman uyumsuz işlem bulunur.
Bunlar gibi kuyruğa dayalı sistemler, genellikle dayanıklı bir kuyrukla birlikte uygulanır. SQS; dayanıklı, ölçeklenebilir, en az bir defa mesaj teslimi semantiği sunar. Bu nedenle, Lambda ve IoT dahil olmak üzere Amazon ekipleri zaman uyumsuz ölçeklenebilir sistemler oluştururken düzenli olarak bunu kullanır. Kuyruğa dayalı sistemlerde, bir bileşen mesajları kuyruğa koyarak veri üretir; diğer bir bileşen belirli aralıklarla mesajları sorgulayarak, işleyerek ve sonunda işlem bitince silerek verileri tüketir.
Zaman uyumsuz sistemlerdeki hatalar
AWS Lambda'da işlevinizin çağırma işlemi normalden daha yavaşsa (örneğin, bağımlılık yüzünden) veya kısa süreli olarak başarısız olursa, hiçbir veri kaybı olmaz ve Lambda, işleviniz için yeniden deneme yapar. Lambda, çağrılarınızı kuyruğa alır ve işlev tekrar çalışmaya başladığında işlevinizin biriktirme listesi üzerinde ayrıntılı şekilde çalışır. Ancak biriktirme listesi üzerinde ayrıntılı şekilde çalışmanın ve normale dönmenin ne kadar süreceğini bir düşünelim.
Mesajları işlerken bir saatliğine kesintiye uğrayan bir sistem düşünün. Mevcut hız ve işlem kapasitesi fark etmeksizin, kesinti sonrası kurtarma için sistem kapasitesinin, kurtarmanın ardından bir saat boyunca iki katı olması gerekir. Pratikte, özellikle de Lambda gibi esnek sistemlerle, sistem mevcut kapasitenin iki katından daha fazlasına sahip olabilir ve kurtarma daha hızlı tamamlanabilir. Diğer taraftan, işlevinizin etkileşime geçtiği diğer sistemler, siz biriktirme listesi üzerinde ayrıntılı şekilde çalışırken işlemedeki bu büyük artışa hazırlıklı olmayabilir. Böyle bir durumda, kurtarma çok daha uzun sürebilir. Zaman uyumsuz hizmetler, kesintiler sırasında biriktirme listeleri oluşturduğu için uzun kurtarma sürelerine yol açarken; zaman uyumlu hizmetler, kesintiler sırasında istek almayı sonlandırdıkları için daha kısa kurtarma sürelerine sahiptir.
Yıllar içerisinde kuyruğa alma hakkında düşünürken, zaman uyumsuz sistemler için gecikme süresinin önemli olmadığını düşünmeye bazen meyil gösterdik. Zaman uyumsuz sistemler, genellikle dayanıklılık sağlamak veya anlık arayanı gecikme süresinden yalıtmak için oluşturulur. Ancak pratikte, işlem süresinin önemli olduğunu ve zaman uyumsuz sistemlerin bile bazen saniyenin altında veya daha kısa gecikme sürelerine sahip olmasının beklendiğini belirledik. Dayanıklılık için kuyruklar sunulduğunda, biriktirme listesinden kaynaklanan uzun işleme gecikme süreleri gibi tavizleri gözden kaçırmak kolaydır. Zaman uyumsuz sistemlerde gizli risk, büyük biriktirme listeleriyle başa çıkmaktır.
Erişilebilirliği ve gecikme süresini nasıl ölçeriz?
Erişilebilirlik için gecikme süresinden ödün verme konusu, ilginç bir soruyu beraberinde getiriyor: Zaman uyumsuz bir hizmet için gecikme süresini ve erişilebilirliği nasıl ölçeriz ve nasıl hedefler koyarız? Üreticinin gözünden hata oranlarını ölçme, erişilebilirlik hakkında bir fikir verir ancak resmin tamamını sunmaz. Üretici erişilebilirliği, kullandığımız sistemin kuyruk erişilebilirliğiyle orantılıdır. Bu nedenle altyapı olarak SQS'i kullandığımızda, üretici erişilebilirliğimiz SQS erişilebilirliğiyle eşleşir.
Diğer taraftan, tüketici tarafında erişilebilirliği ölçtüğümüzde, sistemin erişilebilirliği, olduğundan daha kötü görünebilir çünkü hatalar için yeniden deneme yapılabilir ve sonraki bir denemede başarılı sonuç alınabilir.
Ayrıca teslim edilemeyen mesaj kuyruklarından (DLQ) erişilebilirlik ölçümlerini elde ederiz. Bir mesaj, denemeler sonucunda iletilmezse ya atılır ya da DLQ’ya eklenir. DLQ, işlenemeyen mesajları daha sonra araştırmak ve müdahale etmek üzere saklamak için kullanılan ayrı bir kuyruktur. Sonlandırılan veya DLQ'ya gönderilen mesajların oranı, iyi bir erişilebilirlik ölçümüdür ancak sorunu çok geç tespit edebilir. DLQ hacimleriyle ilgili uyarı almak iyi bir fikir olsa da DLQ bilgileri özellikle sorunları tespit etmemizde güvenemeyeceğimiz kadar geç ulaşır.
Peki ya gecikme süresi? Burada da üreticinin gözlemlediği gecikme süresi, kuyruk hizmetimizin gecikme süresini yansıtır. Bu nedenle daha çok kuyruktaki mesajların yaşını ölçme üzerine odaklanırız. Bu, sistemlerin arkada çalıştığı veya sıklıkla hata verip yeniden denemelere neden olduğu durumları hızlıca yakalar. SQS gibi hizmetler, her mesajın kuyruğa ulaştığı an için zaman damgası sağlar. Zaman damgası bilgisi sayesinde kuyruktan aldığımız her mesajda, sistemlerimizin ne kadar geride kaldığıyla ilgili ölçümleri günlüğe kaydedip oluşturabiliriz.
Gecikme süresi sorunu biraz daha inceliklidir. Sonuçta, biriktirme listelerinin oluşması beklenmektedir ve aslında bazı mesajlar için normaldir. Örneğin, AWS IoT'de bir cihazın mesajlarını okumak için çevrimdışı olmasının veya yavaşlamasının beklendiği zamanlar vardır. Bunun sebebi, çoğu IoT cihazının düşük güçlü ve internet bağlantılarının sorunlu olmasıdır. AWS IoT Core operatörleri olarak, cihazların çevrimdışı olarak veya mesajları yavaş okumayı seçerek sebep oldukları beklenen biriktirme listeleri ile sistem genelinde beklenmedik bir biriktirme listesi oluşması arasındaki farkı ayırt etmemiz gerekiyor.
AWS IoT'de hizmeti başka bir ölçümle düzenledik: AgeOfFirstAttempt. Bu ölçüm, eksi mesaj kuyruğa alma zamanını kaydeder ancak bunu yalnızca AWS IoT bir mesajı bir cihaza ilk kez teslim etmeye çalıştığında yapar. Bu sayede cihazlar yedeklendiğinde, yeniden mesaj teslim etme ve kuyruğa alma denemelerine sahip cihazlarla kirletilmemiş net ölçümlere sahip oluruz. Ölçümü daha da netleştirmek için ikinci bir ölçüm yayımlarız: AgeOfFirstSubscriberFirstAttempt. AWS IoT gibi bir PubSub hizmetinde, belirli bir konuya abone olabilecek cihaz ve uygulama sayısının sınırı olmadığından, bir milyon cihaza mesaj göndermeyle oluşan gecikme süresi, tek bir cihaza gönderirken yaşanan gecikme süresinden fazladır. Tutarlı bir ölçüm sağlamak için konunun ilk abonesine ilk mesaj yayımlama denemesinde zamanlayıcı bir ölçüm yayımlarız. Ardından, geri kalan mesajların yayımlanmasıyla ilgili sistemin ilerlemesini ölçen diğer ölçümlerimiz bulunur.
AgeOfFirstAttempt ölçümünün, sistem genelinde bir sorunu önceden haber verme görevi bulunur. Bunun nedeni büyük oranda, mesajlarını daha yavaş okumayı seçen cihazlardan gelen paraziti filtrelemesidir. AWS IoT gibi sistemlerin bundan çok daha fazla ölçümle düzenlendiğinden bahsetmemiz gerekir. Gecikme süresiyle ilgili mevcut tüm ölçümlerle, yeniden denemelerdeki gecikme süresinden ayrı ilk denemelerdeki gecikme süresini kategorize etme stratejisi Amazon genelinde yaygın olarak kullanılır.
Zaman uyumsuz sistemlerin gecikme süresini ve erişilebilirliğini ölçmek zordur ve hata ayıklama da karmaşık olabilir. Bunun nedeni, isteklerin sunucular arasında gidip gelmesi ve her sistemin dışında gecikebilecek olmasıdır. Dağıtılmış izlemeyle ilgili yardımcı olması amacıyla parçaları birleştirebilmemiz için kuyruğa alınan mesajlarımıza bir istek kimliği dağıtıyoruz. Bu konuda yardımcı olması için X-Ray gibi sistemleri yaygın olarak kullanıyoruz.
Çok kiracılı zaman uyumsuz sistemlerde biriktirme listeleri
Zaman uyumsuz çoğu sistem, çok kiracılıdır ve işleri birden fazla müşteri adına tamamlar. Bu, gecikme süresinin ve erişilebilirliğin yönetilmesini zorlaştırır. Çok kiracılı sistemin avantajı, birden çok filoyu ayrı ayrı yönetmenin getirdiği iş yükünü kolaylaştırması ve birleştirilmiş iş yüklerini daha yüksek kaynak kullanımıyla çalıştırmamıza izin vermesidir. Ancak müşteriler, diğer müşterilerin iş yüklerini hesaba katmadan tahmin edilebilir gecikme süresi ve yüksek erişilebilirlik ile sistemin sadece tek kiracı kendileriymiş gibi davranmasını bekler.
AWS hizmetleri, dahili kuyruklarını mesajlarını koymak için arayanlara doğrudan ifşa etmez. Bunun yerine, arayanların kimliğini doğrulamak ve arayan bilgilerini kuyruğa alma öncesinde her mesaja eklemek için basit API'ler uygular. Bu, daha önce açıklanan Lambda mimarisine benzemektedir: Bir işlevi zaman uyumsuz olarak çağırdığınızda Lambda, mesajınızı Lambda'nın dahili kuyruklarını doğrudan size ifşa etmeden Lambda'nın sahip olduğu bir kuyruğa koyar ve hemen size döndürür.
Bu basit API'ler adil kısıtlama eklememize de izin verir. Çok kiracılı bir sistemde adil olmak, bir müşterinin iş yüklerinin diğer müşteriyi etkilememesi için çok önemlidir. AWS'nin adil kullanım uygulamasının genel bir yolu, müşteri başına orana dayalı sınırlama belirleyerek ani performans artışları için biraz esneklik sunmaktır. SQS gibi diğer pek çok sistemimizde, müşteri başına sınırı müşterilerimiz organik olarak büyüdükçe artırırız. Sınırlar, beklenmedik artışlar için korkuluk işlevi görerek, arka planda tedarikle ilgili düzenlemeler yapmamızı sağlar.
Zaman uyumsuz sistemlerdeki adil kullanım, bazı açılardan zaman uyumlu sistemlerdeki kısıtlama gibi çalışır. Ancak çok hızlı bir şekilde oluşabilecek büyük biriktirme listeleri nedeniyle zaman uyumsuz sistemlerde bunu göz önünde bulundurmanın daha önemli olduğunu düşünüyoruz.
Daha iyi açıklamak için zaman uyumsuz bir sistemde gürültücü komşulara karşı yerleşik bir koruma olmadığında neler olabileceğini düşünün. Sistemdeki müşterilerden birisinin trafiği kısıtlama olmaksızın bir anda fırladığında ve sistem genelinde biriktirme listesi oluşturduğunda bir operatörün ilgilenmesi, neler olduğunu anlaması ve sorunu hafifletmesi 30 dakikayı bulabilir. Bu 30 dakika boyunca, sistemin üretici tarafı iyi bir şekilde ölçeklendirme yapıp tüm mesajları kuyruğa alabilir. Ancak kuyruğa alınan mesajların hacmi, tüketici tarafının ölçeklendirildiği kapasitenin 10 katıysa, sistemin biriktirme listesi üzerinde ayrıntılı şekilde çalışması ve kurtarma gerçekleştirmesi için 300 dakika gerekecektir. Kısa yük artışları bile uzun kurtarma süreleriyle sonuçlanabilir ve sonucunda uzun süreli kesintilere sebep olabilir.
Pratikte, kuyruk biriktirme listelerinin olumsuz etkilerini en aza indirmek veya önlemek için AWS'deki sistemlerde pek çok telafi edici faktör bulunmaktadır. Örneğin, otomatik ölçeklendirme yük artışında sorunları hafifletmeye yardımcı olur. Ancak telafi edici faktörleri göz önüne almadan yalnızca kuyruğa alma etkilerine odaklanmak yararlıdır çünkü bu, birden çok katmanda güvenilir olan tasarım sistemlerine yardımcı olur. Aşağıda büyük kuyruk biriktirme listelerinden ve uzun kurtarma sürelerinden kaçınmanıza yardımcı olabilecek birkaç tasarım modelini bulabilirsiniz:
• Zaman uyumsuz sistemlerde tüm katmanlarda koruma sağlama çok önemlidir. Zaman uyumlu sistemlerin biriktirme listesi oluşturma eğilimi olmadığından, onları ön kapı kısıtlaması veya erişim denetimi yoluyla koruruz. Zaman uyumsuz sistemlerde, sistemimizdeki her bileşenin kendini aşırı yüke karşı koruması ve bir iş yükünün kaynakların adil olmayan büyüklükte bir kısmını tüketmesinin önlenmesi gerekir. Her zaman ön kapı erişim denetiminden sıyrılan bazı iş yükleri ortaya çıkar ve bu yüzden sistemlerin aşırı yüklenmemesi için bir kemere, askıya ve cep korumalarına ihtiyacımız vardır.
• Birden fazla kuyruk kullanımı, trafiği şekillendirmeye yardımcı olur. Bazı açılardan tek bir kuyruk ile çok kiracılı sistemler birbirleriyle anlaşmazlık içinde olabilir. İş, paylaşımlı bir kuyruğa alınana kadar bir iş yükünü diğerinden yalıtmak zordur.
• Gerçek zamanlı sistemler genellikle FIFO benzeri kuyruklarla kullanılır ancak LIFO benzeri davranışlar tercih eder. Müşterilerimizden duyduklarımıza göre, bir biriktirme listesiyle karşılaştıklarında, yeni verilerin anında işlendiğini görmeyi tercih ediyorlar. Bir kesinti veya dalgalanma sırasında toplanan tüm veriler, kapasite kullanılabilir olduğunda işlenebilir.
Dayanıklı, çok kiracılı ve zaman uyumsuz sistemler oluşturmak için Amazon'un stratejileri
Çok kiracılı ve zaman uyumsuz sistemlerin, iş yüklerindeki değişikliklere karşı dayanıklı olması için Amazon'daki sistemler pek çok model kullanır. Pek çok teknik mevcuttur ancak Amazon'da her biri kendine ait canlılık setine ve dayanıklılık gereksinimine sahip pek çok sistem bulunmaktadır. Aşağıdaki kısımda, kullandığımız ve AWS müşterilerin sistemlerinde kullandıklarını söyledikleri bazı modelleri anlatacağım.
Bir kuyruğu tüm müşterilere paylaştırmak yerine, bazı sistemlerde her müşteriye kendine ait bir kuyruk veriyoruz. Hizmetlerin tüm kaynakları mevcut tüm kuyrukları yoklamak için kullanması gerekeceğinden, her bir müşteri veya iş yükü için bir kuyruk eklemek her zaman uygun maliyetli olmayabilir. Ancak az sayıda müşterinin veya bitişik hizmetin yer aldığı sistemlerde, bu basit çözüm yardımcı olabilir. Diğer taraftan, bir sistemde onlarca veya yüzlerce müşteri bulunuyorsa, ayrı kuyruklar yönetilmesi zor hale gelebilir. Örneğin, AWS IoT dünyadaki tüm IoT cihazları için ayrı kuyruk kullanmaz. Bu durumda, yoklama maliyetleri iyi bir şekilde ölçeklenmez.
AWS Lambda, her Lambda müşterisi için ayrı bir kuyruğu yoklamanın çok maliyetli olabileceği bir sistem örneğidir. Ancak tek bir kuyruğa sahip olmak, bu makalede anlatılan bazı sorunlara sebep olabilir. Bu nedenle tek bir kuyruğa sahip olmak yerine AWS Lambda, belirli sayıda kuyruk tedarik eder ve her bir müşteriyi az sayıdaki kuyruğa yerleştirir. Bir mesajı kuyruğa almadan önce hedeflenen kuyrukların hangisinde en az mesaj bulunduğunu görmek için kontrol eder ve mesajı, en az mesaj içeren kuyruğa yerleştirir. Bir müşterinin iş yükü arttığında, eşlenen kuyruklarda bir biriktirme listesi oluşturur ancak diğer iş yükleri otomatik olarak başka kuyruklara yönlendirilir. Bazı sihirli kaynak yalıtımlarında oluşturmak için çok fazla kuyruğa gerek yoktur. Bu, Lambda'da yerleşik olan pek çok korumadan sadece birisidir ve Amazon'daki diğer hizmetlerde de kullanılan bir tekniktir.
Bazı durumlarda bir kuyrukta biriktirme listesi oluştuğunda, trafiği önceliklendirmek için çok geç kalınmış olur. Ancak mesajın işlenmesi nispeten maliyetli ve zaman alacaksa, yine de mesajları ayrı, dış bir kuyruğa taşımak faydalı olabilir. Amazon'daki bazı sistemlerde tüketici hizmeti, dağıtılmış kısıtlama uygular ve yapılandırılmış oranı aşan bir müşterinin mesajları kuyruktan çıkarıldığında ayrı bir dış kuyruğa alınır ve birincil kuyruktaki mesajlar silinir. Kaynaklar erişilebilir hale gelir gelmez sistem hâlâ dış kuyruktaki mesajlar üzerinde çalışır. Aslında bu, bir öncelik kuyruğuna benzer. Benzer mantık, bazen üretici tarafında da uygulanır. Bu sayede sistem, tek bir iş yükünden yüksek miktarda istek kabul ettiğinde, bu iş yükü etkin yol kuyruğundaki diğer iş yüklerini kalabalıklaştırmaz.
Fazla trafikte olduğu gibi, eski trafiği de ayrı bir kuyruğa alabiliriz. Bir mesajı kuyruktan çıkardığımızda, ne kadar eski olduğunu kontrol edebiliriz. Sadece yaşını günlüğe kaydetmektense, mesajı canlı kuyruğu yakaladıktan sonra üzerinde ayrıntılı şekilde çalışacağımız biriktirme listesi kuyruğuna alıp almayacağımıza karar vermek için bilgileri kullanabiliriz. Çok fazla veri aldığımız durumda bir yük artışı meydana gelir ve geride kalırsak, bu trafik dalgasını olabildiğince hızlı bir şekilde kuyruktan çıkararak ayrı bir kuyruğa alırız. Bu, yeni mesajlar üzerinde çalışma için biriktirme listesi üzerinde sırayla çalışmamızdan çok daha hızlı bir şekilde tüketici kaynaklarını boşa çıkarır. LIFO sıralamasına benzemenin bir yolu budur.
Bazı sistemler, eski mesajların sonlandırılmasına tolerans gösterebilir. Örneğin, bazı sistemler deltaları sistemlere hızlıca işlemesine rağmen düzenli olarak tam eşitlemeyi de destekler. Çoğu zaman bu düzenli eşitleme sistemlerini, entropi karşıtı süpürücüler olarak adlandırırız. Böyle durumlarda, kuyruğa alınmış eski trafiği ayırmaktansa, en son süpürme işleminden önce ortaya çıkmışsa, maliyetsiz şekilde sonlandırırız.
Zaman uyumlu hizmetlerimizdeki kadar, zaman uyumsuz sistemlerimizi de bir iş yükünün, adil iş parçacığı payından fazlasını kullanmasını önlemek için tasarlıyoruz. AWS IoT hakkında bahsetmediğimiz bir özellik de kural altyapısıdır. Müşteriler AWS IoT'yi yapılandırarak, kendi cihazlarından mesajları bir müşterinin sahip olduğu Amazon Elasticsearch kümesine, Kinesis Stream'e vb. yönlendirebilir. Müşterinin sahip olduğu bu kaynaklardaki gecikme süresi artarsa ancak gelen mesaj oranı sabit kalırsa, sistemdeki eş zamanlılık miktarı artar. Sistemin herhangi bir anda ele alabileceği eş zamanlılık miktarı sınırlı olduğundan kural altyapısı, iş yükünün kendine ayrılan adil eş zamanla alakalı kaynak miktarından fazlasını kullanmasını önler.
Burada geçerli olan kural; bir sistemdeki eş zamanlılığı, varış hızı ile her isteğin ortalama gecikme süresinin çarpımına eşit olarak veren Little Kanunu ile açıklanır. Örneğin, bir sunucu saniyede 100 mesajı 100 ms ortalama ile işliyorsa, ortalama 10 iş parçacığı kullanıyordur. Gecikme süresi bir anda 10 saniyeye çıkarsa, kolayca bir iş parçacığı havuzunu tüketecek şekilde 1.000 iş parçacığını (ortalama; pratikte daha fazla olabilir) anında kullanmaya başlayabilir.
Kural altyapısı, bunu önlemek için pek çok teknik kullanır. İş parçacığı tükenmesini önlemek için engellenmeyen G/Ç kullanır ancak yine de belirli bir sunucunun sahip olduğu iş miktarı için diğer sınırlamalar bulunur (müşteri, bağlantıları işlerken bellek ve dosya tanımlayıcıları ile bağımlılığın zaman aşımına uğraması gibi). Kullanılabilecek ikinci bir eş zamanlılık koruması, herhangi bir anda tek bir iş yükü için kullanılabilecek eş zamanlılık miktarını ölçen ve sınırlandıran bir semafordur. Kural altyapısı aynı zamanda oran tabanlı adil sınırlama kullanır. Ancak, iş yüklerinin zaman içinde değişimi tamamen normal olduğundan, kural altyapısı da iş yüklerindeki değişikliklere uyum sağlamak için zaman içinde otomatik olarak ölçeklenir. Kural altyapısı kuyruğa dayalı olduğundan, IoT cihazları ile kaynakların otomatik ölçeklenmesi ve arka plandaki güvenlik sınırlamaları arasında tampon görevi görür.
Amazon'daki hizmetlerde, bir iş yükünün mevcut tüm iş parçacıklarını tüketmesini önlemek için her iş yükü için ayrı iş parçacığı havuzu kullanırız. Ayrıca her biri için izin verilen eş zamanlılığı sınırlandırmada her iş yükü için bir AtomicInteger ve orana dayalı kaynakları yalıtmak için orana dayalı kısıtlama yaklaşımları kullanırız.
Bir iş yükü, müşterinin yetişemeyeceği kadar büyük bir biriktirme listesi oluşturuyorsa, sistemlerimizin çoğu üreticide çalışmayı daha agresif bir şekilde otomatik olarak reddetmeye başlar. Bir iş yükü için bir günlük biriktirme listesi oluşması kolaydır. Bu iş yükü yalıtılsa bile, kazara meydana gelmiş ve işlenmesi masraflı olabilir. Bu yaklaşımın bir uygulaması, bir iş yükünün kuyruk uzunluğunun arada bir ölçülmesi (iş yükünün kendine ait kuyrukta olduğunu varsayarsak) ve biriktirme listesi boyutuna oransal olarak gelen kısıtlama sınırının ölçeklenmesi (tersine) kadar basit olabilir.
Birden fazla iş yükü için SQS kuyruğu paylaştığımız durumlarda, bu yaklaşım yanıltıcı olabilir. Kuyruktaki mesaj sayısını döndüren bir SQS API'si bulunurken, belirli özniteliğe sahip bir kuyruktaki mesaj sayısını döndürebilecek API yoktur. Yine de kuyruk uzunluğunu ölçebilir ve ters yönde baskıyı uygun şekilde kullanabiliriz ancak aynı kuyruğu paylaşan masum iş yükleri için ters yönde baskı kullanmak adil olmaz. Amazon MQ gibi diğer sistemlerde daha ayrıntılı biriktirme listesi görünürlüğü bulunur.
Ters yönde baskı, Amazon'daki her sistemde kullanılamaz. Örneğin, amazon.com'da sipariş işleyen sistemlerde bir biriktirme listesi oluştuğunda bile yeni siparişleri kabul etmeyi engellemektense, sipariş kabul etmeye devam etmeyi tercih ederiz. Ama tabii ki bu durumda en acil siparişlerin ilk önce işlenmesi için arka planda pek çok önceliklendirme işlemi yapılır.
Sistemler, belirli bir iş yükü için aktarım hızının azaltılması gerektiğini düşündüğünde, bu iş yükü için bir geri alma stratejisi kullanmayı deneriz. Bunu uygulamak için genellikle bir mesajın teslim edilmesini daha ileri bir zamana erteleyen bir SQS özelliğini kullanırız. Bir mesajı işlediğimizde ve daha sonrası için saklamaya karar verdiğimizde, bu mesajı bazen ayrı bir dalgalanma kuyruğuna alırız ancak gecikme parametresini, mesajın bir kaç dakika gecikme kuyruğunda gizli kalması için ayarlarız. Böylece sisteme daha yeni veriler üzerinde çalışma şansı sunulmuş olur.
SQS gibi bazı kuyruğa alma hizmetlerinde kuyruğun tüketicisine teslim edilebilecek işleme alınan mesaj sayısı için sınırlamalar bulunur. Bu, kuyruğa alınabilecek mesaj sayısından (pratikte bunda bir sınır yoktur) farklıdır; daha çok tüketici filosunun aynı anda üzerinde çalışabileceği mesaj sayısıdır. Bir sistem mesajları kuyruktan çıkartır ancak bunları silemezse, bu sayı artabilir. Örneğin, bir mesajı işlerken kodun bir istisnayı yakalamada başarısız olduğu ve mesajı silmeyi unuttuğu hatalarla karşılaştık. Böyle durumlarda mesaj, mesajın Görünürlük Zaman Aşımı için SQS perspektifinden işlemede kalır. Hatalarla başa çıkma ve aşırı yüklenme stratejilerimizi tasarlarken, bu sınırlamaları göz önünde bulunduruyoruz ve fazla mesajları, görünür kalmalarındansa farklı bir kuyruğa taşımayı tercih ediyoruz.
SQS FIFO kuyruklarında benzer ancak zor sınırlar vardır. SQS FIFO ile sistemler, belirli bir mesaj grubu için mesajlarınızı sırayla işler ancak farklı gruplardaki mesajlar herhangi bir sırayla işlenir. Yani bir mesaj grubu için küçük bir biriktirme listesi oluşturduğumuzda, diğer gruplardaki mesajları işlemeye devam ederiz. Ancak, SQS FIFO sadece işlenmemiş en yeni 20 bin mesajın yoklamasını yapar. Bu nedenle mesaj gruplarının bir alt kümesinde 20 binin üzerinde işlenmemiş mesaj varsa, diğer mesaj gruplarındaki yeni mesajlar kendi hallerine bırakılır.
İşlenemeyen mesajlar, sistemin aşırı yüklenmesine etki edebilir. Sistem, işlenemeyen bir mesajı kuyruğa alırsa (bir girdi onayı uç durumu tetiklediği için olabilir), SQS bu mesajları teslim edilemeyen kuyruk (DLQ) özelliği ile otomatik olarak ayrı bir kuyruğa taşımanıza yardımcı olabilir. Bu kuyrukta herhangi bir mesaj olduğunda uyarı veririz çünkü bu, düzeltmemiz gereken bir hatanın ortaya çıktığı anlamına gelir. DLQ'nun avantajı, hata düzeltildikten sonra mesajları yeniden işlememize izin vermesidir.
Bir iş yükü, kararlı durumdayken bile yoklama yapan iş parçacıklarının sürekli meşgul olmasına yol açacak şekilde aktarım hızı sunuyorsa, o zaman sistem, trafikteki dalgalanmayla başa çıkmak için bir tamponun bulunmadığı duruma gelmiş olabilir. Böyle bir durumda, gelen trafikteki ufak bir artış, daha uzun gecikme sürelerine yol açacak şekilde işlenmemiş biriktirme listesi uzunluğunun sürekli olmasına neden olacaktır. Bu tür artışlarla başa çıkmak için yoklama yapan iş parçacıklarında ek tampon planlarız. Ölçümlerden bir tanesi, boş yanıtlarla sonuçlanan yoklama denemelerinin sayısını takip etmektir. Tüm yoklama denemelerinde fazladan bir mesaj dönüyorsa, o zaman ya sahip olduğumuzu yoklama yapan iş parçacıklarının sayısının doğru olduğunu ya da gelen trafik için muhtemelen yetersiz olduğunu görürüz.
Sistem bir SQS mesajını işlerken, sistemin kilitlendiğini varsaymadan önce SQS, sisteme mesajı işlemeyi tamamlaması ve tekrar denemek üzere mesajı başka bir tüketiciye teslim etmesi için belirli bir zaman verir. Kod çalışmaya devam eder ve bu süre sınırını unutursa, aynı mesaj paralel olarak birden fazla kez teslim edilir. İlk işlemci zaman aşımından sonra bile mesaj üzerinde işlemeye devam ederken, ikinci bir işlemci onu alır ve aynı şekilde zaman aşımından sonra işlemeye başlar ve ardından üçüncü bir işlemciyle aynı olay devam eder. Bu art arda gelen kesinti olasılığı nedeniyle mesaj işleme mantığımızı bir mesajın tarihi geçtiğinde çalışmayı durduracak veya SQS'e hala üzerinde çalıştığımızı hatırlatması için sinyal vermeye devam edecek şekilde uyguluyoruz. Bu kavram, lider seçimindeki kiralamalara benzer.
Bu, muhtemelen veritabanına yapılan sorguların uzun zaman alması veya sunucuların başka çıkabileceklerinden daha fazla işi kabul etmesi nedeniyle bir sistemin gecikme süresinin aşırı yüklenme sırasında arttığını gördüğümüz için sinsi bir sorundur. Sistemin gecikme süresi VisibilityTimeout eşiğini aştığında, zaten aşırı yüklenmiş bir hizmetin kendisinde temel olarak fork-bomb oluşturur.
Dağıtılmış bir sistemdeki hataları anlamak zaten zordur. İzleme hakkındaki ilgili makale, zaman uyumsuz sistemleri izlemek için düzenli olarak kuyruk uzunluklarını kaydetme veya "izleme kimlikleri" dağıtma ve X-Ray ile entegre etme gibi bazı yaklaşımlarımızı açıklamaktadır. Veya sistemlerimizde, küçük bir SQS kuyruğunun ötesinde zaman uyumsuz karmaşık bir iş akışı oluştuğunda, iş akışı için görünürlük sağlayan ve dağıtılmış hata ayıklamayı basitleştiren Step Functions gibi zaman uyumsuz farklı bir iş akışı hizmetini bazen kullanırız.
Sonuç
Zaman uyumsuz bir sistemde, gecikme süresi hakkında düşünmenin ne kadar önemli olduğunu unutmak kolaydır. Sonuçta, zaman uyumsuz sistemler, güvenilir yeniden denemeler gerçekleştirmek için bir kuyruğun arkasında kaldıkları için bazen daha fazla zaman harcamaları beklenir. Ancak, aşırı yüklenme ve hata senaryoları, hizmetin makul bir sürede kurtarılamayacağı, başa çıkılması güç büyüklükte biriktirme listeleri oluşturabilir. Bu biriktirme listeleri tek bir iş yükünden veya müşterinin beklenmedik bir oranda kuyruğa alma işleminden, işlenmesinin tahmin edilenden daha masraflı olacağı iş yüklerinden veya bağımlılıktaki gecikme süresinden ve hatalardan dolayı oluşabilir.
Zaman uyumsuz bir sistem oluştururken, bu biriktirme listesi senaryolarına odaklanmalı ve bunları beklemeli; önceliklendirme, ayırma ve ters yönde baskı gibi teknikler kullanarak bunları en aza indirmeliyiz.
Daha fazla kaynak
• Kuyruğa alma teorisi
• Little kanunu
• Amdahl kanunu
• Little A Proof for the Queuing Formula: L = λW, Case Western, 1961
• McKenney, Stochastic Fairness Queuing, IBM, 1990
• Nichols and Jacobson, Controlling Queue Delay, PARC, 2011
Yazar hakkında
David Yanacek, AWS Lambda üzerinde çalışan bir Kıdemli Baş Mühendistir. David, 2006'dan bu yana Amazon'da çalışan bir yazılım geliştiricisidir. Daha önce Amazon DynamoDB ve AWS IoT'de, dâhili web hizmeti çerçevelerinde ve filo operasyonları otomasyon sistemlerinde çalışmıştır. David'in iş yerinde en sevdiği aktivitelerden birisi, sistemlerin zaman içinde daha hatasız çalışması için operasyonel ölçümler yoluyla günlük analizi ve elemeler yapmaktır.